/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.uwb;

import static com.android.internal.util.Preconditions.checkNotNull;

import android.Manifest.permission;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.os.Binder;
import android.os.Build;
import android.os.CancellationSignal;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.util.Log;

import androidx.annotation.RequiresApi;

import com.android.internal.annotations.GuardedBy;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

/**
 * This class provides a way to perform Ultra Wideband (UWB) operations such as querying the
 * device's capabilities and determining the distance and angle between the local device and a
 * remote device.
 *
 * <p>To get a {@link UwbManager}, call the <code>Context.getSystemService(UwbManager.class)</code>.
 *
 * <p> Note: This API surface uses opaque {@link PersistableBundle} params. These params are to be
 * created using the provided UWB support library. The support library is present in this
 * location on AOSP: <code>packages/modules/Uwb/service/support_lib/</code>
 *
 * @hide
 */
@SystemApi
@SystemService(Context.UWB_SERVICE)
public final class UwbManager {
    private static final String TAG = "UwbManager";

    private final Context mContext;
    private final IUwbAdapter mUwbAdapter;
    private final AdapterStateListener mAdapterStateListener;
    private final RangingManager mRangingManager;
    private final UwbVendorUciCallbackListener mUwbVendorUciCallbackListener;
    private final UwbOemExtensionCallbackListener mUwbOemExtensionCallbackListener;

    /**
     * Interface for receiving UWB adapter state changes
     */
    public interface AdapterStateCallback {
        /**
         * @hide
         */
        @Retention(RetentionPolicy.SOURCE)
        @IntDef(value = {
                STATE_CHANGED_REASON_SESSION_STARTED,
                STATE_CHANGED_REASON_ALL_SESSIONS_CLOSED,
                STATE_CHANGED_REASON_SYSTEM_POLICY,
                STATE_CHANGED_REASON_SYSTEM_BOOT,
                STATE_CHANGED_REASON_ERROR_UNKNOWN,
                STATE_CHANGED_REASON_SYSTEM_REGULATION})
        @interface StateChangedReason {}

        /**
         * @hide
         */
        @Retention(RetentionPolicy.SOURCE)
        @IntDef(value = {
                STATE_ENABLED_INACTIVE,
                STATE_ENABLED_ACTIVE,
                STATE_DISABLED,
                STATE_ENABLED_HW_IDLE})
        @interface State {}

        /**
         * Indicates that the state change was due to opening of first UWB session
         */
        int STATE_CHANGED_REASON_SESSION_STARTED = 0;

        /**
         * Indicates that the state change was due to closure of all UWB sessions
         */
        int STATE_CHANGED_REASON_ALL_SESSIONS_CLOSED = 1;

        /**
         * Indicates that the state change was due to changes in system policy
         */
        int STATE_CHANGED_REASON_SYSTEM_POLICY = 2;

        /**
         * Indicates that the current state is due to a system boot
         */
        int STATE_CHANGED_REASON_SYSTEM_BOOT = 3;

        /**
         * Indicates that the state change was due to some unknown error
         */
        int STATE_CHANGED_REASON_ERROR_UNKNOWN = 4;

        /**
         * Indicates that the state change is due to a system regulation.
         */
        int STATE_CHANGED_REASON_SYSTEM_REGULATION = 5;

        /**
         * Indicates that UWB is disabled on device
         */
        int STATE_DISABLED = 0;
        /**
         * Indicates that UWB is enabled on device but has no active ranging sessions
         */
        int STATE_ENABLED_INACTIVE = 1;

        /**
         * Indicates that UWB is enabled and has active ranging session
         */
        int STATE_ENABLED_ACTIVE = 2;

        /**
         * The state when UWB is enabled by user but the hardware is not enabled since no clients
         * have requested for it.
         * Only sent if the device supports {@link #isUwbHwIdleTurnOffEnabled()} feature.
         */
        @FlaggedApi("com.android.uwb.flags.hw_state")
        int STATE_ENABLED_HW_IDLE = 3;

        /**
         * Invoked when underlying UWB adapter's state is changed
         * <p>Invoked with the adapter's current state after registering an
         * {@link AdapterStateCallback} using
         * {@link UwbManager#registerAdapterStateCallback(Executor, AdapterStateCallback)}.
         *
         * <p>Possible reasons for the state to change are
         * {@link #STATE_CHANGED_REASON_SESSION_STARTED},
         * {@link #STATE_CHANGED_REASON_ALL_SESSIONS_CLOSED},
         * {@link #STATE_CHANGED_REASON_SYSTEM_POLICY},
         * {@link #STATE_CHANGED_REASON_SYSTEM_BOOT},
         * {@link #STATE_CHANGED_REASON_ERROR_UNKNOWN}.
         * {@link #STATE_CHANGED_REASON_SYSTEM_REGULATION}.
         *
         * <p>Possible values for the UWB state are
         * {@link #STATE_ENABLED_INACTIVE},
         * {@link #STATE_ENABLED_ACTIVE},
         * {@link #STATE_DISABLED}.
         *
         * @param state the UWB state; inactive, active or disabled
         * @param reason the reason for the state change
         */
        void onStateChanged(@State int state, @StateChangedReason int reason);
    }

