w3c / web-nfc

Web NFC
https://w3c.github.io/web-nfc/
Other
313 stars 69 forks source link

NDEF Read triggered when writting an NDEF #551

Closed OlivierGrenoble closed 3 years ago

OlivierGrenoble commented 4 years ago

When writting an NDEF with writer.write(), I have noticed that the NFC service is immediately processing the NDEF freshly written. I have been told to run scan() and discard the onreading events.

Isn't that a pity that it is not the default behavior? When writing to a tag, one seldom needs the processing by NFC service of the NDEF freshly written. That makes the things harder for someone wanting to write an NDEF into an NFC tag.

beaufortfrancois commented 4 years ago

To be sure I understand this correctly, you're saying that Android NFC service reads content of NDEF tags and starts an "NFC tag read" activity after website has successfully written something to the NFC tag. In that case, I wonder if the browser should "catch" all NFC tags discovered events and dispatch them appropriately, so that web developers don't have to run scan() to catch them manually.

What do you think @leonhsl?

kenchris commented 4 years ago

This is what the ignoreRead was for

kenchris commented 4 years ago

@beaufortfrancois I still had the plan to join the reader/writer to avoid cases like these

beaufortfrancois commented 4 years ago

ignoreRead is used with an NDEFReader scan session. I think @OlivierGrenoble is using only NDEFWriter there. In that case, it is not necessarily clear he needs to run NDEFReader scan().

zolkis commented 4 years ago

I still had the plan to join the reader/writer to avoid cases like these

I would support that, but IIRC wasn't there an argument to keep them separate because they might be handled in completely different code? IIRC you had a use case for that in a web app.

So if we keep them separate, we need a control or a default policy at writer level. Let's see if default policy is enough.

OlivierGrenoble commented 4 years ago

This is what the ignoreRead was for

On my side it doesn't work. I have set it to true and I still get the native processing of NDEF when I call the write function.

OlivierGrenoble commented 4 years ago

With the previous WebNFC API I didn't have this problem so I guess that, at that time, ignoreRead was working

OlivierGrenoble commented 4 years ago

To be sure I understand this correctly, you're saying that Android NFC service reads content of NDEF tags and starts an "NFC tag read" activity after website has successfully written something to the NFC tag.

Yes. I start with a Tag containing no NDEF. I use the write function to write an URI. The then() function is called indicating that the write was successful. At the same time, the NFC service processes the URI NDEF and proposes to open Chrome. If I accept, Chrome is opened on the URI that I have written.

A side note: Chrome could be opened directly (it would be the case for most users) but, on my phone, I have several applications able to process this NDEF so Android asks which one to launch.

leonhsl commented 4 years ago

I don't think this relates to ignoreRead, which is only for ignoring active WebNFC scan() sessions when writing and does not (cannot) affect how Android handles the tag.

leonhsl commented 4 years ago

Did this work for you? I mean can it forbid Android from reading the tag? @OlivierGrenoble

I have been told to run scan() and discard the onreading events.

OlivierGrenoble commented 4 years ago

I don't think this relates to ignoreRead, which is only for ignoring active WebNFC scan() sessions when writing and does not (cannot) affect how Android handles the tag.

Thanks for this clarification, that 's good to know.

Did this work for you? I mean can it forbid Android from reading the tag? @OlivierGrenoble

I have been told to run scan() and discard the onreading events.

No, for the moment I haven't been able to get it work. I have started a scan() before calling the write function but the difficult point is to know when to abort this scan. I have done it in the then() function of writer.write() but I still get the native processing. Based on what you said, I'm now thinking that I should set ignoreRead to false to be sure that my callbacks "catched" the read notification and discard it.

Would you have an example of code writing an NDEF without triggering the native processing of this NDEF?

My 2 cents: I have the feeling that we are not going in the right direction here. It is too cumbersome.

beaufortfrancois commented 4 years ago

Saying it again in case it was not clear, I feel like this is the UA responsibility to block/catch "discovered NFC tags events" and dispatch them appropriately.

beaufortfrancois commented 4 years ago

@OlivierGrenoble How would like it work?

leonhsl commented 4 years ago

Yes, starting a scan beforehand is not helpful for the scenario, no matter ignoreRead is true or false.

