How to disable TECH_DISCOVERED on the app? - android

I used DeepLink to launch my application after detecting a certain url address from NFC like this.
<activity
android:name=".view.main.MainActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="example.com"
android:scheme="http" />
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="#xml/nfc_tech_filter" />
</activity>
However, it works even when I already launched my application. So, duplicate activities keep coming up. And sometimes, it leads my app to malfunction or crash. I just want to use Deeplink while I am not using my application.
Is there any way to solve this problem?

You can use launchMode="singleTop". This way, if the activity already is launched and is on the top of the stack, any new launches would be routed through this instance and you would get this as a callback in the onNewIntent() method.
<activity android:name=".view.main.MainActivity"
android:launchMode="singleTop" />

Dinesh's solution seems like it should work, but if it's giving you problems, you can try an alternative (more complicated) one. You can use the NfcAdapter.enableForegroundDispatch() method to take control of the android.nfc.action.TECH_DISCOVERED action inside your activity and make sure it does nothing.
You can that by adding the following two methods to your MainActivity (or adding the code to existing ones):
#Override
protected void onResume() {
super.onResume();
//create a broadcast intent that doesn't trigger anything
Intent localIntent = new Intent("fake.action");
localIntent.setPackage(getApplicationContext().getPackageName());
localIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
//set the NFC adapter to trigger our broadcast intent
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.enableForegroundDispatch(
this,
PendingIntent.getBroadcast(this, 0, localIntent, PendingIntent.FLAG_UPDATE_CURRENT),
new IntentFilter[] {new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)},
new String[][] { { "android.nfc.tech.IsoDep" /* add other tag types as necessary */ } });
}
#Override
protected void onPause() {
super.onPause();
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.disableForegroundDispatch(this);
}
Now whenever your activity is visible and an NFC tag is detected, no action will be triggered. (Or, more specifically, it will trigger a broadcast to which no method is registered.)
Hope this helps.

Related

Why starting the Activity by an intent (Intent.ACTION_VIEW) doesn't always start a new activity and call onCreate()?

I have two Android applications.
The first application is the "browser". It gets an URL and displays it in a WebView. The corresponding activity is declared as:
<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.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
</intent-filter>
</activity>
The second application has a few buttons. Tapping each button opens the first application and sends the URL to it using Intent.ACTION_VIEW:
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse(urlString);
intent.setData(uri);
intent.setComponent(new ComponentName("com.custom.browser", "com.custom.browser.MainActivity"));
startActivity(intent);
I expect this code to start a new activity as per https://developer.android.com/reference/android/app/Activity. So the browser application retrieves the URL in onCreate() by using the code:
#Override
protected void onCreate(Bundle savedInstanceState) {
...
Intent intent = getIntent();
if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
displayUrl(intent.getDataString());
}
...
}
However I found that sometimes onCreate() is not called. After calling startActivity(intent), the browser application is just brought to the front, therefore displaying a previous URL.
I can override this behavior by moving the intent retrieval code in the onResume().
However I'd like to understand what am I doing wrong? Shouldn't the method startActivity(Intent) always start a new activity and always call onCreate(), as suggested by the Android documentation?
I expect this code to start a new activity
That is not necessarily what will happen.
However I found that sometimes onCreate() is not called. After calling startActivity(intent), the browser application is just brought to the front,
Yes, that will happen if the activity you are starting is already running at the front of a task. See the documentation for tasks.
I can override this behavior by moving the intent retrieval code in the onResume().
That will not work. Override onNewIntent() and get the new Intent there. Or, adjust the flags in your Intent, or adjust the manifest settings for the activity that you are starting, as is discussed in the documentation for tasks.

Links are opening in Android webview creating multiple instances

