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 |
new file mode 100644 |
index 0000000000000000000000000000000000000000..35a73205968cabea4f543f80833fec2eaa5fdd42 |
--- /dev/null |
+++ b/device/nfc/android/java/src/org/chromium/device/nfc/NfcImpl.java |
@@ -0,0 +1,426 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.device.nfc; |
+ |
+import android.Manifest; |
+import android.annotation.TargetApi; |
+import android.app.Activity; |
+import android.content.Context; |
+import android.content.pm.PackageManager; |
+import android.nfc.FormatException; |
+import android.nfc.NfcAdapter; |
+import android.nfc.NfcAdapter.ReaderCallback; |
+import android.nfc.NfcManager; |
+import android.nfc.Tag; |
+import android.nfc.TagLostException; |
+import android.os.Build; |
+import android.os.Process; |
+ |
+import org.chromium.base.Log; |
+import org.chromium.mojo.bindings.Callbacks; |
+import org.chromium.mojo.system.MojoException; |
+import org.chromium.mojom.device.nfc.Nfc; |
+import org.chromium.mojom.device.nfc.NfcClient; |
+import org.chromium.mojom.device.nfc.NfcError; |
+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.NfcWatchOptions; |
+ |
+import java.io.IOException; |
+ |
+/** |
+ * Android implementation of the NFC mojo service defined in |
+ * device/nfc/nfc.mojom. |
+ */ |
+public class NfcImpl implements Nfc { |
+ private static final String TAG = "NfcImpl"; |
+ |
+ /** |
+ * Used to get instance of NFC adapter, @see android.nfc.NfcManager |
+ */ |
+ private final NfcManager mNfcManager; |
+ |
+ /** |
+ * NFC adapter. @see android.nfc.NfcAdapter |
+ */ |
+ private final NfcAdapter mNfcAdapter; |
+ |
+ /** |
+ * Activity that is in foreground and is used to enable / disable NFC reader mode operations. |
+ * Can be updated when activity associated with web page is changed. @see #setActivity |
+ */ |
+ private Activity mActivity; |
+ |
+ /** |
+ * Flag that indicates whether NFC permission is granted. |
+ */ |
+ private final boolean mHasPermission; |
+ |
+ /** |
+ * Implementation of android.nfc.NfcAdapter.ReaderCallback. @see ReaderCallbackHandler |
+ */ |
+ private ReaderCallbackHandler mReaderCallbackHandler; |
+ |
+ /** |
+ * Object that contains data that was passed to method |
+ * #push(NfcMessage message, NfcPushOptions options, PushResponse callback) |
+ * @see PendingPushOperation |
+ */ |
+ private PendingPushOperation mPendingPushOperation; |
+ |
+ /** |
+ * Utility that provides I/O operations for a Tag. Created on demand when Tag is found. |
+ * @see NfcTagHandler |
+ */ |
+ private NfcTagHandler mTagHandler; |
+ |
+ public NfcImpl(Context context) { |
+ int permission = |
+ context.checkPermission(Manifest.permission.NFC, Process.myPid(), Process.myUid()); |
+ mHasPermission = permission == PackageManager.PERMISSION_GRANTED; |
+ |
+ if (!mHasPermission || Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { |
+ Log.w(TAG, "NFC operations are not permitted."); |
+ mNfcAdapter = null; |
+ mNfcManager = null; |
+ } else { |
+ mNfcManager = (NfcManager) context.getSystemService(Context.NFC_SERVICE); |
+ if (mNfcManager == null) { |
+ Log.w(TAG, "NFC is not supported."); |
+ mNfcAdapter = null; |
+ } else { |
+ mNfcAdapter = mNfcManager.getDefaultAdapter(); |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Sets Activity that is used to enable / disable NFC reader mode. When Activity is set, |
+ * reader mode is disabled for old Activity and enabled for the new Activity. |
+ */ |
+ protected void setActivity(Activity activity) { |
+ disableReaderMode(); |
+ mActivity = activity; |
+ enableReaderMode(); |
+ } |
+ |
+ /** |
+ * Sets NfcClient. NfcClient interface is used to notify mojo NFC service client when NFC |
+ * device is in proximity and has NfcMessage that matches NfcWatchOptions criteria. |
+ * @see Nfc#watch(NfcWatchOptions options, WatchResponse callback) |
+ * |
+ * @param client @see NfcClient |
+ */ |
+ @Override |
+ public void setClient(NfcClient client) { |
+ // TODO(crbug.com/625589): Should be implemented when watch() is implemented. |
+ } |
+ |
+ /** |
+ * Pushes NfcMessage to Tag or Peer, whenever NFC device is in proximity. At the moment, only |
+ * passive NFC devices are supported (NfcPushTarget.TAG). |
+ * |
+ * @param message that should be pushed to NFC device. |
+ * @param options that contain information about timeout and target device type. |
+ * @param callback that is used to notify when push operation is completed. |
+ */ |
+ @Override |
+ public void push(NfcMessage message, NfcPushOptions options, PushResponse callback) { |
+ if (!checkIfReady(callback)) return; |
+ |
+ if (options.target == NfcPushTarget.PEER) { |
+ callback.call(createError(NfcErrorType.NOT_SUPPORTED)); |
+ return; |
+ } |
+ |
+ // If previous pending push operation is not completed, cancel it. |
+ if (mPendingPushOperation != null) { |
+ mPendingPushOperation.complete(createError(NfcErrorType.OPERATION_CANCELLED)); |
+ } |
+ |
+ mPendingPushOperation = new PendingPushOperation(message, options, callback); |
+ enableReaderMode(); |
+ processPendingPushOperation(); |
+ } |
+ |
+ /** |
+ * Cancels pending push operation. |
+ * At the moment, only passive NFC devices are supported (NfcPushTarget.TAG). |
+ * |
+ * @param target @see NfcPushTarget |
+ * @param callback that is used to notify caller when cancelPush() is completed. |
+ */ |
+ @Override |
+ public void cancelPush(int target, CancelPushResponse callback) { |
+ if (!checkIfReady(callback)) return; |
+ |
+ if (target == NfcPushTarget.PEER) { |
+ callback.call(createError(NfcErrorType.NOT_SUPPORTED)); |
+ return; |
+ } |
+ |
+ if (mPendingPushOperation == null) { |
+ callback.call(createError(NfcErrorType.NOT_FOUND)); |
+ } else { |
+ mPendingPushOperation.complete(createError(NfcErrorType.OPERATION_CANCELLED)); |
+ mPendingPushOperation = null; |
+ callback.call(null); |
+ disableReaderMode(); |
+ } |
+ } |
+ |
+ /** |
+ * Watch method allows to set filtering criteria for NfcMessages that are found when NFC device |
+ * is within proximity. On success, watch ID is returned to caller through WatchResponse |
+ * callback. When NfcMessage that matches NfcWatchOptions is found, it is passed to NfcClient |
+ * interface together with corresponding watch ID. |
+ * @see NfcClient#onWatch(int[] id, NfcMessage message) |
+ * |
+ * @param options used to filter NfcMessages, @see NfcWatchOptions. |
+ * @param callback that is used to notify caller when watch() is completed and return watch ID. |
+ */ |
+ @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)); |
+ } |
+ |
+ /** |
+ * Cancels NFC watch operation. |
+ * |
+ * @param id of watch operation. |
+ * @param callback that is used to notify caller when cancelWatch() is completed. |
+ */ |
+ @Override |
+ public void cancelWatch(int id, CancelWatchResponse callback) { |
+ if (!checkIfReady(callback)) return; |
+ // TODO(crbug.com/625589): Not implemented. |
+ callback.call(createError(NfcErrorType.NOT_SUPPORTED)); |
+ } |
+ |
+ /** |
+ * Cancels all NFC watch operations. |
+ * |
+ * @param callback that is used to notify caller when cancelAllWatches() is completed. |
+ */ |
+ @Override |
+ public void cancelAllWatches(CancelAllWatchesResponse callback) { |
+ if (!checkIfReady(callback)) return; |
+ // TODO(crbug.com/625589): Not implemented. |
+ callback.call(createError(NfcErrorType.NOT_SUPPORTED)); |
+ } |
+ |
+ /** |
+ * Suspends all pending operations. Should be called when web page visibility is lost. |
+ */ |
+ @Override |
+ public void suspendNfcOperations() { |
+ disableReaderMode(); |
+ } |
+ |
+ /** |
+ * Resumes all pending watch / push operations. Should be called when web page becomes visible. |
+ */ |
+ @Override |
+ public void resumeNfcOperations() { |
+ enableReaderMode(); |
+ } |
+ |
+ @Override |
+ public void close() { |
+ disableReaderMode(); |
+ } |
+ |
+ @Override |
+ public void onConnectionError(MojoException e) { |
+ close(); |
+ } |
+ |
+ /** |
+ * Holds information about pending push operation. |
+ */ |
+ private static class PendingPushOperation { |
+ public final NfcMessage nfcMessage; |
+ public final NfcPushOptions nfcPushOptions; |
+ private final PushResponse mPushResponseCallback; |
+ |
+ public PendingPushOperation( |
+ NfcMessage message, NfcPushOptions options, PushResponse callback) { |
+ nfcMessage = message; |
+ nfcPushOptions = options; |
+ mPushResponseCallback = callback; |
+ } |
+ |
+ /** |
+ * Completes pending push operation. |
+ * |
+ * @param error should be null when operation is completed successfully, otherwise, |
+ * error object with corresponding NfcErrorType should be provided. |
+ */ |
+ public void complete(NfcError error) { |
+ if (mPushResponseCallback != null) mPushResponseCallback.call(error); |
+ } |
+ } |
+ |
+ /** |
+ * Helper method that creates NfcError object from NfcErrorType. |
+ */ |
+ private NfcError createError(int errorType) { |
+ NfcError error = new NfcError(); |
+ error.errorType = errorType; |
+ return error; |
+ } |
+ |
+ /** |
+ * Checks if NFC funcionality can be used by the mojo service. If permission to use NFC is |
+ * granted and hardware is enabled, returns null. |
+ */ |
+ private NfcError checkIfReady() { |
+ if (!mHasPermission || mActivity == null) { |
+ return createError(NfcErrorType.SECURITY); |
+ } else if (mNfcManager == null || mNfcAdapter == null) { |
+ return createError(NfcErrorType.NOT_SUPPORTED); |
+ } else if (!mNfcAdapter.isEnabled()) { |
+ return createError(NfcErrorType.DEVICE_DISABLED); |
+ } |
+ return null; |
+ } |
+ |
+ /** |
+ * Uses checkIfReady() method and if NFC cannot be used, calls mojo callback with NfcError. |
+ * |
+ * @param WatchResponse Callback that is provided to watch() method. |
+ * @return boolean true if NFC functionality can be used, false otherwise. |
+ */ |
+ private boolean checkIfReady(WatchResponse callback) { |
+ NfcError error = checkIfReady(); |
+ if (error == null) return true; |
+ |
+ callback.call(0, error); |
+ return false; |
+ } |
+ |
+ /** |
+ * Uses checkIfReady() method and if NFC cannot be used, calls mojo callback with NfcError. |
+ * |
+ * @param callback Generic callback that is provided to push(), cancelPush(), |
+ * cancelWatch() and cancelAllWatches() methods. |
+ * @return boolean true if NFC functionality can be used, false otherwise. |
+ */ |
+ private boolean checkIfReady(Callbacks.Callback1<NfcError> callback) { |
+ NfcError error = checkIfReady(); |
+ if (error == null) return true; |
+ |
+ callback.call(error); |
+ return false; |
+ } |
+ |
+ /** |
+ * Implementation of android.nfc.NfcAdapter.ReaderCallback. Callback is called when NFC tag is |
+ * discovered, Tag object is delegated to mojo service implementation method |
+ * NfcImpl.onTagDiscovered(). |
+ */ |
+ @TargetApi(Build.VERSION_CODES.KITKAT) |
+ private static class ReaderCallbackHandler implements ReaderCallback { |
+ private final NfcImpl mNfcImpl; |
+ |
+ public ReaderCallbackHandler(NfcImpl impl) { |
+ mNfcImpl = impl; |
+ } |
+ |
+ @Override |
+ public void onTagDiscovered(Tag tag) { |
+ mNfcImpl.onTagDiscovered(tag); |
+ } |
+ } |
+ |
+ /** |
+ * Enables reader mode, allowing NFC device to read / write NFC tags. |
+ * @see android.nfc.NfcAdapter#enableReaderMode |
+ */ |
+ private void enableReaderMode() { |
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; |
+ |
+ if (mReaderCallbackHandler != null || mActivity == null) return; |
+ |
+ // TODO(crbug.com/625589): Check if there are active watch operations. |
+ if (mPendingPushOperation == null) return; |
+ |
+ mReaderCallbackHandler = new ReaderCallbackHandler(this); |
+ mNfcAdapter.enableReaderMode(mActivity, mReaderCallbackHandler, |
+ NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_NFC_B |
+ | NfcAdapter.FLAG_READER_NFC_F | NfcAdapter.FLAG_READER_NFC_V, |
+ null); |
+ } |
+ |
+ /** |
+ * Disables reader mode. |
+ * @see android.nfc.NfcAdapter#disableReaderMode |
+ */ |
+ private void disableReaderMode() { |
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; |
+ |
+ mReaderCallbackHandler = null; |
+ if (mActivity != null) { |
+ mNfcAdapter.disableReaderMode(mActivity); |
+ } |
+ } |
+ |
+ /** |
+ * 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(); |
+ } |
+ |
+ if (error != null) mTagHandler = null; |
+ } |
+ |
+ /** |
+ * Checks whether there is a #mPendingPushOperation and writes data to NFC tag. In case of |
+ * exception calls pendingPushOperationCompleted() with appropriate error object. |
+ */ |
+ private void processPendingPushOperation() { |
+ if (mTagHandler == null || mPendingPushOperation == null) return; |
+ |
+ if (mTagHandler.isTagOutOfRange()) { |
+ mTagHandler = null; |
+ return; |
+ } |
+ |
+ try { |
+ 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)); |
+ } catch (TagLostException e) { |
+ Log.w(TAG, "Cannot write data to NFC tag. Tag is lost."); |
+ pendingPushOperationCompleted(createError(NfcErrorType.IO_ERROR)); |
+ } catch (FormatException | IOException e) { |
+ Log.w(TAG, "Cannot write data to NFC tag. IO_ERROR."); |
+ pendingPushOperationCompleted(createError(NfcErrorType.IO_ERROR)); |
+ } |
+ } |
+ |
+ /** |
+ * Called by ReaderCallbackHandler when NFC tag is in proximity. |
+ */ |
+ public void onTagDiscovered(Tag tag) { |
+ mTagHandler = NfcTagHandler.create(tag); |
+ processPendingPushOperation(); |
+ } |
+} |