Chromium Code Reviews| 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 b74902e021ba4aaa8bb2c419cbb86338cb7ee859..ba2aef35f8f22d4a6831b4d0c18637cc45a05e9f 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.device.nfc.mojom.Nfc; |
| @@ -26,11 +28,15 @@ import org.chromium.device.nfc.mojom.NfcErrorType; |
| import org.chromium.device.nfc.mojom.NfcMessage; |
| import org.chromium.device.nfc.mojom.NfcPushOptions; |
| import org.chromium.device.nfc.mojom.NfcPushTarget; |
| +import org.chromium.device.nfc.mojom.NfcWatchMode; |
| import org.chromium.device.nfc.mojom.NfcWatchOptions; |
| import org.chromium.mojo.bindings.Callbacks; |
| import org.chromium.mojo.system.MojoException; |
| 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 |
| + * proximity, 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()); |
| @@ -105,7 +130,7 @@ public class NfcImpl implements Nfc { |
| protected void setActivity(Activity activity) { |
| disableReaderMode(); |
| mActivity = activity; |
| - enableReaderMode(); |
| + enableReaderModeIfNeeded(); |
| } |
| /** |
| @@ -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; |
|
dcheng
2016/11/28 19:46:40
Can we file a bug to get rid of this? Ideally, we
shalamov
2016/11/29 13:07:22
Thank you for looking Daniel, unfortunately, I'm n
|
| } |
| /** |
| @@ -132,6 +157,11 @@ public class NfcImpl implements Nfc { |
| public void push(NfcMessage message, NfcPushOptions options, PushResponse callback) { |
| if (!checkIfReady(callback)) return; |
| + if (!NfcMessageValidator.isValid(message)) { |
| + callback.call(createError(NfcErrorType.INVALID_MESSAGE)); |
| + return; |
| + } |
| + |
| if (options.target == NfcPushTarget.PEER) { |
| callback.call(createError(NfcErrorType.NOT_SUPPORTED)); |
| return; |
| @@ -143,7 +173,7 @@ public class NfcImpl implements Nfc { |
| } |
| mPendingPushOperation = new PendingPushOperation(message, options, callback); |
| - enableReaderMode(); |
| + enableReaderModeIfNeeded(); |
| processPendingPushOperation(); |
| } |
| @@ -169,7 +199,7 @@ public class NfcImpl implements Nfc { |
| mPendingPushOperation.complete(createError(NfcErrorType.OPERATION_CANCELLED)); |
| mPendingPushOperation = null; |
| callback.call(null); |
| - disableReaderMode(); |
| + disableReaderModeIfNeeded(); |
| } |
| } |
| @@ -186,8 +216,11 @@ 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)); |
| + int watcherId = ++mWatcherId; |
| + mWatchers.put(watcherId, options); |
| + callback.call(watcherId, null); |
| + enableReaderModeIfNeeded(); |
| + processPendingWatchOperations(); |
| } |
| /** |
| @@ -199,8 +232,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) { |
| + callback.call(createError(NfcErrorType.NOT_FOUND)); |
| + } else { |
| + mWatchers.remove(id); |
| + callback.call(null); |
| + disableReaderModeIfNeeded(); |
| + } |
| } |
| /** |
| @@ -211,8 +250,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) { |
| + callback.call(createError(NfcErrorType.NOT_FOUND)); |
| + } else { |
| + mWatchers.clear(); |
| + callback.call(null); |
| + disableReaderModeIfNeeded(); |
| + } |
| } |
| /** |
| @@ -228,7 +273,7 @@ public class NfcImpl implements Nfc { |
| */ |
| @Override |
| public void resumeNfcOperations() { |
| - enableReaderMode(); |
| + enableReaderModeIfNeeded(); |
| } |
| @Override |
| @@ -343,13 +388,13 @@ public class NfcImpl implements Nfc { |
| * Enables reader mode, allowing NFC device to read / write NFC tags. |
| * @see android.nfc.NfcAdapter#enableReaderMode |
| */ |
| - private void enableReaderMode() { |
| + private void enableReaderModeIfNeeded() { |
| if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; |
| if (mReaderCallbackHandler != null || mActivity == null || mNfcAdapter == 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; |
| mReaderCallbackHandler = new ReaderCallbackHandler(this); |
| mNfcAdapter.enableReaderMode(mActivity, mReaderCallbackHandler, |
| @@ -377,16 +422,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) { |
| + 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; |
| @@ -408,7 +460,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)); |
| @@ -422,10 +473,130 @@ public class NfcImpl implements Nfc { |
| } |
| /** |
| + * Reads NfcMessage from a tag and forwards message to matching method. |
| + */ |
| + 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_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 == null || message.url.isEmpty())) { |
| + return false; |
| + } |
| + |
| + // Filter by NfcMessage.url |
| + if (options.url != null && !options.url.isEmpty() && !options.url.equals(message.url)) { |
| + return false; |
| + } |
| + |
| + // Matches any record / media type. |
| + if ((options.mediaType == null || 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 == null || 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); |
| + processPendingOperations(NfcTagHandler.create(tag)); |
| + } |
| + |
| + /** |
| + * Processes pending operation when NFC tag is in proximity. |
| + */ |
| + protected void processPendingOperations(NfcTagHandler tagHandler) { |
| + mTagHandler = tagHandler; |
| + processPendingWatchOperations(); |
| processPendingPushOperation(); |
| + if (mTagHandler != null && mTagHandler.isConnected()) { |
| + try { |
| + mTagHandler.close(); |
| + } catch (IOException e) { |
| + Log.w(TAG, "Cannot close NFC tag connection."); |
| + } |
| + } |
| } |
| } |