    /**
     * Abstract class for receiving ADF provisioning state.
     * Should be extended by applications and set when calling
     * {@link UwbManager#provisionProfileAdfByScript(PersistableBundle, Executor,
     * AdfProvisionStateCallback)}
     */
    public abstract static class AdfProvisionStateCallback {
        private final AdfProvisionStateCallbackProxy mAdfProvisionStateCallbackProxy;

        public AdfProvisionStateCallback() {
            mAdfProvisionStateCallbackProxy = new AdfProvisionStateCallbackProxy();
        }

        /**
         * @hide
         */
        @Retention(RetentionPolicy.SOURCE)
        @IntDef(value = {
                REASON_INVALID_OID,
                REASON_SE_FAILURE,
                REASON_UNKNOWN
        })
        @interface Reason { }

        /**
         * Indicates that the OID provided was not valid.
         */
        public static final int REASON_INVALID_OID = 1;

        /**
         * Indicates that there was some SE (secure element) failure while provisioning.
         */
        public static final int REASON_SE_FAILURE = 2;

        /**
         * No known reason for the failure.
         */
        public static final int REASON_UNKNOWN = 3;

        /**
         * Invoked when {@link UwbManager#provisionProfileAdfByScript(PersistableBundle, Executor,
         * AdfProvisionStateCallback)} is successful.
         *
         * @param params protocol specific params that provide the caller with provisioning info
         **/
        public abstract void onProfileAdfsProvisioned(@NonNull PersistableBundle params);

        /**
         * Invoked when {@link UwbManager#provisionProfileAdfByScript(PersistableBundle, Executor,
         * AdfProvisionStateCallback)} fails.
         *
         * @param reason Reason for failure
         * @param params protocol specific parameters to indicate failure reason
         */
        public abstract void onProfileAdfsProvisionFailed(
                @Reason int reason, @NonNull PersistableBundle params);

        /*package*/
        @NonNull
        AdfProvisionStateCallbackProxy getProxy() {
            return mAdfProvisionStateCallbackProxy;
        }

        private static class AdfProvisionStateCallbackProxy extends
                IUwbAdfProvisionStateCallbacks.Stub {
            private final Object mLock = new Object();
            @Nullable
            @GuardedBy("mLock")
            private Executor mExecutor;
            @Nullable
            @GuardedBy("mLock")
            private AdfProvisionStateCallback mCallback;

            AdfProvisionStateCallbackProxy() {
                mCallback = null;
                mExecutor = null;
            }

            /*package*/ void initProxy(@NonNull Executor executor,
                    @NonNull AdfProvisionStateCallback callback) {
                synchronized (mLock) {
                    mExecutor = executor;
                    mCallback = callback;
                }
            }

            /*package*/ void cleanUpProxy() {
                synchronized (mLock) {
                    mExecutor = null;
                    mCallback = null;
                }
            }

            @Override
            public void onProfileAdfsProvisioned(@NonNull PersistableBundle params) {
                Log.v(TAG, "AdfProvisionStateCallbackProxy: onProfileAdfsProvisioned : " + params);
                AdfProvisionStateCallback callback;
                Executor executor;
                synchronized (mLock) {
                    executor = mExecutor;
                    callback = mCallback;
                }
                if (callback == null || executor == null) {
                    return;
                }
                Binder.clearCallingIdentity();
                executor.execute(() -> callback.onProfileAdfsProvisioned(params));
                cleanUpProxy();
            }

            @Override
            public void onProfileAdfsProvisionFailed(@AdfProvisionStateCallback.Reason int reason,
                    @NonNull PersistableBundle params) {
                Log.v(TAG, "AdfProvisionStateCallbackProxy: onProfileAdfsProvisionFailed : "
                        + reason + ", " + params);
                AdfProvisionStateCallback callback;
                Executor executor;
                synchronized (mLock) {
                    executor = mExecutor;
                    callback = mCallback;
                }
                if (callback == null || executor == null) {
                    return;
                }
                Binder.clearCallingIdentity();
                executor.execute(() -> callback.onProfileAdfsProvisionFailed(reason, params));
                cleanUpProxy();
            }
        }
    }

    /**
     * Interface for receiving vendor UCI responses and notifications.
     */
    public interface UwbVendorUciCallback {
        /**
         * Invoked when a vendor specific UCI response is received.
         *
         * @param gid Group ID of the command. This needs to be one of the vendor reserved GIDs from
         *            the UCI specification.
         * @param oid Opcode ID of the command. This is left to the OEM / vendor to decide.
         * @param payload containing vendor Uci message payload.
         */
        void onVendorUciResponse(
                @IntRange(from = 0, to = 15) int gid, int oid, @NonNull byte[] payload);

