[Nfc] Add nfc oem extension callback.

Test: atest CtsNfcTestCases
Bug: 338255533
Flag: android.nfc.nfc_oem_extension

Change-Id: I372cb6fea677b5732254dfd83e7ea4cbdc0c92fe
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 2ff9829..25a01b9 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -62,10 +62,29 @@
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback);
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterCallback(@NonNull android.nfc.NfcOemExtension.Callback);
+    field public static final int HCE_ACTIVATE = 1; // 0x1
+    field public static final int HCE_DATA_TRANSFERRED = 2; // 0x2
+    field public static final int HCE_DEACTIVATE = 3; // 0x3
+    field public static final int STATUS_OK = 0; // 0x0
+    field public static final int STATUS_UNKNOWN_ERROR = 1; // 0x1
   }
 
   public static interface NfcOemExtension.Callback {
+    method public void onApplyRouting(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method public void onBootFinished(int);
+    method public void onBootStarted();
+    method public void onDisable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method public void onDisableFinished(int);
+    method public void onDisableStarted();
+    method public void onEnable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method public void onEnableFinished(int);
+    method public void onEnableStarted();
+    method public void onHceEventReceived(int);
+    method public void onNdefRead(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method public void onRoutingChanged();
+    method public void onStateUpdated(int);
     method public void onTagConnected(boolean, @NonNull android.nfc.Tag);
+    method public void onTagDispatch(@NonNull java.util.function.Consumer<java.lang.Boolean>);
   }
 
 }
diff --git a/nfc/api/system-lint-baseline.txt b/nfc/api/system-lint-baseline.txt
index 761c8e6..c7a6181 100644
--- a/nfc/api/system-lint-baseline.txt
+++ b/nfc/api/system-lint-baseline.txt
@@ -9,6 +9,18 @@
     Field 'ACTION_TRANSACTION_DETECTED' is missing @BroadcastBehavior
 
 
+CallbackMethodName: android.nfc.NfcOemExtension.Callback#shouldSkipRoutingChange():
+    Callback method names must follow the on<Something> style: shouldSkipRoutingChange
+
+
+MethodNameTense: android.nfc.NfcOemExtension.Callback#onEnable():
+    Unexpected tense; probably meant `enabled`, was `onEnable`
+
+
+MissingNullability: android.nfc.cardemulation.CardEmulation#overrideRoutingTable(android.app.Activity, String, String) parameter #1:
+    Missing nullability on parameter `protocol` in method `overrideRoutingTable`
+MissingNullability: android.nfc.cardemulation.CardEmulation#overrideRoutingTable(android.app.Activity, String, String) parameter #2:
+    Missing nullability on parameter `technology` in method `overrideRoutingTable`
 MissingNullability: android.nfc.cardemulation.OffHostApduService#onBind(android.content.Intent):
     Missing nullability on method `onBind` return
 MissingNullability: android.nfc.cardemulation.OffHostApduService#onBind(android.content.Intent) parameter #0:
@@ -96,10 +108,12 @@
 RequiresPermission: android.nfc.tech.TagTechnology#connect():
     Method 'connect' documentation mentions permissions without declaring @RequiresPermission
 
+
 SamShouldBeLast: android.nfc.NfcAdapter#enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle):
     SAM-compatible parameters (such as parameter 2, "callback", in android.nfc.NfcAdapter.enableReaderMode) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.nfc.NfcAdapter#ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler):
     SAM-compatible parameters (such as parameter 3, "tagRemovedListener", in android.nfc.NfcAdapter.ignore) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 
+
 SdkConstant: android.nfc.NfcAdapter#ACTION_REQUIRE_UNLOCK_FOR_NFC:
     Field 'ACTION_REQUIRE_UNLOCK_FOR_NFC' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index 6c9096d..c19a44b 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -16,10 +16,25 @@
 package android.nfc;
 
 import android.nfc.Tag;
+import android.os.ResultReceiver;
 
 /**
  * @hide
  */
 interface INfcOemExtensionCallback {
    void onTagConnected(boolean connected, in Tag tag);
+   void onStateUpdated(int state);
+   void onApplyRouting(in ResultReceiver isSkipped);
+   void onNdefRead(in ResultReceiver isSkipped);
+   void onEnable(in ResultReceiver isAllowed);
+   void onDisable(in ResultReceiver isAllowed);
+   void onBootStarted();
+   void onEnableStarted();
+   void onDisableStarted();
+   void onBootFinished(int status);
+   void onEnableFinished(int status);
+   void onDisableFinished(int status);
+   void onTagDispatch(in ResultReceiver isSkipped);
+   void onRoutingChanged();
+   void onHceEventReceived(int action);
 }
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 204ba9f..6c02edd 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -18,17 +18,31 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.Binder;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
 
 /**
  * Used for OEM extension APIs.
@@ -50,6 +64,52 @@
     private final Object mLock = new Object();
 
     /**
+     * Event that Host Card Emulation is activated.
+     */
+    public static final int HCE_ACTIVATE = 1;
+    /**
+     * Event that some data is transferred in Host Card Emulation.
+     */
+    public static final int HCE_DATA_TRANSFERRED = 2;
+    /**
+     * Event that Host Card Emulation is deactivated.
+     */
+    public static final int HCE_DEACTIVATE = 3;
+    /**
+     * Possible events from {@link Callback#onHceEventReceived}.
+     *
+     * @hide
+     */
+    @IntDef(value = {
+            HCE_ACTIVATE,
+            HCE_DATA_TRANSFERRED,
+            HCE_DEACTIVATE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface HostCardEmulationAction {}
+
+    /**
+     * Status OK
+     */
+    public static final int STATUS_OK = 0;
+    /**
+     * Status unknown error
+     */
+    public static final int STATUS_UNKNOWN_ERROR = 1;
+
+    /**
+     * Status codes passed to OEM extension callbacks.
+     *
+     * @hide
+     */
+    @IntDef(value = {
+            STATUS_OK,
+            STATUS_UNKNOWN_ERROR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StatusCode {}
+
+    /**
      * Interface for Oem extensions for NFC.
      */
     public interface Callback {
@@ -61,21 +121,114 @@
          * @param tag Tag details
          */
         void onTagConnected(boolean connected, @NonNull Tag tag);
+
+        /**
+         * Update the Nfc Adapter State
+         * @param state new state that need to be updated
+         */
+        void onStateUpdated(@NfcAdapter.AdapterState int state);
+        /**
+         * Check if NfcService apply routing method need to be skipped for
+         * some feature.
+         * @param isSkipped The {@link Consumer} to be completed. If apply routing can be skipped,
+         *                  the {@link Consumer#accept(Object)} should be called with
+         *                  {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}.
+         */
+        void onApplyRouting(@NonNull Consumer<Boolean> isSkipped);
+        /**
+         * Check if NfcService ndefRead method need to be skipped To skip
+         * and start checking for presence of tag
+         * @param isSkipped The {@link Consumer} to be completed. If Ndef read can be skipped,
+         *                  the {@link Consumer#accept(Object)} should be called with
+         *                  {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}.
+         */
+        void onNdefRead(@NonNull Consumer<Boolean> isSkipped);
+        /**
+         * Method to check if Nfc is allowed to be enabled by OEMs.
+         * @param isAllowed The {@link Consumer} to be completed. If enabling NFC is allowed,
+         *                  the {@link Consumer#accept(Object)} should be called with
+         *                  {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}.
+         * false if NFC cannot be enabled at this time.
+         */
+        @SuppressLint("MethodNameTense")
+        void onEnable(@NonNull Consumer<Boolean> isAllowed);
+        /**
+         * Method to check if Nfc is allowed to be disabled by OEMs.
+         * @param isAllowed The {@link Consumer} to be completed. If disabling NFC is allowed,
+         *                  the {@link Consumer#accept(Object)} should be called with
+         *                  {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}.
+         * false if NFC cannot be disabled at this time.
+         */
+        void onDisable(@NonNull Consumer<Boolean> isAllowed);
+
+        /**
+         * Callback to indicate that Nfc starts to boot.
+         */
+        void onBootStarted();
+
+        /**
+         * Callback to indicate that Nfc starts to enable.
+         */
+        void onEnableStarted();
+
+        /**
+         * Callback to indicate that Nfc starts to enable.
+         */
+        void onDisableStarted();
+
+        /**
+         * Callback to indicate if NFC boots successfully or not.
+         * @param status the status code indicating if boot finished successfully
+         */
+        void onBootFinished(@StatusCode int status);
+
+        /**
+         * Callback to indicate if NFC is successfully enabled.
+         * @param status the status code indicating if enable finished successfully
+         */
+        void onEnableFinished(@StatusCode int status);
+
+        /**
+         * Callback to indicate if NFC is successfully disabled.
+         * @param status the status code indicating if disable finished successfully
+         */
+        void onDisableFinished(@StatusCode int status);
+
+        /**
+         * Check if NfcService tag dispatch need to be skipped.
+         * @param isSkipped The {@link Consumer} to be completed. If tag dispatch can be skipped,
+         *                  the {@link Consumer#accept(Object)} should be called with
+         *                  {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}.
+         */
+        void onTagDispatch(@NonNull Consumer<Boolean> isSkipped);
+
+        /**
+         * Notifies routing configuration is changed.
+         */
+        void onRoutingChanged();
+
+        /**
+         * API to activate start stop cpu boost on hce event.
+         *
+         * <p>When HCE is activated, transferring data, and deactivated,
+         * must call this method to activate, start and stop cpu boost respectively.
+         * @param action Flag indicating actions to activate, start and stop cpu boost.
+         */
+        void onHceEventReceived(@HostCardEmulationAction int action);
     }
 
 
     /**
      * Constructor to be used only by {@link NfcAdapter}.
-     * @hide
      */
-    public NfcOemExtension(@NonNull Context context, @NonNull NfcAdapter adapter) {
+    NfcOemExtension(@NonNull Context context, @NonNull NfcAdapter adapter) {
         mContext = context;
         mAdapter = adapter;
         mOemNfcExtensionCallback = new NfcOemExtensionCallback();
     }
 
     /**
-     * Register an {@link Callback} to listen for UWB oem extension callbacks
+     * Register an {@link Callback} to listen for NFC oem extension callbacks
      * <p>The provided callback will be invoked by the given {@link Executor}.
      *
      * @param executor an {@link Executor} to execute given callback
@@ -183,5 +336,162 @@
                 }
             }
         }
+        @Override
+        public void onStateUpdated(int state) throws RemoteException {
+            handleVoidCallback(state, mCallback::onStateUpdated);
+        }
+        @Override
+        public void onApplyRouting(ResultReceiver isSkipped) throws RemoteException {
+            handleVoidCallback(
+                    new ReceiverWrapper(isSkipped), mCallback::onApplyRouting);
+        }
+        @Override
+        public void onNdefRead(ResultReceiver isSkipped) throws RemoteException {
+            handleVoidCallback(
+                    new ReceiverWrapper(isSkipped), mCallback::onNdefRead);
+        }
+        @Override
+        public void onEnable(ResultReceiver isAllowed) throws RemoteException {
+            handleVoidCallback(
+                    new ReceiverWrapper(isAllowed), mCallback::onEnable);
+        }
+        @Override
+        public void onDisable(ResultReceiver isAllowed) throws RemoteException {
+            handleVoidCallback(
+                    new ReceiverWrapper(isAllowed), mCallback::onDisable);
+        }
+        @Override
+        public void onBootStarted() throws RemoteException {
+            handleVoidCallback(null, (Object input) -> mCallback.onBootStarted());
+        }
+        @Override
+        public void onEnableStarted() throws RemoteException {
+            handleVoidCallback(null, (Object input) -> mCallback.onEnableStarted());
+        }
+        @Override
+        public void onDisableStarted() throws RemoteException {
+            handleVoidCallback(null, (Object input) -> mCallback.onDisableStarted());
+        }
+        @Override
+        public void onBootFinished(int status) throws RemoteException {
+            handleVoidCallback(status, mCallback::onBootFinished);
+        }
+        @Override
+        public void onEnableFinished(int status) throws RemoteException {
+            handleVoidCallback(status, mCallback::onEnableFinished);
+        }
+        @Override
+        public void onDisableFinished(int status) throws RemoteException {
+            handleVoidCallback(status, mCallback::onDisableFinished);
+        }
+        @Override
+        public void onTagDispatch(ResultReceiver isSkipped) throws RemoteException {
+            handleVoidCallback(
+                    new ReceiverWrapper(isSkipped), mCallback::onTagDispatch);
+        }
+        @Override
+        public void onRoutingChanged() throws RemoteException {
+            handleVoidCallback(null, (Object input) -> mCallback.onRoutingChanged());
+        }
+        @Override
+        public void onHceEventReceived(int action) throws RemoteException {
+            handleVoidCallback(action, mCallback::onHceEventReceived);
+        }
+
+        private <T> void handleVoidCallback(T input, Consumer<T> callbackMethod) {
+            synchronized (mLock) {
+                if (mCallback == null || mExecutor == null) {
+                    return;
+                }
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    mExecutor.execute(() -> callbackMethod.accept(input));
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
+        }
+
+        private <S, T> S handleNonVoidCallbackWithInput(
+                S defaultValue, T input, Function<T, S> callbackMethod) throws RemoteException {
+            synchronized (mLock) {
+                if (mCallback == null) {
+                    return defaultValue;
+                }
+                final long identity = Binder.clearCallingIdentity();
+                S result = defaultValue;
+                try {
+                    ExecutorService executor = Executors.newSingleThreadExecutor();
+                    FutureTask<S> futureTask = new FutureTask<>(
+                            () -> callbackMethod.apply(input)
+                    );
+                    executor.submit(futureTask);
+                    try {
+                        result = futureTask.get(
+                                OEM_EXTENSION_RESPONSE_THRESHOLD_MS, TimeUnit.MILLISECONDS);
+                    } catch (ExecutionException | InterruptedException e) {
+                        e.printStackTrace();
+                    } catch (TimeoutException e) {
+                        Log.w(TAG, "Callback timed out: " + callbackMethod);
+                        e.printStackTrace();
+                    } finally {
+                        executor.shutdown();
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+                return result;
+            }
+        }
+
+        private <T> T handleNonVoidCallbackWithoutInput(T defaultValue, Supplier<T> callbackMethod)
+                throws RemoteException {
+            synchronized (mLock) {
+                if (mCallback == null) {
+                    return defaultValue;
+                }
+                final long identity = Binder.clearCallingIdentity();
+                T result = defaultValue;
+                try {
+                    ExecutorService executor = Executors.newSingleThreadExecutor();
+                    FutureTask<T> futureTask = new FutureTask<>(
+                            callbackMethod::get
+                    );
+                    executor.submit(futureTask);
+                    try {
+                        result = futureTask.get(
+                                OEM_EXTENSION_RESPONSE_THRESHOLD_MS, TimeUnit.MILLISECONDS);
+                    } catch (ExecutionException | InterruptedException e) {
+                        e.printStackTrace();
+                    } catch (TimeoutException e) {
+                        Log.w(TAG, "Callback timed out: " + callbackMethod);
+                        e.printStackTrace();
+                    } finally {
+                        executor.shutdown();
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+                return result;
+            }
+        }
+    }
+
+    private class ReceiverWrapper implements Consumer<Boolean> {
+        private final ResultReceiver mResultReceiver;
+
+        ReceiverWrapper(ResultReceiver resultReceiver) {
+            mResultReceiver = resultReceiver;
+        }
+
+        @Override
+        public void accept(Boolean result) {
+            mResultReceiver.send(result ? 1 : 0, null);
+        }
+
+        @Override
+        public Consumer<Boolean> andThen(Consumer<? super Boolean> after) {
+            return Consumer.super.andThen(after);
+        }
     }
 }