Ok so I've been bangin away at my first Android app and the NFC has been very hit and miss. I'm able to successfully pickup on plain text type records, but when I switch over to try to pickup on uri records the phone's browser keeps opening rather than my app. I'm clueless at this point so here's what I got...
<activity
android:name=".MainActivity"
android:label="#string/title_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="http" android:host="google.com"/>
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<data android:mimeType="text/plain"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
When I read the tag, I get a new intent but it's action type is "MAIN". Is it just being relaunched? And if so why doesn't the text record do the same? I've tried multiple uri records and every time I get this behavior. Here is part of the java src.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent launchIntent = getIntent();
String action = launchIntent.getAction();
if(action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED)) {
Log.i(tag, "MATCH!");
}
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
tagDetected = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
tagDetected.addDataScheme("http");
tagDetected.addDataAuthority("google.com", null);
filters = new IntentFilter[] { tagDetected };
techArray = new String[][] {
new String[] {
IsoDep.class.getName()
},
new String[] {
NfcB.class.getName()
},
new String[] {
Ndef.class.getName()
}
};
}
public void onResume(){
super.onResume();
nfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, filters, techArray);
Intent launchIntent = getIntent();
String action = launchIntent.getAction();
if(action.equals(NfcAdapter.ACTION_NDEF_DISCOVERED)) {
Log.i(tag, "MATCH!");
} else if(action.equals(NfcAdapter.ACTION_TECH_DISCOVERED)) {
Log.i(tag, "TECH DISCOVERED");
} else if(action.equals(NfcAdapter.ACTION_TAG_DISCOVERED)) {
Log.i(tag, "TAG DISCOVERED");
}
Parcelable[] msg = launchIntent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
//byte[] payloadData = msg.getRecords()[0].getPayload();
//Log.i(tag, "NUM records = " + Integer.toString(msg.getRecords().length));
}
public void onPause() {
super.onPause();
nfcAdapter.disableForegroundDispatch(this);
}
Another interesting note is that when I don't include the list of technologies in the enableForegroundDispatch() call, then the app doesn't pickup any intents resulting from NFC at all (when trying to read uri records). Any ideas oh wise internet?!
With ForegroundDispatch enabled, the intent will arrive through onNewIntent(), so you need to override that method in your activity to receive the NFC intent.
You also need to make sure that the hostname matches exactly. If the tag contains "www.google.com", your intent filter should contain the same name: <data android:scheme="http" android:host="www.google.com"/> and tagDetected.addDataAuthority("www.google.com", null).
Related
My question is how to start app A within app B that appears app A is within app B through deep linking?
In the first picture below, the Debug app appears as a separate app from Slack (new code, Firebase deep linking). In the 2nd picture, the Debug app appears to be within the Slack app (old code, Android deep linking). I want to use Firebase deep linking and show the Debug app within other apps (Slack, Gmail etc).
Can anyone please go through my code below and let me know how I can achieve this?
Old code, Android deep linking:
AndroidManifest
<activity
android:name=".activity.SplashScreenActivity"
android:screenOrientation="portrait"
android:theme="#style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with "abc://flights” -->
<data
android:host="sales"
android:scheme="abc" />
<data
android:host="deals"
android:scheme="abc" />
</intent-filter>
</activity>
Activity:
Intent intent = new Intent(SplashScreenActivity.this, BottomNavBarActivity.class);
//Deep Linking Content
Uri deepLinkData = getIntent().getData();
if (deepLinkData != null) {
intent.putExtra(EXTRA_DEEP_LINK, deepLinkData.getHost());
}
startActivity(intent);
overridePendingTransition(R.anim.splash_fade_in, R.anim.splash_fade_out);
finish();
New Code, Firebase deep linking:
AndroidManifest:
<activity
android:name=".activity.SplashScreenActivity"
android:screenOrientation="portrait"
android:theme="#style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="abc.app.goo.gl"
android:scheme="http"/>
<data
android:host="abc.app.goo.gl"
android:scheme="https"/>
</intent-filter>
</activity>
Activity:
FirebaseDynamicLinks.getInstance()
.getDynamicLink(getIntent())
.addOnSuccessListener(this, new OnSuccessListener<PendingDynamicLinkData>() {
#Override
public void onSuccess(PendingDynamicLinkData pendingDynamicLinkData) {
// Get deep link from result (may be null if no link is found)
Uri deepLink = null;
if (pendingDynamicLinkData != null) {
// Start the activity through intent, same as before.
}
}
})
.addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.v(TAG, "Firebase deep link failure");
}
});
Its dependent on the app which sends the intent, for example whether they pass FLAG_ACTIVITY_NEW_TASK or not. I suspect the difference here is how Slack is handling the links - they may treat web URLs differently than other format ones (your old links have non-standard schemes).
Create a method openApp(), and call it accorrding to your need.
public void openAnApp()
{
Boolean flag=false;
try{
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
final PackageManager packageManager = getActivity().getPackageManager();
Intent intent1 = new Intent(Intent.ACTION_MAIN, null);
intent1.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> resInfos = packageManager.queryIntentActivities(intent1, 0);
ActivityInfo activity = null;
//getting package names and adding them to the hashset
for(ResolveInfo resolveInfo : resInfos) {
System.out.println("apap="+resolveInfo.activityInfo.packageName);
if(resolveInfo.activityInfo.packageName.equals("your.app.packagename"))
{
flag = true;
activity = resolveInfo.activityInfo;
break;
}
}
if (flag) {
// final ActivityInfo activity = app.activityInfo;
final ComponentName name = new ComponentName(activity.applicationInfo.packageName,activity.name);
intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setComponent(name);
getActivity().startActivity(intent);
//startActivity(Intent.createChooser(intent , "Send image using.."));
} else {
Uri uri=Uri.parse("market://details?id=your.app.packagename");
Intent goToMarket=new Intent(Intent.ACTION_VIEW,uri);
try{
startActivity(goToMarket);
}catch(ActivityNotFoundException e){
Toast.makeText(getActivity(),"Couldn't launch the market",Toast.LENGTH_SHORT).show();
}
}
} catch (Exception e) {
Toast.makeText(getActivity(), "Something went wrong", Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
I've read the documentation a few times now and looked at the example code but I can't seem to figure out what I'm doing wrong.
Whenever I put my two devices together I see the Beam screen and tap it, but nothing seems to be sent and none of my breakpoints get hit.
Here's my code, please help me figure out why nothing is sent to the other device. In the editor I can see that everything in BeamFileActivityis run, I just never see any of the break points in MainActivity get accessed on the receiving device. What am I doing wrong?
AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.myapp">
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
<application
android:name=".MainActivity"
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme">
<meta-data
android:name="QUERY_LOG"
android:value="false" />
<meta-data
android:name="DOMAIN_PACKAGE_NAME"
android:value="com.myapp.domain" />
<activity
android:name=".MainActivity"
android:exported="true"
android:label="#string/app_name"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="com.google.android.apps.drive.DRIVE_OPEN" />
<action android:name="com.google.android.apps.drive.DRIVE_SAVE" />
<data android:mimeType="application/vnd.google-apps.drive-sdk.111111111" />
<data android:mimeType="application/vnd.google-apps.spreadsheet" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="vnd.android.nfc"
android:host="ext"
android:pathPrefix="/com.myapp:SpreadsheetDom"/>
</intent-filter>
</activity>
<activity
android:name=".dialog.BeamFileActivity"
android:label="#string/title_activity_beam_file"
android:parentActivityName=".MainActivity"
android:theme="#style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.myapp.MainActivity" />
</activity>
</application>
MainActivity: This is what will receive the Beam intent, I'm only including the methods I think are relevant.
#Override
protected void onResume() {
super.onResume();
//I removed some code that reloads my application, didn't seem relevant
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
processIntent(getIntent());
}
}
/**
* Parses the NDEF Message from the intent and stores the collection
*/
void processIntent(Intent intent) {
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
// only one message expected during the beam
NdefMessage msg = (NdefMessage) rawMsgs[0];
//This is the data I'm beaming, It has a method that converts its data to a byte array, and a method, fromBytes() that converts bytes into the object
SpreadsheetDom dom = new SpreadsheetDom();
try {
for(int i = 0; i < msg.getRecords().length; i++) {
if(SpreadsheetDom.class.getName().equalsIgnoreCase(new String(msg.getRecords()[i].getType()))) {
dom.fromBytes(msg.getRecords()[i].getPayload());
//removed some code that saves and displays changes
}
}
} catch(IOException | ClassNotFoundException e) {
}
}
#Override
public void onNewIntent(Intent intent) {
setIntent(intent);
}
BeamFileActivity: This is a separate activity that just displays instructions for beaming the data and does the actual beam.
private void initFileToBeam(Long spreadsheetId) {
try {
List<SpreadsheetDom> spreadsheets = db.getSpreadsheetDao().queryForEq(SpreadsheetDom.SPREADSHEET_ID_NAME, spreadsheetId);
if(spreadsheets != null && spreadsheets.size() > 0) {
fileToBeam = spreadsheets.get(0);
}
} catch(SQLException e) {
ErrorDialog ed = new ErrorDialog(this, "Unable to beam current collection.");
ed.show();
}
}
#Override
public NdefMessage createNdefMessage(NfcEvent event) {
if(fileToBeam == null) {
Long spreadsheetId = Settings.getInstance().get(SettingKey.CURRENT_SPREADSHEET).getValue();
initFileToBeam(spreadsheetId);
}
NdefMessage msg = null;
try {
msg = new NdefMessage(
new NdefRecord[]{
new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, "application/com.myapp:SpreadsheetDom".getBytes(), new byte[0], fileToBeam.toBytes()),
//NdefRecord.createExternal("com.myapp", "spreadsheetdom", fileToBeam.toBytes()),
NdefRecord.createApplicationRecord("com.myapp")
});
} catch(IOException e) {
String textMessage = "Unable to transfer collection: " + e.getMessage();
msg = new NdefMessage(
new NdefRecord[]{
NdefRecord.createMime("text/plain", textMessage.getBytes()),
NdefRecord.createApplicationRecord("com.myapp")
});
}
return msg;
}
#Override
public void onResume() {
super.onResume();
Intent exportFileIntent = getIntent();
Long spreadsheetId = exportFileIntent.getLongExtra(Constants.EXTRA_SPREADSHEET_ID, Database.INVALID_ID);
initFileToBeam(spreadsheetId);
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNfcAdapter == null) {
Toast.makeText(this, "NFC is not available", Toast.LENGTH_LONG).show();
finish();
return;
}
// Register callback
mNfcAdapter.setNdefPushMessageCallback(this, this);
}
Really all of this is from the examples and documentation here, here and here
You are sending a different NDEF record than what your MainActivity expects. Your MainActivity is registered for the NFC Forum external type "com.myapp:SpreadsheetDom":
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="vnd.android.nfc"
android:host="ext"
android:pathPrefix="/com.myapp:SpreadsheetDom"/>
</intent-filter>
Since intent filters in Android are case-sensitive but NFC Forum external type names are case-insensitive, Android automatically converts the names of NFC Forum external types (just as with MIME types) to lower-case. As your intent filter contains the upper-case letters "S" and "D", it will never match the type name of your NFC Forum external type. Therefore, you have to specify the type name as all lower-case to achieve a match (see also here):
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="vnd.android.nfc"
android:host="ext"
android:pathPrefix="/com.myapp:spreadsheetdom"/>
</intent-filter>
Next, in your BeamFileActivity you create an external record with the invalid type name "application/com.myapp:SpreadsheetDom":
msg = new NdefMessage(new NdefRecord[] {
new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, "application/com.myapp:SpreadsheetDom".getBytes(), new byte[0], fileToBeam.toBytes()),
NdefRecord.createApplicationRecord("com.myapp")
});
An NFC Forum external type name that matches the above intent filter would be "com.myapp:spreadsheetdom" (again, all lower-case letters). You can create such a record with (make sure to use US-ASCII encoding for the type name):
msg = new NdefMessage(new NdefRecord[] {
new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, "com.myapp:spreadsheetdom".getBytes("US-ASCII"), new byte[0], fileToBeam.toBytes()),
NdefRecord.createApplicationRecord("com.myapp")
});
Finally, note that "com.myapp" is not a well-formed domain name for an external type according to the NFC Forum Record Type Definition. Instead, a well-formed domain name would be "myapp.com" (Internet domain name format instead of Java package name format).
i've tried to sending data between App1 to App2 via Intent in Android
i used this code but i couldn't resolve my problem.
App1 MainActivity :
Intent i2 = new Intent("com.appstore.MainActivity");
i2.setPackage("com.appstore");//the destination packageName
i2.putExtra("Id", "100");
startActivity(i2);
App2 MainActivity :
Bundle data = getIntent().getExtras;
if(data!=null){
String myString = b.getString("Id");
}
Manfiest App2 MainActivity:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
Final code:
App 1 :
Intent intent = new Intent();
intent.setClassName("com.appstore", "com.appstore.MyBroadcastReceiver");
intent.setAction("com.appstore.MyBroadcastReceiver");
intent.putExtra("KeyName","code1id");
sendBroadcast(intent);
App 2:
Reciver:
public class MyBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Data Received from External App", Toast.LENGTH_SHORT).show();
}
}
Manifest :
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="first_app_packagename" />
</intent-filter>
</receiver>
MainActivity :
MyBroadcastReceiver mReceiver = new MyBroadcastReceiver();
registerReceiver(mReceiver,
new IntentFilter("first_app_packagename"));
My requirement was to send the "user id" from App1 to App2 and get "username" back to App1.
I needed to launch my app directly without any chooser. I was able to achieve this using implicit intent and startActivityForResult.
App1 > MainActivity.java
private void launchOtherApp() {
Intent sendIntent = new Intent();
//Need to register your intent filter in App2 in manifest file with same action.
sendIntent.setAction("com.example.sender.login"); // <packagename.login>
Bundle bundle = new Bundle();
bundle.putString("user_id", "1111");
sendIntent.putExtra("data", bundle);
sendIntent.setType("text/plain");
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(sendIntent, REQUEST_CODE);
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
Bundle bundle = data.getBundleExtra("data");
String username = bundle.getString("user_name");
result.success(username);
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
I had two activity in App2 ie. MainActivity and LoginActivity.
App2 > AndroidManifest.xml
<activity android:name=".LoginActivity">
<intent-filter>
<!--The action has to be same as App1-->
<action android:name="com.example.sender.login" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
Sorry for this I had a little mix up with Java and Kotlin. My second app was in Kotlin, not that it will effect in any way.
App2 > LoginActivity.java
override fun onResume() {
super.onResume()
var userId = "No data received"
val intent = intent
if (intent != null
&& intent.action != null
&& intent.action.equals("com.example.sender.login")
) {
val bundle = intent.getBundleExtra("data")
if (bundle != null) {
userId = bundle.getString("user_id")
userId = " User id is $userId"
}
}
tvMessage.text = "Data Received: $userId"
}
fun onClickBack(view: View) {
val intent = intent
val bundle = Bundle()
bundle.putString("sesion_id", "2222")
intent.putExtra("data", bundle)
setResult(Activity.RESULT_OK, intent)
finish()
}
When you do this:
Intent i2 = new Intent("com.appstore.MainActivity");
i2.setPackage("com.appstore");//the destination packageName
i2.putExtra("Id", "100");
startActivity(i2);
you are calling the single-argument constructor of Intent. In this constructor, the argument is interpreted as the Intent ACTION. You then set the package name in the Intent.
When you call startActivity() with this Intent, Android will look for an Activity that contains an <intent-filter> with the specified ACTION. There are no installed applications that have an Activity defined like this in the manifest:
<activity>
<intent-filter>
<action android:name="com.appstore.MainActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
So Android will not be able to find and launch the Activity that you want.
As you want to specify explicitly the component that you want to use, instead of using the 1-argument Intent constructor, you should do this instead:
Intent i2 = new Intent();
i2.setClassName("com.appstore", "com.appstore.MainActivity");
i2.putExtra("Id", "100");
startActivity(i2);
Using setClassName() you provide the package name and the class name of the component that you want to launch.
This should work:
APP1
Intent i2 = new Intent();
i2.setComponent(new ComponentName(PACKAGE,ACTIVITY));//the destination packageName
i2.putExtra("Id", "100");
startActivity(i2);
APP2
String myString = getIntent().getStringExtra("Id");
Using Bundle.putSerializable(Key,Object); and Bundle.putParcelable(Key, Object);
the former object must implement Serializable, and the latter object must implement Parcelable.
Content providers:
Content providers are the standard interface that connects data in one process with code running in another process.
See Android Docs.
Content provider working demo here.
I am trying to use Android Beam to transfer large files between apps. The sending part is working well and files are appearing in the 'beam/' directory. The Notification status bar displays "Beam complete". However, the receiving app does not get notified after the files are renamed into the beam/ directory and onNewIntent() never gets called on the receiving end. What am I missing with the intent-filter? Also is it possible to specify a Android Application Record while using createBeamUris()? TIA
// sending app
nfcAdapter.setBeamPushUrisCallback(this, this);
...
#Override
public Uri[] createBeamUris(NfcEvent event) { // send files
File dir = Environment.getExternalStorageDirectory();
File file = new File(dir, "test.txt");
file.setReadable(true, false); // readable=true, ownerOnly=false
return new Uri[] { Uri.fromFile(file) };
}
My Manifest.xml:
<activity
android:name=".BeamDemo2"
android:label="#string/app_name"
android:launchMode="singleTop" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.txt" />
<data android:host="*" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/com.example.beamdemo2" />
</intent-filter>
</activity>
I have also tried enableForegroundDispatch():
#Override
public void onResume() {
super.onResume();
try {
PendingIntent mPendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
IntentFilter ndefIntent = new IntentFilter("android.intent.action.VIEW");
ndefIntent.addCategory("android.intent.category.DEFAULT");
ndefIntent.addDataType("*/*");
IntentFilter[]mIntentFilters = new IntentFilter[] { ndefIntent };
String[][] mNFCTechLists = new String[][] { new String[] { NfcF.class.getName() } };
mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, mIntentFilters, mNFCTechLists);
} catch (Exception e) {
Log.e("onResume", e.toString());
}
}
Short answer, there is no answer :-(. After the Beam finishes, one needs to pull down the "Beam Complete" notification and select "View File". This causes a Chooser to appear, and then a VIEW intent is dispatched to the activity. I was hoping to avoid this manual step, but it seems to be unavoidable. I'd also like to avoid the Chooser to select an application for VIEW, but that too seems unavoidable. EnableForegroundDispatch seems to be only for android.nfc.action.NDEF_DISCOVERED.
After choosing the app, things work as expected. I see onNewIntent(), followed by onResume(). One weirdness, the received intent seems to stick and keeps reappearing. After processing the VIEW intent in onResume(), I had to kill it by calling setIntent(null).
#Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
#Override
public void onResume() {
super.onResume();
Intent intent = getIntent();
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
processIntent(intent); //
setIntent(null); // kill this event
}
}
I am new to NFC. I tried to connect to NFC and share the text data from a device to another device.
I install my application on both devices and in one device I open my application and start tapping the device to the another device in order to transmit data over Beam.
On the other device, my application opens due to the Beam interaction. However, the activity is started with the default MAIN intent action and not with TAG_DISCOVERED (or similar NFC intent). Every time it does the same thing.
Also, it's not calling the onNewIntent() method. I tried to call onNewIntent from onCreate, but the intent action is still MAIN in that case. I expected to receive an NFC intent for the Beam interaction. So can you please tell me where did I go wrong?
In this code I am not sharing the data. I just need the tag first.
Manifest:
<activity android:name="com.framentos.hellonfc.MainActivity"
android:clearTaskOnLaunch="true"
android:label="#string/app_name"
android:launchMode="singleTop"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="#xml/nfc_tech_filter" />
</activity>
Java code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ctx = this;
// startHandler();
_handler = new Handler();
Button btnWrite = (Button) findViewById(R.id.button);
message = (TextView) findViewById(R.id.nfcwriteTag);
btnWrite.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}
});
adapter = NfcAdapter.getDefaultAdapter(this);
if (adapter == null) {
message.setText("NFC is not supported on this device.");
}
if (adapter.isEnabled()) {
message.setText("NFC is Enabled on this device.");
} else {
message.setText("Please enable NFC to communicate.");
}
pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
IntentFilter tagDetected = new IntentFilter(
NfcAdapter.ACTION_NDEF_DISCOVERED);
tagDetected.addCategory(Intent.CATEGORY_DEFAULT);
IntentFilter tagTech = new IntentFilter(
NfcAdapter.ACTION_TECH_DISCOVERED);
tagTech.addCategory(Intent.CATEGORY_DEFAULT);
IntentFilter tagDetect = new IntentFilter(
NfcAdapter.ACTION_TAG_DISCOVERED);
tagDetect.addCategory(Intent.CATEGORY_DEFAULT);
writeTagFilters = new IntentFilter[] { tagDetected, tagTech ,tagDetect};
// handleIntent(getIntent());
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.i(getPackageName(), "on New Intent is called...!");
handleIntent(getIntent());
}
public void onPause() {
super.onPause();
WriteModeOff();
}
#Override
public void onResume() {
super.onResume();
WriteModeOn();
}
private void WriteModeOn() {
writeMode = true;
adapter.enableForegroundDispatch(this, pendingIntent, writeTagFilters,
null);
}
private void WriteModeOff() {
writeMode = false;
adapter.disableForegroundDispatch(this);
}
From the code that you posted in your question, I assume that you did not register your application to send a specific NDEF message. In that case, if your app is open on one device, Android will automatically Beam an NDEF message containing a URI record with the Play Store link of your app and an Android Application Record (AAR) to the other device.
So your second device will receive the following NDEF message:
+---------------------------------------------------------------------------------------+
| WKT:URI | http://play.google.com/store/apps/details?id=your.package.name&feature=beam |
+---------------------------------------------------------------------------------------+
| EXT:android:com:pkg | your.package.name |
+---------------------------------------------------------------------------------------+
What happens now if your app is not already open on the second device is, that the Android Application Record (second record) will force your app to be started. However, looking at your manifest, you do not have an intent filter that matches the first record of this NDEF message (the Play Store URL). Consequently, Android thinks that you do not expect an NDEF message and uses the standard android.intent.action.MAIN (with category android.intent.category.LAUNCHER) to start your app (or rather the first activity of your app that has an intent filter for action MAIN with category LAUNCHER).
In order to receive an NFC intent together with the whole NDEF message in your app, you would need to define a proper intent filter that matches the first record in the above NDEF message:
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http"
android:host="play.google.com"
android:pathPrefix="/store/apps/details?id=your.package.name" />
</intent-filter>
Now, Android will recognize that your app/activity is ready to receive an NFC intent and pass the NDEF_DISCOVEREDintent to your activity. Note that you still won't receive that intent through onNewIntent() if your app is not already running. Instead you can get the intent that started your activity with the activity's getIntent() method. E.g. in onCreate/onStart/onResume, you could use
Intent intent = getIntent();
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
NdefMessage ndefMessage = null;
Parcelable[] rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if ((rawMessages != null) && (rawMessages.length > 0)) {
ndefMessage = (NdefMessage)rawMessages[0];
}
// TODO: do something with the received NDEF message
}
to get the intent and the NDEF message.
Regarding the intent filters you already have in your manifest:
NDEF_DISCOVERED:
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
An NDEF_DISCOVERED intent filter without a <data ... /> tag will never trigger on many NFC devices. You should always define what specific data you expect to be present in the launching NDEF record.
TECH_DISCOVERED:
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="#xml/nfc_tech_filter" />
You should only use this intent filter if you wan't you application to be started upon detection of a certain NFC tag technology (you define the specific technologies in the nfc_tech_filter.xml file. For your Beam scenario, you would not use such an intent filter. Btw. the <category ...> tag is not used for this intent filter.
TAG_DISCOVERED:
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
This intent filter should normally not be used in the manifest. It is meant as a fallback that triggers if no other app could possibly handle a detected NFC tag. Using this can lead to bad user-experience (e.g. your app being started for tags it can't or does not actually want to handle). It is primarily available for backward compatibility with API level 9 (?).
In order to also catch the NFC intents if your activity is already started, you could register for the foreground dispatch like this (in the onResume method):
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
That way, you will receive any NFC discovery events that occur while your activity is in the foreground in your activity's onNewIntent method. They will be dispatched through an ACTION_TAG_DISCOVERED intent.