        /**
         * Invoked when a vendor specific UCI notification is received.
         *
         * @param gid Group ID of the command. This needs to be one of the vendor reserved GIDs from
         *            the UCI specification.
         * @param oid Opcode ID of the command. This is left to the OEM / vendor to decide.
         * @param payload containing vendor Uci message payload.
         */
        void onVendorUciNotification(
                @IntRange(from = 9, to = 15) int gid, int oid, @NonNull byte[] payload);
    }


    /**
     * @hide
     * Vendor configuration successful for the session
     */
    public static final int VENDOR_SET_SESSION_CONFIGURATION_SUCCESS = 0;

    /**
     * @hide
     * Failure to set vendor configuration for the session
     */
    public static final int VENDOR_SET_SESSION_CONFIGURATION_FAILURE = 1;

    /**
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(value = {
            VENDOR_SET_SESSION_CONFIGURATION_SUCCESS,
            VENDOR_SET_SESSION_CONFIGURATION_FAILURE,
    })
    @interface VendorConfigStatus {}


    /**
     * Interface for Oem extensions on ongoing session
     */
    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
    public interface UwbOemExtensionCallback {
        /**
         * Invoked when session status changes.
         *
         * @param sessionStatusBundle session related info
         */
        void onSessionStatusNotificationReceived(@NonNull PersistableBundle sessionStatusBundle);

        /**
         * Invoked when DeviceStatusNotification is received from UCI.
         *
         * @param deviceStatusBundle device state
         */
        void onDeviceStatusNotificationReceived(@NonNull PersistableBundle deviceStatusBundle);

        /**
         * Invoked when session configuration is complete.
         *
         * @param openSessionBundle Session Params
         * @return Error code
         */
        @NonNull @VendorConfigStatus int onSessionConfigurationComplete(
                @NonNull PersistableBundle openSessionBundle);

        /**
         * Invoked when ranging report is generated.
         *
         * @param rangingReport ranging report generated
         * @return Oem modified ranging report
         */
        @NonNull RangingReport onRangingReportReceived(
                @NonNull RangingReport rangingReport);

        /**
         * Invoked to check pointed target decision by Oem.
         *
         * @param pointedTargetBundle pointed target params
         * @return Oem pointed status
         */
        boolean onCheckPointedTarget(@NonNull PersistableBundle pointedTargetBundle);
    }

    /**
     * Use <code>Context.getSystemService(UwbManager.class)</code> to get an instance.
     *
     * @param ctx Context of the client.
     * @param adapter an instance of an {@link android.uwb.IUwbAdapter}
     * @hide
     */
    public UwbManager(@NonNull Context ctx, @NonNull IUwbAdapter adapter) {
        mContext = ctx;
        mUwbAdapter = adapter;
        mAdapterStateListener = new AdapterStateListener(adapter);
        mRangingManager = new RangingManager(adapter);
        mUwbVendorUciCallbackListener = new UwbVendorUciCallbackListener(adapter);
        mUwbOemExtensionCallbackListener = new UwbOemExtensionCallbackListener(adapter);
    }

    /**
     * Register an {@link AdapterStateCallback} to listen for UWB adapter state changes
     * <p>The provided callback will be invoked by the given {@link Executor}.
     *
     * <p>When first registering a callback, the callbacks's
     * {@link AdapterStateCallback#onStateChanged(int, int)} is immediately invoked to indicate
     * the current state of the underlying UWB adapter with the most recent
     * {@link AdapterStateCallback.StateChangedReason} that caused the change.
     *
     * @param executor an {@link Executor} to execute given callback
     * @param callback user implementation of the {@link AdapterStateCallback}
     */
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public void registerAdapterStateCallback(@NonNull @CallbackExecutor Executor executor,
            @NonNull AdapterStateCallback callback) {
        mAdapterStateListener.register(executor, callback);
    }

    /**
     * Unregister the specified {@link AdapterStateCallback}
     * <p>The same {@link AdapterStateCallback} object used when calling
     * {@link #registerAdapterStateCallback(Executor, AdapterStateCallback)} must be used.
     *
     * <p>Callbacks are automatically unregistered when application process goes away
     *
     * @param callback user implementation of the {@link AdapterStateCallback}
     */
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public void unregisterAdapterStateCallback(@NonNull AdapterStateCallback callback) {
        mAdapterStateListener.unregister(callback);
    }

    /**
     * Register an {@link UwbVendorUciCallback} to listen for UWB vendor responses and notifications
     * <p>The provided callback will be invoked by the given {@link Executor}.
     *
     * <p>When first registering a callback, the callbacks's
     * {@link UwbVendorUciCallback#onVendorUciCallBack(byte[])} is immediately invoked to
     * notify the vendor notification.
     *
     * @param executor an {@link Executor} to execute given callback
     * @param callback user implementation of the {@link UwbVendorUciCallback}
     */
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public void registerUwbVendorUciCallback(@NonNull @CallbackExecutor Executor executor,
            @NonNull UwbVendorUciCallback callback) {
        mUwbVendorUciCallbackListener.register(executor, callback);
    }