I am working on an Android webview app and opening www.xyz.com in it.
When the app is running in the background and if I try to open Whatsapp/SMS received message-www.xyz.com/example then it is opening in a new instance and not opening in the already running instance which is in background.
And when I tried using android:launchMode="singleTask" it resumes www.xyz.com only instead of opening www.xyz.com/example.
Below is my AndroidManifest.xml
<activity android:name=".MainActivity" android:screenOrientation="portrait">
<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="www.xyz.com"
android:scheme="https"
/>
</intent-filter>
</activity>
Below is the MainActivity.java code -
protected void onResume() {
super.onResume();
Uri data = getIntent().getData();
if (data != null && data.isHierarchical()) {
String uri = this.getIntent().getDataString();
myWebView.loadUrl(uri);
Log.i("MyApp", "Deep link clicked " + uri);
}
}
Please let me know what I am doing wrong? Thank You.
By using launchMode="singleTask" and according to Android docs (see https://developer.android.com/guide/components/activities/tasks-and-back-stack):
The system creates a new task and instantiates the activity at the
root of the new task. However, if an instance of the activity already
exists in a separate task, the system routes the intent to the
existing instance through a call to its onNewIntent() method, rather
than creating a new instance. Only one instance of the activity can
exist at a time.
Add to your main activity an onNewIntent handler where you should see the new URL being passed in newIntent:
#Override
protected void onNewIntent(Intent newIntent)
{
// reload WebView
}
Additionally, I'm not sure that using onResume to mywebView.loadUrl() is a good or necessary practice, as even simple pause/resume events would lead to the page being reloaded. I'd suggest loading it in onCreate(), and reloading it in onNewIntent().

Hide NFC app from "Choose Application" list / Disable start by external NFC intent

I'm currently writing a couple of NFC enabled apps for Android and wanted to know how I can stop my application from being in the "choose application" list that's opened whenever a tag is scanned from the launcher or a non-NFC app. I only want my apps to be able to read the tags when they are open.
My current intent filters:
<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:mimeType="text/plain" />
</intent-filter>
<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" />
nfc_tech_filter.xml:
<tech-list>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
You currently have a couple of NFC related intent filters in your app's manifest:
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
By adding such intent filters to the manifest of your app, you indicate that certain activities should be started (or presented as options in the intent chooser if multiple activities are registered for the same intents) upon these NFC tag discovery events.
Consequently, you need to remove those intent filters from the AndroidManifest.xml if you don't want your app to be launchable by those intents.
However, you would then also lose the ability to listen for those intents while an activity of your app is in the foreground. Since NFC intents are activity intents, you can't receive them through dynamically registered broadcast receivers (cf. RubbelDieKatz's answer). Instead, the Android API provides alternative means to catch NFC related events while an activity of your app is displayed in the foreground (this also allows your app to get precedence over other apps, which is not possible with manifest-based NFC intent filters):
Since API level 10 (basically since the beginning of Android NFC), Android features the foreground dispatch system. You can register your foreground activity to receive NFC events by using the method NfcAdapter.enableForegroundDispatch() upon onResume() (don't forget to unregister when your activity loses focus in onPause()):
public void onResume() {
super.onResume();
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
adapter.enableForegroundDispatch(this, pendingIntent, null, null);
}
public void onPause() {
super.onPause();
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
adapter.disableForegroundDispatch(this);
}
You could even register to receive only a specific subset of NFC discovery events by providing the optional tech-list and intent-filters arguments to enableForegroundDispatch().
Once you registered your activity with the foreground dispatch system, you will receive NFC events through the onNewIntent() callback:
public void onNewIntent(Intent intent) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
...
}
Starting with API level 19, there is the new NFC reader mode API. You can now register to receive regular callbacks (instead of going through the intent dispatch which sometimes causes soome hickups). You can do this by using the method NfcAdpater.enableReaderMode():
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
adapter.enableReaderMode(this, this, NfcAdapter.FLAG_READER_NFC_A, null);
}
By using other flags in addition to (or instead of) NfcAdapter.FLAG_READER_NFC_A you could set to reader mode to listen for other tag technologies as well.
You will then receive callbacks upon tag discovery (the receiver needs to implement the callback interface NfcAdapter.ReaderCallback):
public void onTagDiscovered(Tag tag) {
...
}
Also see What's the difference between enableReaderMode and enableForegroundDispatch? for a more detailed comparison between the two options.
You're using the NFC and Tech Discovered intent-filters. You could remove them and register intent listeners programmatically in your app.
<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:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
About registering the intent: Here's a very good answer.
In your onCreate method you can register a receiver like this:
private BroadcastReceiver receiver;
#Override
public void onCreate(Bundle savedInstanceState){
// your oncreate code should be
IntentFilter filter = new IntentFilter();
filter.addAction("SOME_ACTION");
filter.addAction("SOME_OTHER_ACTION");
receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
//do something based on the intent's action
}
};
registerReceiver(receiver, filter);
}
Remember to run this in the onDestroy method:
#Override
protected void onDestroy() {
if (receiver != null) {
unregisterReceiver(receiver);
receiver = null;
}
super.onDestroy();
}
Another interesting quote from the answer below that one:
One important point that people forget to mention is the life time of
the Broadcast Receiver. The difference of programmatically
registering it from registering in AndroidManifest.xml is that.
In the manifest file, it doesn't depend on application life time.
While when programmatically registering it it does depend on the
application life time. This means that if you register in
AndroidManifest.xml, you can catch the broadcasted intents even when your application is not running.
Edit: The mentioned note is no longer true as of Android 3.1, the Android system excludes all receiver from receiving intents by default
if the corresponding application has never been started by the user or
if the user explicitly stopped the application via the Android menu
(in Manage → Application).
https://developer.android.com/about/versions/android-3.1.html
This is an additional security feature as the user can be sure that
only the applications he started will receive broadcast intents.
So it can be understood as receivers programmatically registered in
Application's onCreate() would have same effect with ones declared
in AndroidManifest.xml from Android 3.1 above.
If you unregister your receivers when your activity is killed, your application won't receive these broadcasts anymore.

