Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2c96522f authored by ziyiw's avatar ziyiw
Browse files

[Nfc] Add nfc oem extension callback.

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

Change-Id: I372cb6fea677b5732254dfd83e7ea4cbdc0c92fe
parent e7f900f0
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -62,10 +62,29 @@ package android.nfc {
    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>);
  }

}
+14 −0
Original line number Diff line number Diff line
@@ -9,6 +9,18 @@ BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
    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#close():
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)
+15 −0
Original line number Diff line number Diff line
@@ -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);
}
+313 −3
Original line number Diff line number Diff line
@@ -18,17 +18,31 @@ package android.nfc;

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.
@@ -49,6 +63,52 @@ public final class NfcOemExtension {
    private Callback mCallback = null;
    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.
     */
@@ -61,21 +121,114 @@ public final class NfcOemExtension {
         * @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 @@ public final class NfcOemExtension {
                }
            }
        }
        @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);
        }
    }
}