    /**
     * Unregister the specified {@link UwbVendorUciCallback}
     *
     * <p>The same {@link UwbVendorUciCallback} object used when calling
     * {@link #registerUwbVendorUciCallback(Executor, UwbVendorUciCallback)} must be used.
     *
     * <p>Callbacks are automatically unregistered when application process goes away
     *
     * @param callback user implementation of the {@link UwbVendorUciCallback}
     */
    public void unregisterUwbVendorUciCallback(@NonNull UwbVendorUciCallback callback) {
        mUwbVendorUciCallbackListener.unregister(callback);
    }

    /**
     * Register an {@link UwbOemExtensionCallback} to listen for UWB oem extension callbacks
     * <p>The provided callback will be invoked by the given {@link Executor}.
     *
     * @param executor an {@link Executor} to execute given callback
     * @param callback oem implementation of {@link UwbOemExtensionCallback}
     */
    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public void registerUwbOemExtensionCallback(@NonNull @CallbackExecutor Executor executor,
            @NonNull UwbOemExtensionCallback callback) {
        mUwbOemExtensionCallbackListener.register(executor, callback);
    }

    /**
     * Unregister the specified {@link UwbOemExtensionCallback}
     *
     * <p>The same {@link UwbOemExtensionCallback} object used when calling
     * {@link #registerUwbOemExtensionCallback(Executor, UwbOemExtensionCallback)} must be used.
     *
     * <p>Callbacks are automatically unregistered when an application process goes away
     *
     * @param callback oem implementation of {@link UwbOemExtensionCallback}
     */
    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public void unregisterUwbOemExtensionCallback(@NonNull UwbOemExtensionCallback callback) {
        mUwbOemExtensionCallbackListener.unregister(callback);
    }

    /**
     * Get a {@link PersistableBundle} with the supported UWB protocols and parameters.
     * <p>The {@link PersistableBundle} should be parsed using a support library
     *
     * <p>Android reserves the '^android.*' namespace</p>
     *
     * @return {@link PersistableBundle} of the device's supported UWB protocols and parameters
     */
    @NonNull
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public PersistableBundle getSpecificationInfo() {
        return getSpecificationInfoInternal(/* chipId= */ null);
    }

    /**
     * Get a {@link PersistableBundle} with the supported UWB protocols and parameters.
     *
     * @see #getSpecificationInfo() if you don't need multi-HAL support
     *
     * @param chipId identifier of UWB chip for multi-HAL devices
     *
     * @return {@link PersistableBundle} of the device's supported UWB protocols and parameters
     */
    // TODO(b/205614701): Add documentation about how to find the relevant chipId
    @NonNull
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public PersistableBundle getSpecificationInfo(@NonNull String chipId) {
        checkNotNull(chipId);
        return getSpecificationInfoInternal(chipId);
    }