And, by my current understanding, seems Browser has no way to block the native processing of NDEF? I mean, Browser is just one of the clients of Android NFC service, it can get the "discovered NFC tags events" but is not able to block other clients getting them.

beaufortfrancois commented 4 years ago

And, by my current understanding, seems Browser has no way to block the native processing of NDEF? I mean, Browser is just one of the clients of Android NFC service, it can get the "discovered NFC tags events" but is not able to block other clients getting them.

When reading NDEF tags with Web NFC, the native "Android NFC tag" activity is not launched. This is the root issue from what I understand. Chrome should catch it and dispatch it to the website using Web NFC scan. If not, it should "hold" it.

@OlivierGrenoble Can you share/record a screencast of what's currently happening so that everyone get a good understanding?

OlivierGrenoble commented 4 years ago

@OlivierGrenoble How would like it work?

I need some time to think about it. Here are some elements:

In my application, I didn't want to have a scan() listening for NFC events all the time because this would take the hand over the native processing of an NFC event. So I'm calling scan() only when the user has clicked on a button to read the tag content.

We can maybe find inspiration from what is done in Android with enableforegrounddispatch() function: https://developer.android.com/reference/android/nfc/NfcAdapter#enableForegroundDispatch(android.app.Activity,%20android.app.PendingIntent,%20android.content.IntentFilter%5B%5D,%20java.lang.String%5B%5D%5B%5D)

The WebPage would subscribe to get the NFC notifications but only when it is in the foreground. If the user changes Tab in Chrome or goes to another application, the WebPage would stop catching the NFC events otherwise it would prevent other applications from getting them.

OlivierGrenoble commented 4 years ago

@OlivierGrenoble Can you share/record a screencast of what's currently happening so that everyone get a good understanding?

Here is a screencast (in a zip file). I have clicked on "Write NFC Tag", then on "Write URI", then I have taped the NFC Tag. The URI is written successfully. As you can see, I immediately gets a notification to open the URI in Chrome.

screencast.zip

OlivierGrenoble commented 4 years ago

The WebPage would subscribe to get the NFC notifications but only when it is in the foreground. If the user changes Tab in Chrome or goes to another application, the WebPage would stop catching the NFC events otherwise it would prevent other applications from getting them.

What I have told here was too simple. I don't think that the web page should receive every NFC events while it is in foreground. I think that it should listen to NFC events only when it is expecting something.

zolkis commented 4 years ago

Here is a screencast (in a zip file). I have clicked on "Write NFC Tag", then on "Write URI", then I have taped the NFC Tag. The URI is written successfully. As you can see, I immediately gets a notification to open the URI in Chrome.

Based on the video I would say things work as they are designed to work (*) and also according to your preference to not interfere with other apps on reading.

(*) But IIUC you would prefer that Web NFC implementation would create and "own" a sort of "tag writing session" in a way that re-reading that tag would get dispatched to Web NFC and discarded there?

Of course the app could do that by:

zolkis commented 4 years ago

However, others may come and say "I specifically want to test written tags by allowing default dispatching of NFC content."

So Web NFC could include another write option to control that policy. The default behaviour could be discussed further.