How to read NFC tag data globally inside an Android Application

I would like to read the NFC tag data inside my application. Basically I have different NFC tags associated with different actions to perform inside my application. I know declaring the IntentFilter inside the Service is a bad idea. I don't want to associate this to any Activity.
Here is my manifest:
<service android:name="com.yo.helpers.NFCReaderHelper"
android:enabled="true"
android:exported="true" >
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</service>
Here is my Service class:
#Override
protected void onHandleIntent(Intent intent) {
Log.d("yoyo", "Service");
Tag mTag = null;
if(intent.getAction()!=null)
{
if(intent.getAction().equals("android.nfc.action.NDEF_DISCOVERED"))
Log.d("yoyo", "Intent call");
mTag = (Tag) intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
Log.i("tag ID", bytesToHexString(mTag.getId()));
}
}
I don't get the call inside the onHandleIntent method. If I do the same inside an Activity then I receive the intent inside the onNewIntent method.
You can't receive the NFC discovery intents (NDEF_DISCOVERED, TECH_DISCOVERED, TAG_DISCOVERED) with a service (or a broadcast receiver). NFC on Android is designed as a means of user interaction and, hence, these intents are only delivered to activities.
A trick that you could use is to use an invisible/transparent activity to handle the intent. See How to run android program without open the app?

Android launch activity multiple times on an NFC tag

My Android app has 2 activities, a main one for info and one for receiving NFC.
When launching the app for the first time, I can read NFC tags, multiple times - each time bringing up a new activity and showing some info.
If the app is closed but the phone is brought to the NFC tag - it will show the nfc tag activity the first time, but never respond to any other tags again.
What am I doing wrong?!
Manifest part and code for second activity:
<uses-sdk android:minSdkVersion="10" />
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
<application
android:icon="#drawable/aaa"
android:label="#string/app_name"
android:theme="#android:style/Theme.NoTitleBar">
<activity
android:label="#string/app_name"
android:name=".MainActivity">
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".TagDiscoveredActivity"
android:screenOrientation="portrait">
<intent-filter >
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="#xml/filter_nfc" />
</activity>
</application>
</manifest>
The code
public class TagDiscoveredActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.info);
etc
}
#Override
public void onNewIntent(Intent intent) {
setIntent(intent);
resolveIntent(intent);
}
private void resolveIntent(Intent intent) {
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
//| Intent.FLAG_ACTIVITY_SINGLE_TOP);
boolean handled = false;
// Parse the intent
final String action = intent.getAction();
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) ||
NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
// When a tag is discovered we send it to the service to be save. We
// include a PendingIntent for the service to call back onto. This
// will cause this activity to be restarted with onNewIntent(). At
// that time we read it from the database and view it.
Parcelable nfctag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (nfctag != null) {
//read tag and display here
}
}
}
if (!handled) {
Log.e(logTag, "Unknown intent " + intent);
finish();
return;
}
}
When I run it and log for the second scenario - launching direct from NFC without the app running - the log shows it working first time, but the second time, none of the functions are logging anything.
Thank you for any helpful suggestions.
I found the answer finally after trying everything.
The answer is to set the activity to android:launchmode="singleTask",
and in the code in onNewIntent add the lines:
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
What you are probably looking for is foreground dispatch like in this example. I have also written an Android boilerplate (shameless plug) which you might find interesting.

Categories

Resources