    private PersistableBundle getSpecificationInfoInternal(String chipId) {
        try {
            return mUwbAdapter.getSpecificationInfo(chipId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Get uwbs timestamp in micros.
     *
     * @return uwb device timestamp in micros.
     */
    @NonNull
    @RequiresPermission(permission.UWB_PRIVILEGED)
    @FlaggedApi("com.android.uwb.flags.query_timestamp_micros")
    public long queryUwbsTimestampMicros() {
        try {
            return mUwbAdapter.queryUwbsTimestampMicros();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Get the timestamp resolution for events in nanoseconds
     * <p>This value defines the maximum error of all timestamps for events reported to
     * {@link RangingSession.Callback}.
     *
     * @return the timestamp resolution in nanoseconds
     */
    @SuppressLint("MethodNameUnits")
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public long elapsedRealtimeResolutionNanos() {
        return elapsedRealtimeResolutionNanosInternal(/* chipId= */ null);
    }

    /**
     * Get the timestamp resolution for events in nanoseconds
     *
     * @see #elapsedRealtimeResolutionNanos() if you don't need multi-HAL support
     *
     * @param chipId identifier of UWB chip for multi-HAL devices
     *
     * @return the timestamp resolution in nanoseconds
     */
    @SuppressLint("MethodNameUnits")
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public long elapsedRealtimeResolutionNanos(@NonNull String chipId) {
        checkNotNull(chipId);
        return elapsedRealtimeResolutionNanosInternal(chipId);
    }

    private long elapsedRealtimeResolutionNanosInternal(String chipId) {
        try {
            return mUwbAdapter.getTimestampResolutionNanos(chipId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Open a {@link RangingSession} with the given parameters
     * <p>The {@link RangingSession.Callback#onOpened(RangingSession)} function is called with a
     * {@link RangingSession} object used to control ranging when the session is successfully
     * opened.
     *
     * if this session uses FIRA defined profile (not custom profile), this triggers:
     *   - OOB discovery using service UUID
     *   - OOB connection establishment after discovery for session params
     *     negotiation.
     *   - Secure element interactions needed for dynamic STS based session establishment.
     *   - Setup the UWB session based on the parameters negotiated via OOB.
     *   - Note: The OOB flow requires additional BLE Permissions
     *     {permission.BLUETOOTH_ADVERTISE/permission.BLUETOOTH_SCAN
     *      and permission.BLUETOOTH_CONNECT}.
     *
     * <p>If a session cannot be opened, then
     * {@link RangingSession.Callback#onClosed(int, PersistableBundle)} will be invoked with the
     * appropriate {@link RangingSession.Callback.Reason}.
     *
     * <p>An open {@link RangingSession} will be automatically closed if client application process
     * dies.
     *
     * <p>A UWB support library must be used in order to construct the {@code parameter}
     * {@link PersistableBundle}.
     *
     * @param parameters the parameters that define the ranging session
     * @param executor {@link Executor} to run callbacks
     * @param callbacks {@link RangingSession.Callback} to associate with the
     *                  {@link RangingSession} that is being opened.
     *
     * @return an {@link CancellationSignal} that is able to be used to cancel the opening of a
     *         {@link RangingSession} that has been requested through {@link #openRangingSession}
     *         but has not yet been made available by
     *         {@link RangingSession.Callback#onOpened(RangingSession)}.
     */
    @NonNull
    @RequiresPermission(allOf = {
            permission.UWB_PRIVILEGED,
            permission.UWB_RANGING
    })
    public CancellationSignal openRangingSession(@NonNull PersistableBundle parameters,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull RangingSession.Callback callbacks) {
        return openRangingSessionInternal(parameters, executor, callbacks, /* chipId= */ null);
    }

    /**
     * Open a {@link RangingSession} with the given parameters on a specific UWB subsystem
     *
     * @see #openRangingSession(PersistableBundle, Executor, RangingSession.Callback) if you don't
     * need multi-HAL support
     *
     * @param parameters the parameters that define the ranging session
     * @param executor {@link Executor} to run callbacks
     * @param callbacks {@link RangingSession.Callback} to associate with the
     *                  {@link RangingSession} that is being opened.
     * @param chipId identifier of UWB chip for multi-HAL devices
     *
     * @return an {@link CancellationSignal} that is able to be used to cancel the opening of a
     *         {@link RangingSession} that has been requested through {@link #openRangingSession}
     *         but has not yet been made available by
     *         {@link RangingSession.Callback#onOpened(RangingSession)}.
     */
    @NonNull
    @RequiresPermission(allOf = {
            permission.UWB_PRIVILEGED,
            permission.UWB_RANGING
    })
    public CancellationSignal openRangingSession(@NonNull PersistableBundle parameters,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull RangingSession.Callback callbacks,
            @SuppressLint("ListenerLast") @NonNull String chipId) {
        checkNotNull(chipId);
        return openRangingSessionInternal(parameters, executor, callbacks, chipId);
    }

    private CancellationSignal openRangingSessionInternal(PersistableBundle parameters,
            Executor executor, RangingSession.Callback callbacks, String chipId) {
        return mRangingManager.openSession(
                mContext.getAttributionSource(), parameters, executor, callbacks, chipId);
    }

    /**
     * Returns the current enabled/disabled state for UWB.
     *
     * Possible values are:
     * AdapterStateCallback#STATE_DISABLED
     * AdapterStateCallback#STATE_ENABLED_INACTIVE
     * AdapterStateCallback#STATE_ENABLED_ACTIVE
     *
     * @return value representing current enabled/disabled state for UWB.
     */
    public @AdapterStateCallback.State int getAdapterState() {
        return mAdapterStateListener.getAdapterState();
    }

    /**
     * Whether UWB is enabled or disabled.
     *
     * <p>
     * If disabled, this could indicate that either
     * <li> User has toggled UWB off from settings, OR </li>
     * <li> UWB subsystem has shut down due to a fatal error. </li>
     * </p>
     *
     * @return true if enabled, false otherwise.
     *
     * @see #getAdapterState()
     * @see #setUwbEnabled(boolean)
     */
    public boolean isUwbEnabled() {
        int adapterState = getAdapterState();
        return adapterState != AdapterStateCallback.STATE_DISABLED;
    }

    /**
     * Disables or enables UWB by the user.
     *
     * If enabled any subsequent calls to
     * {@link #openRangingSession(PersistableBundle, Executor, RangingSession.Callback)} will be
     * allowed. If disabled, all active ranging sessions will be closed and subsequent calls to
     * {@link #openRangingSession(PersistableBundle, Executor, RangingSession.Callback)} will be
     * disallowed.
     *
     * @param enabled value representing intent to disable or enable UWB.
     */
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public void setUwbEnabled(boolean enabled) {
        mAdapterStateListener.setEnabled(enabled);
    }

    /**
     * Whether UWB hardware will automatically turn off when there are no clients requesting it.
     * This feature is only turned on non-phone form factor devices which needs to keep the UWB
     * hardware turned to avoid battery drain.
     *
     * <p>
     * If the device supports automatically turning off UWB hardware, the state of UWB hardware
     * is controlled by:
     * <li> UWB user toggle state or Airplane mode state, AND </li>
     * <li> Whether any clients are actively enabling UWB </li>
     * </p>
     *
     * @return true if enabled, false otherwise.
     *
     * @see #isUwbHwEnableRequested()
     * @see #requestUwbHwEnable(boolean)
     */
    @FlaggedApi("com.android.uwb.flags.hw_state")
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public boolean isUwbHwIdleTurnOffEnabled() {
        try {
            return mUwbAdapter.isHwIdleTurnOffEnabled();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Whether this client has requested for UWB hardware to be enabled or disabled.
     * Only supported on devices which supports hw idle turn off (indicated by
     * {@link #isUwbHwIdleTurnOffEnabled()})
     *
     * <p>
     * This does not indicate the global state of UWB, this only indicates whether this app
     * (identified by {@link Context#getAttributionSource()}) has requested for UWB hardware to be
     * enabled or disabled.
     * </p>
     *
     * @return true if enabled, false otherwise.
     * @throws IllegalStateException if the device does not support this feature
     *
     * @see #isUwbHwIdleTurnOffEnabled()
     * @see #requestUwbHwEnable(boolean)
     */
    @FlaggedApi("com.android.uwb.flags.hw_state")
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public boolean isUwbHwEnableRequested() {
        try {
            return mUwbAdapter.isHwEnableRequested(mContext.getAttributionSource());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * This client has requested for UWB hardware to be enabled or disabled.
     * Only supported on devices which supports hw idle turn off (indicated by
     * {@link #isUwbHwIdleTurnOffEnabled()})
     *
     * <p>
     * This does not indicate the global state of UWB, this only indicates whether this app
     * (identified by {@link Context#getAttributionSource()}) has requested for UWB hardware to be
     * enabled or disabled.
     * If UWB is enabled by the user and has at least 1 privileged client requesting UWB toggle on,
     * then UWB hardware is enabled, else the UWB hardware is disabled.
     * </p>
     *
     * @param enabled value representing intent to disable or enable UWB.
     * @throws IllegalStateException if the device does not support this feature
     *
     * @see #isUwbHwIdleTurnOffEnabled()
     * @see #isUwbHwEnableRequested() ()
     */
    @FlaggedApi("com.android.uwb.flags.hw_state")
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public void requestUwbHwEnabled(boolean enabled) {
        try {
            mUwbAdapter.requestHwEnabled(
                    enabled, mContext.getAttributionSource(),
                    new Binder(mContext.getPackageName()));
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns a list of UWB chip infos in a {@link PersistableBundle}.
     *
     * Callers can invoke methods on a specific UWB chip by passing its {@code chipId} to the
     * method, which can be determined by calling:
     * <pre>
     * List<PersistableBundle> chipInfos = getChipInfos();
     * for (PersistableBundle chipInfo : chipInfos) {
     *     String chipId = ChipInfoParams.fromBundle(chipInfo).getChipId();
     * }
     * </pre>
     *
     * @return list of {@link PersistableBundle} containing info about UWB chips for a multi-HAL
     * system, or a list of info for a single chip for a single HAL system.
     */
    @RequiresPermission(permission.UWB_PRIVILEGED)
    @NonNull
    public List<PersistableBundle> getChipInfos() {
        try {
            return mUwbAdapter.getChipInfos();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns the default UWB chip identifier.
     *
     * If callers do not pass a specific {@code chipId} to UWB methods, then the method will be
     * invoked on the default chip, which is determined at system initialization from a
     * configuration file.
     *
     * @return default UWB chip identifier for a multi-HAL system, or the identifier of the only UWB
     * chip in a single HAL system.
     */
    @RequiresPermission(permission.UWB_PRIVILEGED)
    @NonNull
    public String getDefaultChipId() {
        try {
            return mUwbAdapter.getDefaultChipId();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Register the UWB service profile.
     * This profile instance is persisted by the platform until explicitly removed
     * using {@link #removeServiceProfile(PersistableBundle)}
     *
     * @param parameters the parameters that define the service profile.
     * @return Protocol specific params to be used as handle for triggering the profile.
     */
    @RequiresPermission(permission.UWB_PRIVILEGED)
    @NonNull
    public PersistableBundle addServiceProfile(@NonNull PersistableBundle parameters) {
        try {
            return mUwbAdapter.addServiceProfile(parameters);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Successfully removed the service profile.
     */
    public static final int REMOVE_SERVICE_PROFILE_SUCCESS = 0;

    /**
     * Failed to remove service since the service profile is unknown.
     */
    public static final int REMOVE_SERVICE_PROFILE_ERROR_UNKNOWN_SERVICE = 1;

    /**
     * Failed to remove service due to some internal error while processing the request.
     */
    public static final int REMOVE_SERVICE_PROFILE_ERROR_INTERNAL = 2;

    /**
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(value = {
            REMOVE_SERVICE_PROFILE_SUCCESS,
            REMOVE_SERVICE_PROFILE_ERROR_UNKNOWN_SERVICE,
            REMOVE_SERVICE_PROFILE_ERROR_INTERNAL
    })
    @interface RemoveServiceProfile {}

    /**
     * Remove the service profile registered with {@link #addServiceProfile} and
     * all related resources.
     *
     * @param parameters the parameters that define the service profile.
     *
     * @return true if the service profile is removed, false otherwise.
     */
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public @RemoveServiceProfile int removeServiceProfile(@NonNull PersistableBundle parameters) {
        try {
            return mUwbAdapter.removeServiceProfile(parameters);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Get all service profiles initialized with {@link #addServiceProfile}
     *
     * @return the parameters that define the service profiles.
     */
    @RequiresPermission(permission.UWB_PRIVILEGED)
    @NonNull
    public PersistableBundle getAllServiceProfiles() {
        try {
            return mUwbAdapter.getAllServiceProfiles();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Get the list of ADF (application defined file) provisioning authorities available for the UWB
     * applet in SE (secure element).
     *
     * @param serviceProfileBundle Parameters representing the profile to use.
     * @return The list of key information of ADF provisioning authority defined in FiRa
     * CSML 8.2.2.7.2.4 and 8.2.2.14.4.1.2.
     */
    @RequiresPermission(permission.UWB_PRIVILEGED)
    @NonNull
    public PersistableBundle getAdfProvisioningAuthorities(
            @NonNull PersistableBundle serviceProfileBundle) {
        try {
            return mUwbAdapter.getAdfProvisioningAuthorities(serviceProfileBundle);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Get certificate information for the UWB applet in SE (secure element) that can be used to
     * provision ADF (application defined file).
     *
     * @param serviceProfileBundle Parameters representing the profile to use.
     * @return The Fira applet certificate information defined in FiRa CSML 7.3.4.3 and
     * 8.2.2.14.4.1.1
     */
    @RequiresPermission(permission.UWB_PRIVILEGED)
    @NonNull
    public PersistableBundle getAdfCertificateInfo(
            @NonNull PersistableBundle serviceProfileBundle) {
        try {
            return mUwbAdapter.getAdfCertificateAndInfo(serviceProfileBundle);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Mechanism to provision ADFs (application defined file) in the UWB applet present in SE
     * (secure element) for a profile instance.
     *
     * @param serviceProfileBundle Parameters representing the profile to use.
     * @param executor an {@link Executor} to execute given callback
     * @param callback user implementation of the {@link AdapterStateCallback}
     */
    public void provisionProfileAdfByScript(@NonNull PersistableBundle serviceProfileBundle,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull AdfProvisionStateCallback callback) {
        if (executor == null) throw new IllegalArgumentException("executor must not be null");
        if (callback == null) throw new IllegalArgumentException("callback must not be null");
        AdfProvisionStateCallback.AdfProvisionStateCallbackProxy proxy = callback.getProxy();
        proxy.initProxy(executor, callback);
        try {
            mUwbAdapter.provisionProfileAdfByScript(serviceProfileBundle, proxy);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Successfully removed the profile ADF.
     */
    public static final int REMOVE_PROFILE_ADF_SUCCESS = 0;

    /**
     * Failed to remove ADF since the service profile is unknown.
     */
    public static final int REMOVE_PROFILE_ADF_ERROR_UNKNOWN_SERVICE = 1;

    /**
     * Failed to remove ADF due to some internal error while processing the request.
     */
    public static final int REMOVE_PROFILE_ADF_ERROR_INTERNAL = 2;

    /**
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(value = {
            REMOVE_PROFILE_ADF_SUCCESS,
            REMOVE_PROFILE_ADF_ERROR_UNKNOWN_SERVICE,
            REMOVE_PROFILE_ADF_ERROR_INTERNAL
    })
    @interface RemoveProfileAdf {}

    /**
     * Remove the ADF (application defined file) provisioned by {@link #provisionProfileAdfByScript}
     *
     * @param serviceProfileBundle Parameters representing the profile to use.
     * @return true if the ADF is removed, false otherwise.
     */
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public @RemoveProfileAdf int removeProfileAdf(@NonNull PersistableBundle serviceProfileBundle) {
        try {
            return mUwbAdapter.removeProfileAdf(serviceProfileBundle);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Successfully sent the UCI message.
     */
    public static final int SEND_VENDOR_UCI_SUCCESS = 0;

    /**
     * Failed to send the UCI message because of an error returned from the HAL interface.
     */
    public static final int SEND_VENDOR_UCI_ERROR_HW = 1;

    /**
     * Failed to send the UCI message since UWB is toggled off.
     */
    public static final int SEND_VENDOR_UCI_ERROR_OFF = 2;

    /**
     * Failed to send the UCI message since UWB UCI command is malformed.
     * GID.
     */
    public static final int SEND_VENDOR_UCI_ERROR_INVALID_ARGS = 3;

    /**
     * Failed to send the UCI message since UWB GID used is invalid.
     */
    public static final int SEND_VENDOR_UCI_ERROR_INVALID_GID = 4;

    /**
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(value = {
            SEND_VENDOR_UCI_SUCCESS,
            SEND_VENDOR_UCI_ERROR_HW,
            SEND_VENDOR_UCI_ERROR_OFF,
            SEND_VENDOR_UCI_ERROR_INVALID_ARGS,
            SEND_VENDOR_UCI_ERROR_INVALID_GID,
    })
    @interface SendVendorUciStatus {}

    /**
     * Message Type for UCI Command.
     */
    public static final int MESSAGE_TYPE_COMMAND = 1;
    /**
     * Message Type for C-APDU (Command - Application Protocol Data Unit),
     * used for communication with secure component.
     */
    public static final int MESSAGE_TYPE_TEST_1 = 4;

    /**
     * Message Type for R-APDU (Response - Application Protocol Data Unit),
     * used for communication with secure component.
     */
    public static final int MESSAGE_TYPE_TEST_2 = 5;

    /**
     * @hide
     */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(value = {
            MESSAGE_TYPE_COMMAND,
            MESSAGE_TYPE_TEST_1,
            MESSAGE_TYPE_TEST_2,
    })
    @interface MessageType {}

    /**
     * Send Vendor specific Uci Messages.
     *
     * The format of the UCI messages are defined in the UCI specification. The platform is
     * responsible for fragmenting the payload if necessary.
     *
     * @param gid Group ID of the command. This needs to be one of the vendor reserved GIDs from
     *            the UCI specification.
     * @param oid Opcode ID of the command. This is left to the OEM / vendor to decide.
     * @param payload containing vendor Uci message payload.
     */
    @NonNull
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public @SendVendorUciStatus int sendVendorUciMessage(
            @IntRange(from = 0, to = 15) int gid, int oid, @NonNull byte[] payload) {
        Objects.requireNonNull(payload, "Payload must not be null");
        try {
            return mUwbAdapter.sendVendorUciMessage(MESSAGE_TYPE_COMMAND, gid, oid, payload);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Send Vendor specific Uci Messages with custom message type.
     *
     * The format of the UCI messages are defined in the UCI specification. The platform is
     * responsible for fragmenting the payload if necessary.
     *
     * Note that mt (message type) is added at the beginning of method parameters as it is more
     * distinctive than other parameters and was requested from vendor.
     *
     * @param mt Message Type of the command
     * @param gid Group ID of the command. This needs to be one of the vendor reserved GIDs from
     *            the UCI specification
     * @param oid Opcode ID of the command. This is left to the OEM / vendor to decide
     * @param payload containing vendor Uci message payload
     */
    @NonNull
    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public @SendVendorUciStatus int sendVendorUciMessage(@MessageType int mt,
            @IntRange(from = 0, to = 15) int gid, int oid, @NonNull byte[] payload) {
        Objects.requireNonNull(payload, "Payload must not be null");
        try {
            return mUwbAdapter.sendVendorUciMessage(mt, gid, oid, payload);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    private static class OnUwbActivityEnergyInfoProxy
            extends IOnUwbActivityEnergyInfoListener.Stub {
        private final Object mLock = new Object();
        @Nullable @GuardedBy("mLock") private Executor mExecutor;
        @Nullable @GuardedBy("mLock") private Consumer<UwbActivityEnergyInfo> mListener;

        OnUwbActivityEnergyInfoProxy(Executor executor,
                Consumer<UwbActivityEnergyInfo> listener) {
            mExecutor = executor;
            mListener = listener;
        }

        @Override
        public void onUwbActivityEnergyInfo(UwbActivityEnergyInfo info) {
            Executor executor;
            Consumer<UwbActivityEnergyInfo> listener;
            synchronized (mLock) {
                if (mExecutor == null || mListener == null) {
                    return;
                }
                executor = mExecutor;
                listener = mListener;
                // null out to allow garbage collection, prevent triggering listener more than once
                mExecutor = null;
                mListener = null;
            }
            Binder.clearCallingIdentity();
            executor.execute(() -> listener.accept(info));
        }
    }

    /**
     * Request to get the current {@link UwbActivityEnergyInfo} asynchronously.
     *
     * @param executor the executor that the listener will be invoked on
     * @param listener the listener that will receive the {@link UwbActivityEnergyInfo} object
     *                 when it becomes available. The listener will be triggered at most once for
     *                 each call to this method.
     */
    @RequiresPermission(permission.UWB_PRIVILEGED)
    public void getUwbActivityEnergyInfoAsync(
            @NonNull @CallbackExecutor Executor executor,
            @NonNull Consumer<UwbActivityEnergyInfo> listener) {
        Objects.requireNonNull(executor, "executor cannot be null");
        Objects.requireNonNull(listener, "listener cannot be null");
        try {
            mUwbAdapter.getUwbActivityEnergyInfoAsync(
                    new OnUwbActivityEnergyInfoProxy(executor, listener));
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}