Now IIUC @kenchris, the intent with ignoreRead was actually that behaviour (in which case we need to update the write algorithm and don't need another write option).

OlivierGrenoble commented 4 years ago

Here is a screencast (in a zip file). I have clicked on "Write NFC Tag", then on "Write URI", then I have taped the NFC Tag. The URI is written successfully. As you can see, I immediately gets a notification to open the URI in Chrome.

Based on the video I would say things work as they are designed to work (*) and also according to your preference to not interfere with other apps on reading.

I confirm. I have started a scan, then moved to another Chrome Tab and then taped a tag. The native processing is executed so that's fine.

(*) But IIUC you would prefer that Web NFC implementation would create and "own" a sort of "tag writing session" in a way that re-reading that tag would get dispatched to Web NFC and discarded there?

Yes and I was proposing to make it the default behavior.

Of course the app could do that by:

  • setting up a scan
  • write with ignoreRead=true
  • ignoring the read event. But I understood that was not your preference, right?

This is not easy to do. For the moment I haven't succeed to get a version working reliably. Here is my code. I had to use ignoreRead=false in order to be sure that the NFC event get hooked by my application and doesn't get processed natively.

function writeUriNdef(uriTxt) {

  console.log("writeUriNdef: " + uriTxt);

  if (!('NDEFWriter' in window)) {
    showWebNfcWarning();
    return;
  }

  if (!window.isSecureContext) {
    showSecureContextWarning();
    return;
  }

  var message = {
    records : [
      {
        recordType: "url",
        data: uriTxt
      }
    ]
  };

    console.log("Please tap the NFC tag to write...");
  showDialog("Please tap the NFC tag to write...", "", cancelPushRequest);

  writerAbortController = new AbortController();
  writerAbortController.signal.onabort = event => {
    console.log("All NFC Write operations have been aborted.");
  };  

  readerAbortController = new AbortController();
  readerAbortController.signal.onabort = event => {
    console.log("All NFC Read operations have been aborted.");
  }; 

  const reader = new NDEFReader();  
  const writer = new NDEFWriter();

  reader.scan({ signal: readerAbortController.signal })
    .then(() => {  
    reader.onreading = event => {
      console.log("NFC read event ignored (write in progress)");    
    };
  }).catch(error => {
    console.log("Read failed: " + error.name);
  });

    writer.write(message, { signal: writerAbortController.signal , ignoreRead: false })
  .then(() => {
        console.log("NDEF written successfully.");
    showDialog("NDEF written successfully.");
        readerAbortController.abort();
  })
  .catch((error) => {
        console.log("error: " + error.name);

    switch(error.name) {
      case "NotSupportedError":
        showDialog('Please turn NFC ON');
        break;
      case "AbortError":
        //showDialog('Abort error');
        break;
      default:
        showDialog('Push Failed, please try again: ' + error.name);
        break;
    }
  }); 

}

function cancelPushRequest() {
  console.log("cancelPushRequest");
  writerAbortController.abort();
    readerAbortController.abort();
}

Here is the console: script.js:413 writeUriNdef: http://www.st.com/st25 script.js:434 Please tap the NFC tag to write... script.js:453 NFC read event ignored (write in progress) script.js:461 NDEF written successfully. script.js:484 cancelPushRequest script.js:439 All NFC Write operations have been aborted. script.js:444 All NFC Read operations have been aborted.

"NFC read event ignored" has been called, which is fine but I still get the native processing.

Now IIUC @kenchris, the intent with ignoreRead was actually that behaviour (in which case we need to update the write algorithm and don't need another write option).

2 years ago, I was using the previous WebNFC API and I didn't have this problem. I was using ignoreRead=true and I think that it was discarding the read event.

zolkis commented 4 years ago

@leonhsl, @kenchris , @beaufortfrancois: any objections against changing the write algorithm to use ignoreRead as described above?

kenchris commented 4 years ago

Can you summarize?

zolkis commented 4 years ago

See https://github.com/w3c/web-nfc/issues/551#issuecomment-601181157 and https://github.com/w3c/web-nfc/issues/551#issuecomment-601184070

kenchris commented 4 years ago

I dont really follow these comments.

ignoreRead was added because if you want to write to a tag, that tag might have existing content, and you don't want a read event dispatches for that during the write (as data is already read when the nfc tag is powered up)

leonhsl commented 4 years ago

Yep I have the same understanding on ignoreRead with the above comment.

OlivierGrenoble commented 4 years ago

ignoreRead was added because if you want to write to a tag, that tag might have existing content, and you don't want a read event dispatches for that during the write (as data is already read when the nfc tag is powered up)

Yep I have the same understanding on ignoreRead with the above comment.

Ok. What is your suggestion about the "write" issue? (When writting an NDEF, it is immediately processed by the NFC service).

kenchris commented 4 years ago

What you are saying is that what you just wrote (not previous content) gets dispatched right after it was written?

That doesn't seem that useful to me. Is this always the case?

So this didn't happen before? I wonder what changed this. Maybe we disconnect from the tag just after and that is why

OlivierGrenoble commented 4 years ago

What you are saying is that what you just wrote (not previous content) gets dispatched right after it was written?

Yes. I'm sure of that because I have done the test with an "empty" tag.

That doesn't seem that useful to me. Is this always the case?

Yes

So this didn't happen before? I wonder what changed this. Maybe we disconnect from the tag just after and that is why

I didn't have this issue with the previous API (the one with push())

zolkis commented 4 years ago

ignoreRead was added because if you want to write to a tag, that tag might have existing content, and you don't want a read event dispatches for that during the write (as data is already read when the nfc tag is powered up)

What you are saying is that what you just wrote (not previous content) gets dispatched right after it was written?

If ignoreRead controls the first case (suppress reading of existing content before writing), what should control suppressing reading after writing?

  1. ignoreRead controls both
  2. ignoreRead controls the first (as now) and by default we suppress reads right after writes (impl needs to catch and ignore the read intent)
  3. we introduce another control option for the second
  4. we don't do anything, meaning we'll let the read-after-write dispatched through the system.

Opinions?

kenchris commented 4 years ago

A read after should only happen if you lost contact with the tag and regained it. Maybe that happens because we call "close()" on the connection and so maybe we should not before the tag is out of reach..

DonnaWuDongxia commented 4 years ago

@OlivierGrenoble thank you for giving the real usage feedback for the WebNFC API. We'd like to hear sound like this. I did some tests based on your info and understand your request. You want to write the tag only, without a following Activity Chooser showing. Actually, I saw this notice before on the old version API occasionally and from the code perspection, WebNFC implementation in Chromium can do nothing to prevent this because it is an OS action. See these: https://developer.android.com/guide/topics/connectivity/nfc/nfc#filtering-intents https://android.stackexchange.com/questions/96564/tapping-an-nfc-tag-which-contains-an-url-how-can-i-override-the-default-actio

Android system will try to start a proper Activity based on the content in a discoverred tag. If there is no proper Activity, it will do nothing, such as writing a text will not open a browser.

WebNFC API is based on Android NFC library, and just being noticed when tag discovered. That means WebNFC is just one of the listeners to the tag events, it can't interfere the default process, can't stop other Application to read the tag after writting. I investigated the lower level interfaces to see if there is some way to stop the event propgation, just like "event.stopPropagation()" in JS. But unfortunately, I haven't found it. Maybe we can suggest Android NFC library to support such usage.

@riju @reillyeon

reillyeon commented 4 years ago

@kenchris's suggestion sounds promising. It it possible that having an active read on the Java side right after the tag is written would suppress the OS default?

leonhsl commented 4 years ago

Right, now WebNFC impl closes the connection to the tag right after we completed all pending read/write operations on this tag.

Maybe we can just leave the connection there until the tag on its own gets out of range, thus WebNFC can hold this tag exclusively to prevent others from accessing it?

OlivierGrenoble commented 4 years ago

A read after should only happen if you lost contact with the tag and regained it. Maybe that happens because we call "close()" on the connection and so maybe we should not before the tag is out of reach..

I think that it is indeed interesting to investigate when to close the connection. There are 2 ways to use NFC tags:

When using the second solution, it will be annoying if the native processing is executed when non wanted. The application should be able to control that. I mean, it should be able to let the NFC service handle the NFC Event or it should be able to handle it itself.

In Android application this is controlled with enableforegrounddispatch(). When the application is in foreground, it can call this function to hook the NFC events. By this way, it will be notified when a tag is tapped and the native processing will not be executed. Calling this function is optional so the application has the ability to choose who should receive the NFC event.

OlivierGrenoble commented 4 years ago

I did some tests based on your info and understand your request. You want to write the tag only, without a following Activity Chooser showing.

Correct. I will try to share my web app today. It is not finished but it can allow every one to play with the write function and see this issue.

OlivierGrenoble commented 4 years ago

Here is my web app: https://www.myst25.com/ST25NFCWebApp/ You can use it to see the problem when writing an NDEF.

NB: This is still under development.

OlivierGrenoble commented 4 years ago

Hello, How can we progress for the Write issue?

beaufortfrancois commented 4 years ago

Maybe we can just leave the connection there until the tag on its own gets out of range, thus WebNFC can hold this tag exclusively to prevent others from accessing it?

Is that something you could prototype so that we can try it out in Chromium?

DonnaWuDongxia commented 4 years ago

Hi @OlivierGrenoble, I made a change to fix this. https://chromium-review.googlesource.com/c/chromium/src/+/2126470

I tested it on my side and it works. If you need, I can provide you an APK.

beaufortfrancois commented 4 years ago

Thanks @DonnaWuDongxia for proposing this chromium CL. It seems like your implementation will leave "Android NFC Reader Mode" enabled after at least one successful write/scan Web NFC operation has been called. But shouldn't we at least disable it when tag goes out of range?

OlivierGrenoble commented 4 years ago

Thank you @DonnaWuDongxia for working on this.

It seems like your implementation will leave "Android NFC Reader Mode" enabled after at least one successful write/scan Web NFC operation has been called.

The question is indeed to know when to release the hook of NFC events. If you release it while the smartphone is still on the tag, the NFC service will process the NFC event (and we are back to the first problem).

But shouldn't we at least disable it when tag goes out of range?

I doubt that you can be notified when the tag gets out of range.

Some thoughts: We are trying here to find out when to release the NFC hook. What do you think of choosing the same strategy as the one chosen for Android API? I mean: Exposing enableforegrounddispatch() and disableforegrounddispatch() in webNFC API. By this way the application has the full control to decide when it wants to receive the NFC events or when it want to let the native execution run.

DonnaWuDongxia commented 4 years ago

@OlivierGrenoble We use another way to read/write tags with NfcAdapter#enableReaderMode/disableReaderMode, not the Intent filter way. Please refer this and the Android Doc.

https://stackoverflow.com/questions/33633736/whats-the-difference-between-enablereadermode-and-enableforegrounddispatch

I think this is a more proper way for a runtime, and you can filter tags with WebNFC API.

OlivierGrenoble commented 4 years ago

@DonnaWuDongxia Ok thanks for this important précision. Back to the question from François, when shall disableReaderMode() be called? If the phone is still on the tag, the NFC service will process the NDEF message.

DonnaWuDongxia commented 4 years ago

@beaufortfrancois @OlivierGrenoble Disable the ReadMode when the tag is out of range is a good thinking, I'm tring on it.

DonnaWuDongxia commented 4 years ago

@beaufortfrancois We've got a listener(android.nfc.NfcAdapter.OnTagRemovedListener) to use and it works but with a high level API(Level 24), we are now at min-level 19. Do we need to sacrifice the compliance? I prefer not.

@OlivierGrenoble It seems the site https://www.myst25.com/ST25NFCWebApp/ has some issue can't trigger right NFC request, so the system launcher will still show. I use the following code piece.

const writer = new NDEFWriter();
writer.write({
  records: [{ recordType: "url", data: "https://w3c.github.io/web-nfc/" }]
}).then(() => {
  console.log("Message written.");
}).catch(_ => {
  console.log("Write failed :-( try again.");
});
OlivierGrenoble commented 4 years ago

@beaufortfrancois We've got a listener(android.nfc.NfcAdapter.OnTagRemovedListener) to use and it works but with a high level API(Level 24), we are now at min-level 19. Do we need to sacrifice the compliance? I prefer not.

Difficult choice. 58% of the phones are currently using a version >= to API Level 24.

@OlivierGrenoble It seems the site https://www.myst25.com/ST25NFCWebApp/ has some issue can't trigger right NFC request, so the system launcher will still show. I use the following code piece.

const writer = new NDEFWriter();
writer.write({
  records: [{ recordType: "url", data: "https://w3c.github.io/web-nfc/" }]
}).then(() => {
  console.log("Message written.");
}).catch(_ => {
  console.log("Write failed :-( try again.");
});

What kind of issue do you see with https://www.myst25.com/ST25NFCWebApp/ ? Do you see this issue only with the code that you have updated?

kenchris commented 4 years ago

Did you have a look at how it is implemented (ontagremoved): https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/nfc/NfcAdapter.java

Maybe we can polyfill it somehow

reillyeon commented 4 years ago

Unfortunately it looks like OnTagRemovedListener is only used for the ignore() method (also from API 24) which will cause the tag to be entirely ignored until it is removed. This is behavior I imagine we would want to have a site opt-in to because otherwise it would prevent multiple pushes to the card.

I am okay with there being functionality which is unavailable on older versions of a platform.

DonnaWuDongxia commented 4 years ago

Yes,OnTagRemovedListener is only used for theignore(), I misunderstood it before. I don't think a site need to be in a state of ignoring some tag that will prevent multiple operations as @reillyeon mentioned. It seems that Android have no passive notifition for a tag removing. Actively, we can check a tag's state by catching IOException in polling way, that is not suitable for our case.