Index: device/nfc/android/java/src/org/chromium/device/nfc/NfcImpl.java |
diff --git a/device/nfc/android/java/src/org/chromium/device/nfc/NfcImpl.java b/device/nfc/android/java/src/org/chromium/device/nfc/NfcImpl.java |
index 35a73205968cabea4f543f80833fec2eaa5fdd42..acc3fe4bdff38b63c2e81217a5c6c65a970bc107 100644 |
--- a/device/nfc/android/java/src/org/chromium/device/nfc/NfcImpl.java |
+++ b/device/nfc/android/java/src/org/chromium/device/nfc/NfcImpl.java |
@@ -10,6 +10,7 @@ import android.app.Activity; |
import android.content.Context; |
import android.content.pm.PackageManager; |
import android.nfc.FormatException; |
+import android.nfc.NdefMessage; |
import android.nfc.NfcAdapter; |
import android.nfc.NfcAdapter.ReaderCallback; |
import android.nfc.NfcManager; |
@@ -17,6 +18,7 @@ import android.nfc.Tag; |
import android.nfc.TagLostException; |
import android.os.Build; |
import android.os.Process; |
+import android.util.SparseArray; |
import org.chromium.base.Log; |
import org.chromium.mojo.bindings.Callbacks; |
@@ -28,9 +30,13 @@ import org.chromium.mojom.device.nfc.NfcErrorType; |
import org.chromium.mojom.device.nfc.NfcMessage; |
import org.chromium.mojom.device.nfc.NfcPushOptions; |
import org.chromium.mojom.device.nfc.NfcPushTarget; |
+import org.chromium.mojom.device.nfc.NfcWatchMode; |
import org.chromium.mojom.device.nfc.NfcWatchOptions; |
import java.io.IOException; |
+import java.io.UnsupportedEncodingException; |
+import java.util.ArrayList; |
+import java.util.List; |
/** |
* Android implementation of the NFC mojo service defined in |
@@ -78,6 +84,25 @@ public class NfcImpl implements Nfc { |
*/ |
private NfcTagHandler mTagHandler; |
+ /** |
+ * Client interface used to deliver NFCMessages for registered watch operations. |
+ * @see #watch |
+ */ |
+ private NfcClient mClient; |
+ |
+ /** |
+ * Watcher id that is incremented for each #watch call. |
+ */ |
+ private int mWatcherId; |
+ |
+ /** |
+ * Map of watchId <-> NfcWatchOptions. All NfcWatchOptions are matched against tag that is in |
+ * proximnity, when match algorithm (@see #matchesWatchOptions) returns true, watcher with |
+ * corresponding ID would be notified using NfcClient interface. |
+ * @see NfcClient#onWatch(int[] id, NfcMessage message) |
+ */ |
+ private final SparseArray<NfcWatchOptions> mWatchers = new SparseArray<>(); |
+ |
public NfcImpl(Context context) { |
int permission = |
context.checkPermission(Manifest.permission.NFC, Process.myPid(), Process.myUid()); |
@@ -117,7 +142,7 @@ public class NfcImpl implements Nfc { |
*/ |
@Override |
public void setClient(NfcClient client) { |
- // TODO(crbug.com/625589): Should be implemented when watch() is implemented. |
+ mClient = client; |
} |
/** |
@@ -169,7 +194,7 @@ public class NfcImpl implements Nfc { |
mPendingPushOperation.complete(createError(NfcErrorType.OPERATION_CANCELLED)); |
mPendingPushOperation = null; |
callback.call(null); |
- disableReaderMode(); |
+ disableReaderModeIfNeeded(); |
} |
} |
@@ -186,8 +211,10 @@ public class NfcImpl implements Nfc { |
@Override |
public void watch(NfcWatchOptions options, WatchResponse callback) { |
if (!checkIfReady(callback)) return; |
- // TODO(crbug.com/625589): Not implemented. |
- callback.call(0, createError(NfcErrorType.NOT_SUPPORTED)); |
+ mWatchers.put(++mWatcherId, options); |
+ callback.call(mWatcherId, null); |
+ enableReaderMode(); |
+ processPendingWatchOperations(); |
} |
/** |
@@ -199,8 +226,14 @@ public class NfcImpl implements Nfc { |
@Override |
public void cancelWatch(int id, CancelWatchResponse callback) { |
if (!checkIfReady(callback)) return; |
- // TODO(crbug.com/625589): Not implemented. |
- callback.call(createError(NfcErrorType.NOT_SUPPORTED)); |
+ |
+ if (mWatchers.indexOfKey(id) >= 0) { |
dcheng
2016/09/08 05:02:32
Nit: it's more common to structure code as:
if (e
shalamov
2016/09/22 13:38:40
Done.
|
+ mWatchers.remove(id); |
+ callback.call(null); |
+ disableReaderModeIfNeeded(); |
+ } else { |
+ callback.call(createError(NfcErrorType.NOT_FOUND)); |
+ } |
} |
/** |
@@ -211,8 +244,14 @@ public class NfcImpl implements Nfc { |
@Override |
public void cancelAllWatches(CancelAllWatchesResponse callback) { |
if (!checkIfReady(callback)) return; |
- // TODO(crbug.com/625589): Not implemented. |
- callback.call(createError(NfcErrorType.NOT_SUPPORTED)); |
+ |
+ if (mWatchers.size() > 0) { |
+ mWatchers.clear(); |
+ callback.call(null); |
+ disableReaderModeIfNeeded(); |
+ } else { |
+ callback.call(createError(NfcErrorType.NOT_FOUND)); |
dcheng
2016/09/08 05:02:31
Is it really an error to call this if the are no a
shalamov
2016/09/22 13:38:40
Done.
|
+ } |
} |
/** |
@@ -348,8 +387,8 @@ public class NfcImpl implements Nfc { |
if (mReaderCallbackHandler != null || mActivity == null) return; |
- // TODO(crbug.com/625589): Check if there are active watch operations. |
- if (mPendingPushOperation == null) return; |
+ // Do not enable reader mode, if there are no active push / watch operations. |
+ if (mPendingPushOperation == null && mWatchers.size() == 0) return; |
dcheng
2016/09/08 05:02:32
Nit: isEmpty()
shalamov
2016/09/22 13:38:40
SparseArray doesn't have isEmpty(), only size().
|
mReaderCallbackHandler = new ReaderCallbackHandler(this); |
mNfcAdapter.enableReaderMode(mActivity, mReaderCallbackHandler, |
@@ -372,16 +411,23 @@ public class NfcImpl implements Nfc { |
} |
/** |
+ * Checks if there are pending push / watch operations and disables readre mode |
+ * whenever necessary. |
+ */ |
+ private void disableReaderModeIfNeeded() { |
+ if (mPendingPushOperation == null && mWatchers.size() == 0) { |
dcheng
2016/09/08 05:02:32
Why don't we just always do these checks enabling
shalamov
2016/09/22 13:38:40
When mojo proxy is closed, tab becomes inactive or
|
+ disableReaderMode(); |
+ } |
+ } |
+ |
+ /** |
* Completes pending push operation. On error, invalidates #mTagHandler. |
*/ |
private void pendingPushOperationCompleted(NfcError error) { |
if (mPendingPushOperation != null) { |
mPendingPushOperation.complete(error); |
mPendingPushOperation = null; |
- |
- // TODO(crbug.com/625589): When nfc.watch is implemented, disable reader mode |
- // only when there are no active watch operations. |
- disableReaderMode(); |
+ disableReaderModeIfNeeded(); |
} |
if (error != null) mTagHandler = null; |
@@ -403,7 +449,6 @@ public class NfcImpl implements Nfc { |
mTagHandler.connect(); |
mTagHandler.write(NfcTypeConverter.toNdefMessage(mPendingPushOperation.nfcMessage)); |
pendingPushOperationCompleted(null); |
- mTagHandler.close(); |
} catch (InvalidNfcMessageException e) { |
Log.w(TAG, "Cannot write data to NFC tag. Invalid NfcMessage."); |
pendingPushOperationCompleted(createError(NfcErrorType.INVALID_MESSAGE)); |
@@ -417,10 +462,115 @@ public class NfcImpl implements Nfc { |
} |
/** |
+ * Reads NfcMessage from a tag and forwards message to mathcing method. |
dcheng
2016/09/08 05:02:31
Nit: mathcing => matching
shalamov
2016/09/22 13:38:40
Done.
|
+ */ |
+ private void processPendingWatchOperations() { |
+ if (mTagHandler == null || mClient == null || mWatchers.size() == 0) return; |
+ |
+ // Skip reading if there is a pending push operation and ignoreRead flag is set. |
+ if (mPendingPushOperation != null && mPendingPushOperation.nfcPushOptions.ignoreRead) { |
+ return; |
+ } |
+ |
+ if (mTagHandler.isTagOutOfRange()) { |
+ mTagHandler = null; |
+ return; |
+ } |
+ |
+ NdefMessage message = null; |
+ |
+ try { |
+ mTagHandler.connect(); |
+ message = mTagHandler.read(); |
+ if (message.getByteArrayLength() > NfcMessage.MAX_NFC_MESSAGE_SIZE) { |
+ Log.w(TAG, "Cannot read data from NFC tag. NfcMessage exceeds allowed size."); |
+ return; |
+ } |
+ } catch (TagLostException e) { |
+ Log.w(TAG, "Cannot read data from NFC tag. Tag is lost."); |
+ } catch (FormatException | IOException e) { |
+ Log.w(TAG, "Cannot read data from NFC tag. IO_ERROR."); |
+ } |
+ |
+ if (message != null) notifyMatchingWatchers(message); |
+ } |
+ |
+ /** |
+ * Iterates through active watchers and if any of those match NfcWatchOptions criteria, |
+ * delivers NfcMessage to the client. |
+ */ |
+ private void notifyMatchingWatchers(NdefMessage message) { |
+ try { |
+ NfcMessage nfcMessage = NfcTypeConverter.toNfcMessage(message); |
+ List<Integer> watchIds = new ArrayList<Integer>(); |
+ for (int i = 0; i < mWatchers.size(); i++) { |
+ NfcWatchOptions options = mWatchers.valueAt(i); |
+ if (matchesWatchOptions(nfcMessage, options)) watchIds.add(mWatchers.keyAt(i)); |
+ } |
+ |
+ if (watchIds.size() != 0) { |
+ int[] ids = new int[watchIds.size()]; |
+ for (int i = 0; i < watchIds.size(); ++i) { |
+ ids[i] = watchIds.get(i).intValue(); |
+ } |
+ mClient.onWatch(ids, nfcMessage); |
+ } |
+ } catch (UnsupportedEncodingException e) { |
+ Log.w(TAG, "Cannot convert NdefMessage to NfcMessage."); |
+ } |
+ } |
+ |
+ /** |
+ * Implements matching algorithm. |
+ */ |
+ private boolean matchesWatchOptions(NfcMessage message, NfcWatchOptions options) { |
+ // Valid WebNFC message must have non-empty url. |
+ if (options.mode == NfcWatchMode.WEBNFC_ONLY && message.url.isEmpty()) return false; |
dcheng
2016/09/08 05:02:32
I would expect validation like this to occur as so
shalamov
2016/09/22 13:38:40
This is not an error. From the blink side, develop
|
+ |
+ // Filter by NfcMessage.url |
+ if (!options.url.isEmpty() && !options.url.equals(message.url)) return false; |
+ |
+ // Matches any record / media type. |
+ if (options.mediaType.isEmpty() && options.recordFilter == null) return true; |
+ |
+ // Filter by mediaType and recordType |
+ for (int i = 0; i < message.data.length; i++) { |
+ boolean matchedMediaType; |
+ boolean matchedRecordType; |
+ |
+ if (options.mediaType.isEmpty()) { |
+ // If media type for the watch options is empty, match all media types. |
+ matchedMediaType = true; |
+ } else { |
+ matchedMediaType = options.mediaType.equals(message.data[i].mediaType); |
+ } |
+ |
+ if (options.recordFilter == null) { |
+ // If record type filter for the watch options is null, match all record types. |
+ matchedRecordType = true; |
+ } else { |
+ matchedRecordType = options.recordFilter.recordType == message.data[i].recordType; |
+ } |
+ |
+ if (matchedMediaType && matchedRecordType) return true; |
+ } |
+ |
+ return false; |
+ } |
+ |
+ /** |
* Called by ReaderCallbackHandler when NFC tag is in proximity. |
*/ |
public void onTagDiscovered(Tag tag) { |
mTagHandler = NfcTagHandler.create(tag); |
+ processPendingWatchOperations(); |
processPendingPushOperation(); |
+ if (mTagHandler != null && mTagHandler.isConnected()) { |
+ try { |
+ mTagHandler.close(); |
+ } catch (IOException e) { |
+ Log.w(TAG, "Cannot close NFC tag connection."); |
+ } |
+ } |
} |
} |