Merge "[nearby][clean up] Delete all Fast Pair AOSP and HalfSheetUX code" into udc-qpr-dev-plus-aosp
diff --git a/nearby/TEST_MAPPING b/nearby/TEST_MAPPING
index dbaca33..7e9a375 100644
--- a/nearby/TEST_MAPPING
+++ b/nearby/TEST_MAPPING
@@ -2,6 +2,11 @@
   "presubmit": [
     {
       "name": "NearbyUnitTests"
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "NearbyUnitTests"
     },
     {
       "name": "NearbyIntegrationPrivilegedTests"
@@ -9,11 +14,6 @@
     {
       "name": "NearbyIntegrationUntrustedTests"
     }
-  ],
-  "postsubmit": [
-    {
-      "name": "NearbyUnitTests"
-    }
   ]
   // TODO(b/193602229): uncomment once it's supported.
   //"mainline-presubmit": [
diff --git a/remoteauth/service/Android.bp b/remoteauth/service/Android.bp
index 2ba59da..dba8b75 100644
--- a/remoteauth/service/Android.bp
+++ b/remoteauth/service/Android.bp
@@ -47,6 +47,7 @@
         "modules-utils-preconditions",
         "modules-utils-backgroundthread",
         "presence-lite-protos",
+        "uwb_androidx_backend",
     ],
     sdk_version: "system_server_current",
     // This is included in service-connectivity which is 30+
diff --git a/remoteauth/service/java/com/android/server/remoteauth/README.md b/remoteauth/service/java/com/android/server/remoteauth/README.md
index b2b5aab..b659bf7 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/README.md
+++ b/remoteauth/service/java/com/android/server/remoteauth/README.md
@@ -6,3 +6,5 @@
 ## Ranging
 Provides the ranging manager to perform ranging with the peer devices.
 
+## Util
+Common utilities.
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingCapabilities.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingCapabilities.java
index 2b5efff..36aa585 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingCapabilities.java
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingCapabilities.java
@@ -15,6 +15,9 @@
  */
 package com.android.server.remoteauth.ranging;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import androidx.annotation.IntDef;
 
 import com.google.common.collect.ImmutableList;
@@ -43,6 +46,8 @@
     public static final int RANGING_METHOD_UWB = 0x1;
 
     private final ImmutableList<Integer> mSupportedRangingMethods;
+    private final androidx.core.uwb.backend.impl.internal.RangingCapabilities
+            mUwbRangingCapabilities;
 
     /**
      * Gets the list of supported ranging methods of the device.
@@ -53,13 +58,28 @@
         return mSupportedRangingMethods;
     }
 
-    private RangingCapabilities(List<Integer> supportedRangingMethods) {
+    /**
+     * Gets the UWB ranging capabilities of the device.
+     *
+     * @return UWB ranging capabilities, null if UWB is not a supported {@link RangingMethod} in
+     *     {@link #getSupportedRangingMethods}.
+     */
+    @Nullable
+    public androidx.core.uwb.backend.impl.internal.RangingCapabilities getUwbRangingCapabilities() {
+        return mUwbRangingCapabilities;
+    }
+
+    private RangingCapabilities(
+            List<Integer> supportedRangingMethods,
+            androidx.core.uwb.backend.impl.internal.RangingCapabilities uwbRangingCapabilities) {
         mSupportedRangingMethods = ImmutableList.copyOf(supportedRangingMethods);
+        mUwbRangingCapabilities = uwbRangingCapabilities;
     }
 
     /** Builder class for {@link RangingCapabilities}. */
     public static final class Builder {
         private List<Integer> mSupportedRangingMethods = new ArrayList<>();
+        private androidx.core.uwb.backend.impl.internal.RangingCapabilities mUwbRangingCapabilities;
 
         /** Adds a supported {@link RangingMethod} */
         public Builder addSupportedRangingMethods(@RangingMethod int rangingMethod) {
@@ -67,9 +87,18 @@
             return this;
         }
 
+        /** Sets the uwb ranging capabilities. */
+        public Builder setUwbRangingCapabilities(
+                @NonNull
+                        androidx.core.uwb.backend.impl.internal.RangingCapabilities
+                                uwbRangingCapabilities) {
+            mUwbRangingCapabilities = uwbRangingCapabilities;
+            return this;
+        }
+
         /** Builds {@link RangingCapabilities}. */
         public RangingCapabilities build() {
-            return new RangingCapabilities(mSupportedRangingMethods);
+            return new RangingCapabilities(mSupportedRangingMethods, mUwbRangingCapabilities);
         }
     }
 }
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingManager.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingManager.java
index 989b5ed..cf0db76 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingManager.java
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingManager.java
@@ -15,7 +15,21 @@
  */
 package com.android.server.remoteauth.ranging;
 
+import static android.content.pm.PackageManager.FEATURE_UWB;
+
+import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UNKNOWN;
+import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.core.uwb.backend.impl.internal.UwbFeatureFlags;
+import androidx.core.uwb.backend.impl.internal.UwbServiceImpl;
+
+import com.android.internal.util.Preconditions;
 
 /**
  * Manages the creation of generic device to device ranging session and obtaining device's ranging
@@ -25,16 +39,46 @@
  * outside of this class.
  */
 public class RangingManager {
+    private static final String TAG = "RangingManager";
 
-    public RangingManager(Context context) {}
+    private Context mContext;
+    @NonNull private RangingCapabilities mCachedRangingCapabilities;
+    @NonNull private UwbServiceImpl mUwbServiceImpl;
+
+    public RangingManager(@NonNull Context context) {
+        mContext = context;
+        if (mContext.getPackageManager().hasSystemFeature(FEATURE_UWB)) {
+            initiateUwb();
+        }
+    }
+
+    /**
+     * Shutdown and stop all listeners and tasks. After shutdown, RangingManager shall not be used
+     * anymore.
+     */
+    public void shutdown() {
+        if (mUwbServiceImpl != null) {
+            mUwbServiceImpl.shutdown();
+        }
+        Log.i(TAG, "shutdown");
+    }
 
     /**
      * Gets the {@link RangingCapabilities} of this device.
      *
      * @return RangingCapabilities.
      */
+    @NonNull
     public RangingCapabilities getRangingCapabilities() {
-        return null;
+        if (mCachedRangingCapabilities == null) {
+            RangingCapabilities.Builder builder = new RangingCapabilities.Builder();
+            if (mUwbServiceImpl != null) {
+                builder.addSupportedRangingMethods(RANGING_METHOD_UWB);
+                builder.setUwbRangingCapabilities(mUwbServiceImpl.getRangingCapabilities());
+            }
+            mCachedRangingCapabilities = builder.build();
+        }
+        return mCachedRangingCapabilities;
     }
 
     /**
@@ -42,9 +86,35 @@
      * provided based on the rangingCapabilities of the device.
      *
      * @param sessionParameters parameters used to setup the session.
-     * @return the created RangingSession.
+     * @return the created RangingSession. Null if session creation failed.
+     * @throws IllegalArgumentException if sessionParameters is invalid.
      */
-    public RangingSession createSession(SessionParameters sessionParameters) {
+    @Nullable
+    public RangingSession createSession(@NonNull SessionParameters sessionParameters) {
+        Preconditions.checkNotNull(sessionParameters, "sessionParameters must not be null");
+        switch (sessionParameters.getRangingMethod()) {
+            case RANGING_METHOD_UWB:
+                if (mUwbServiceImpl == null) {
+                    Log.w(TAG, "createSession with UWB failed - UWB not supported");
+                    break;
+                }
+                return new UwbRangingSession(mContext, sessionParameters, mUwbServiceImpl);
+            case RANGING_METHOD_UNKNOWN:
+                break;
+        }
         return null;
     }
+
+    /** Initiation required for ranging with UWB. */
+    private void initiateUwb() {
+        UwbFeatureFlags uwbFeatureFlags =
+                new UwbFeatureFlags.Builder()
+                        .setSkipRangingCapabilitiesCheck(
+                                Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2)
+                        .setReversedByteOrderFiraParams(
+                                Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU)
+                        .build();
+        mUwbServiceImpl = new UwbServiceImpl(mContext, uwbFeatureFlags);
+        Log.i(TAG, "RangingManager initiateUwb complete");
+    }
 }
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java
index 9ef6bda..adb36c5 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java
@@ -16,9 +16,16 @@
 package com.android.server.remoteauth.ranging;
 
 import android.annotation.NonNull;
+import android.content.Context;
+import android.util.Log;
 
 import androidx.annotation.IntDef;
 
+import com.android.internal.util.Preconditions;
+import com.android.server.remoteauth.util.Crypto;
+
+import com.google.common.hash.Hashing;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.Executor;
@@ -35,18 +42,50 @@
  * <p>Ranging method specific implementation shall be implemented in the extended class.
  */
 public abstract class RangingSession {
+    private static final String TAG = "RangingSession";
 
     /** Types of ranging error. */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(
             value = {
                 RANGING_ERROR_UNKNOWN,
+                RANGING_ERROR_INVALID_PARAMETERS,
+                RANGING_ERROR_STOPPED_BY_REQUEST,
+                RANGING_ERROR_STOPPED_BY_PEER,
+                RANGING_ERROR_FAILED_TO_START,
+                RANGING_ERROR_FAILED_TO_STOP,
+                RANGING_ERROR_SYSTEM_ERROR,
+                RANGING_ERROR_SYSTEM_TIMEOUT,
             })
     public @interface RangingError {}
 
     /** Unknown ranging error type. */
     public static final int RANGING_ERROR_UNKNOWN = 0x0;
 
+    /** Ranging error due to invalid parameters. */
+    public static final int RANGING_ERROR_INVALID_PARAMETERS = 0x1;
+
+    /** Ranging error due to stopped by calling {@link #stop}. */
+    public static final int RANGING_ERROR_STOPPED_BY_REQUEST = 0x2;
+
+    /** Ranging error due to stopped by the peer device. */
+    public static final int RANGING_ERROR_STOPPED_BY_PEER = 0x3;
+
+    /** Ranging error due to failure to start ranging. */
+    public static final int RANGING_ERROR_FAILED_TO_START = 0x4;
+
+    /** Ranging error due to failure to stop ranging. */
+    public static final int RANGING_ERROR_FAILED_TO_STOP = 0x5;
+
+    /**
+     * Ranging error due to system error cause by changes such as privacy policy, power management
+     * policy, permissions, and more.
+     */
+    public static final int RANGING_ERROR_SYSTEM_ERROR = 0x6;
+
+    /** Ranging error due to system timeout in retry attempts. */
+    public static final int RANGING_ERROR_SYSTEM_TIMEOUT = 0x7;
+
     /** Interface for ranging update callbacks. */
     public interface RangingCallback {
         /**
@@ -55,7 +94,8 @@
          * @param sessionInfo info about this ranging session.
          * @param rangingReport new ranging report
          */
-        void onRangingReport(SessionInfo sessionInfo, RangingReport rangingReport);
+        void onRangingReport(
+                @NonNull SessionInfo sessionInfo, @NonNull RangingReport rangingReport);
 
         /**
          * Call upon any ranging error events.
@@ -63,7 +103,49 @@
          * @param sessionInfo info about this ranging session.
          * @param rangingError error type
          */
-        void onError(SessionInfo sessionInfo, @RangingError int rangingError);
+        void onError(@NonNull SessionInfo sessionInfo, @RangingError int rangingError);
+    }
+
+    protected Context mContext;
+    protected SessionInfo mSessionInfo;
+    protected float mLowerProximityBoundaryM;
+    protected float mUpperProximityBoundaryM;
+    protected boolean mAutoDeriveParams;
+    protected byte[] mBaseKey;
+    protected byte[] mSyncData;
+    protected int mSyncCounter;
+    protected byte[] mDerivedData;
+    protected int mDerivedDataLength;
+
+    protected RangingSession(
+            @NonNull Context context,
+            @NonNull SessionParameters sessionParameters,
+            int derivedDataLength) {
+        Preconditions.checkNotNull(context);
+        Preconditions.checkNotNull(sessionParameters);
+        mContext = context;
+        mSessionInfo =
+                new SessionInfo.Builder()
+                        .setDeviceId(sessionParameters.getDeviceId())
+                        .setRangingMethod(sessionParameters.getRangingMethod())
+                        .build();
+        mLowerProximityBoundaryM = sessionParameters.getLowerProximityBoundaryM();
+        mUpperProximityBoundaryM = sessionParameters.getUpperProximityBoundaryM();
+        mAutoDeriveParams = sessionParameters.getAutoDeriveParams();
+        Log.i(
+                TAG,
+                "Creating a new RangingSession {info = "
+                        + mSessionInfo
+                        + ", autoDeriveParams = "
+                        + mAutoDeriveParams
+                        + "}");
+        if (mAutoDeriveParams) {
+            Preconditions.checkArgument(
+                    derivedDataLength > 0, "derivedDataLength must be greater than 0");
+            mDerivedDataLength = derivedDataLength;
+            resetBaseKey(sessionParameters.getBaseKey());
+            resetSyncData(sessionParameters.getSyncData());
+        }
     }
 
     /**
@@ -75,6 +157,8 @@
      * @param rangingParameters parameters to start the ranging.
      * @param executor Executor to run the rangingCallback.
      * @param rangingCallback callback to notify of ranging events.
+     * @throws NullPointerException if params are null.
+     * @throws IllegalArgumentException if rangingParameters is invalid.
      */
     public abstract void start(
             @NonNull RangingParameters rangingParameters,
@@ -94,8 +178,22 @@
      * the secure connection between the devices is lost.
      *
      * @param baseKey new baseKey must be 16 or 32 bytes.
+     * @throws NullPointerException if baseKey is null.
+     * @throws IllegalArgumentException if baseKey has invalid length.
      */
-    public void resetBaseKey(byte[] baseKey) {}
+    public void resetBaseKey(@NonNull byte[] baseKey) {
+        if (!mAutoDeriveParams) {
+            Log.w(TAG, "autoDeriveParams is disabled, new baseKey is ignored.");
+            return;
+        }
+        Preconditions.checkNotNull(baseKey);
+        if (baseKey.length != 16 && baseKey.length != 32) {
+            throw new IllegalArgumentException("Invalid baseKey length: " + baseKey.length);
+        }
+        mBaseKey = baseKey;
+        updateDerivedData();
+        Log.i(TAG, "resetBaseKey");
+    }
 
     /**
      * Resets the synchronization by giving a new syncData used for ranging parameters derivation.
@@ -105,6 +203,52 @@
      * manner.
      *
      * @param syncData new syncData must be 16 bytes.
+     * @throws NullPointerException if baseKey is null.
+     * @throws IllegalArgumentException if syncData has invalid length.
      */
-    public void resetSyncData(byte[] syncData) {}
+    public void resetSyncData(@NonNull byte[] syncData) {
+        if (!mAutoDeriveParams) {
+            Log.w(TAG, "autoDeriveParams is disabled, new syncData is ignored.");
+            return;
+        }
+        Preconditions.checkNotNull(syncData);
+        if (syncData.length != 16) {
+            throw new IllegalArgumentException("Invalid syncData length: " + syncData.length);
+        }
+        mSyncData = syncData;
+        mSyncCounter = 0;
+        updateDerivedData();
+        Log.i(TAG, "resetSyncData");
+    }
+
+    /** Recomputes mDerivedData using the latest mBaseKey, mSyncData, and mSyncCounter. */
+    protected boolean updateDerivedData() {
+        if (!mAutoDeriveParams) {
+            Log.w(TAG, "autoDeriveParams is disabled, updateDerivedData is skipped.");
+            return false;
+        }
+        if (mBaseKey == null
+                || mBaseKey.length == 0
+                || mSyncData == null
+                || mSyncData.length == 0) {
+            Log.w(TAG, "updateDerivedData: Missing baseKey/syncData");
+            return false;
+        }
+        byte[] hashedSyncData =
+                Hashing.sha256()
+                        .newHasher()
+                        .putBytes(mSyncData)
+                        .putInt(mSyncCounter)
+                        .hash()
+                        .asBytes();
+        byte[] newDerivedData = Crypto.computeHkdf(mBaseKey, hashedSyncData, mDerivedDataLength);
+        if (newDerivedData == null) {
+            Log.w(TAG, "updateDerivedData: computeHkdf failed");
+            return false;
+        }
+        mDerivedData = newDerivedData;
+        mSyncCounter++;
+        Log.i(TAG, "updateDerivedData");
+        return true;
+    }
 }
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionInfo.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionInfo.java
index 5e4fc48..0ec640c 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionInfo.java
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionInfo.java
@@ -41,6 +41,16 @@
         mRangingMethod = rangingMethod;
     }
 
+    @Override
+    public String toString() {
+        return "SessionInfo { "
+                + "DeviceId = "
+                + mDeviceId
+                + "RangingMethod = "
+                + mRangingMethod
+                + " }";
+    }
+
     /** Builder class for {@link SessionInfo}. */
     public static final class Builder {
         private String mDeviceId = "";
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java
index 33c3203..2f71244 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java
@@ -19,9 +19,14 @@
 
 import android.annotation.NonNull;
 
+import androidx.annotation.IntDef;
+
 import com.android.internal.util.Preconditions;
 import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * The set of parameters to create a ranging session.
  *
@@ -31,9 +36,29 @@
  */
 public class SessionParameters {
 
+    /** Ranging device role. */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            value = {
+                DEVICE_ROLE_UNKNOWN,
+                DEVICE_ROLE_INITIATOR,
+                DEVICE_ROLE_RESPONDER,
+            })
+    public @interface DeviceRole {}
+
+    /** Unknown device role. */
+    public static final int DEVICE_ROLE_UNKNOWN = 0x0;
+
+    /** Device that initiates the ranging. */
+    public static final int DEVICE_ROLE_INITIATOR = 0x1;
+
+    /** Device that responds to ranging. */
+    public static final int DEVICE_ROLE_RESPONDER = 0x2;
+
     /* Required parameters */
     private final String mDeviceId;
     @RangingMethod private final int mRangingMethod;
+    @DeviceRole private final int mDeviceRole;
 
     /* Optional parameters */
     private final float mLowerProximityBoundaryM;
@@ -51,6 +76,11 @@
         return mRangingMethod;
     }
 
+    @DeviceRole
+    public int getDeviceRole() {
+        return mDeviceRole;
+    }
+
     public float getLowerProximityBoundaryM() {
         return mLowerProximityBoundaryM;
     }
@@ -74,6 +104,7 @@
     private SessionParameters(
             String deviceId,
             @RangingMethod int rangingMethod,
+            @DeviceRole int deviceRole,
             float lowerProximityBoundaryM,
             float upperProximityBoundaryM,
             boolean autoDeriveParams,
@@ -81,6 +112,7 @@
             byte[] syncData) {
         mDeviceId = deviceId;
         mRangingMethod = rangingMethod;
+        mDeviceRole = deviceRole;
         mLowerProximityBoundaryM = lowerProximityBoundaryM;
         mUpperProximityBoundaryM = upperProximityBoundaryM;
         mAutoDeriveParams = autoDeriveParams;
@@ -92,6 +124,7 @@
     public static final class Builder {
         private String mDeviceId = new String("");
         @RangingMethod private int mRangingMethod = RANGING_METHOD_UNKNOWN;
+        @DeviceRole private int mDeviceRole = DEVICE_ROLE_UNKNOWN;
         private float mLowerProximityBoundaryM;
         private float mUpperProximityBoundaryM;
         private boolean mAutoDeriveParams = false;
@@ -120,6 +153,12 @@
             return this;
         }
 
+        /** Sets the {@link DeviceRole} to be used for the {@link RangingSession}. */
+        public Builder setDeviceRole(@DeviceRole int deviceRole) {
+            mDeviceRole = deviceRole;
+            return this;
+        }
+
         /**
          * Sets the lower proximity boundary in meters, must be greater than or equals to zero.
          *
@@ -191,6 +230,7 @@
             Preconditions.checkArgument(!mDeviceId.isEmpty(), "deviceId must not be empty.");
             Preconditions.checkArgument(
                     mRangingMethod != RANGING_METHOD_UNKNOWN, "Unknown rangingMethod");
+            Preconditions.checkArgument(mDeviceRole != DEVICE_ROLE_UNKNOWN, "Unknown deviceRole");
             Preconditions.checkArgument(
                     mLowerProximityBoundaryM >= 0,
                     "Negative lowerProximityBoundaryM: " + mLowerProximityBoundaryM);
@@ -212,6 +252,7 @@
             return new SessionParameters(
                     mDeviceId,
                     mRangingMethod,
+                    mDeviceRole,
                     mLowerProximityBoundaryM,
                     mUpperProximityBoundaryM,
                     mAutoDeriveParams,
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/UwbRangingSession.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/UwbRangingSession.java
new file mode 100644
index 0000000..2015b66
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/UwbRangingSession.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.remoteauth.ranging;
+
+import android.annotation.NonNull;
+import android.content.Context;
+
+import androidx.core.uwb.backend.impl.internal.UwbServiceImpl;
+
+import java.util.concurrent.Executor;
+
+/** UWB (ultra wide-band) implementation of {@link RangingSession}. */
+public class UwbRangingSession extends RangingSession {
+    private static final int DERIVED_DATA_LENGTH = 1;
+
+    public UwbRangingSession(
+            @NonNull Context context,
+            @NonNull SessionParameters sessionParameters,
+            @NonNull UwbServiceImpl uwbServiceImpl) {
+        super(context, sessionParameters, DERIVED_DATA_LENGTH);
+    }
+
+    @Override
+    public void start(
+            @NonNull RangingParameters rangingParameters,
+            @NonNull Executor executor,
+            @NonNull RangingCallback rangingCallback) {}
+
+    @Override
+    public void stop() {}
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/util/Crypto.java b/remoteauth/service/java/com/android/server/remoteauth/util/Crypto.java
new file mode 100644
index 0000000..573597f
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/util/Crypto.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.remoteauth.util;
+
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/** Utility class of cryptographic functions. */
+public final class Crypto {
+    private static final String TAG = "Crypto";
+    private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
+
+    /**
+     * A HAMC sha256 based HKDF algorithm to pseudo randomly hash data and salt into a byte array of
+     * given size.
+     *
+     * @param ikm the input keying material.
+     * @param salt A possibly non-secret random value.
+     * @param size The length of the generated pseudorandom string in bytes. The maximal size is
+     *     255.DigestSize, where DigestSize is the size of the underlying HMAC.
+     * @return size pseudorandom bytes, null if failed.
+     */
+    // Based on
+    // google3/third_party/tink/java_src/src/main/java/com/google/crypto/tink/subtle/Hkdf.java
+    @Nullable
+    public static byte[] computeHkdf(byte[] ikm, byte[] salt, int size) {
+        Mac mac;
+        try {
+            mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
+        } catch (NoSuchAlgorithmException e) {
+            Log.w(TAG, "HMAC_SHA256_ALGORITHM is not supported.", e);
+            return null;
+        }
+
+        if (size > 255 * mac.getMacLength()) {
+            Log.w(TAG, "Size too large. " + size + " > " + 255 * mac.getMacLength());
+            return null;
+        }
+
+        if (ikm == null || ikm.length == 0) {
+            Log.w(TAG, "Ikm cannot be empty.");
+            return null;
+        }
+
+        if (salt == null || salt.length == 0) {
+            Log.w(TAG, "Salt cannot be empty.");
+            return null;
+        }
+
+        try {
+            mac.init(new SecretKeySpec(salt, HMAC_SHA256_ALGORITHM));
+        } catch (InvalidKeyException e) {
+            Log.w(TAG, "Invalid key.", e);
+            return null;
+        }
+
+        byte[] prk = mac.doFinal(ikm);
+        byte[] result = new byte[size];
+        try {
+            mac.init(new SecretKeySpec(prk, HMAC_SHA256_ALGORITHM));
+        } catch (InvalidKeyException e) {
+            Log.w(TAG, "Invalid key.", e);
+            return null;
+        }
+
+        byte[] digest = new byte[0];
+        int ctr = 1;
+        int pos = 0;
+        while (true) {
+            mac.update(digest);
+            mac.update((byte) ctr);
+            digest = mac.doFinal();
+            if (pos + digest.length < size) {
+                System.arraycopy(digest, 0, result, pos, digest.length);
+                pos += digest.length;
+                ctr++;
+            } else {
+                System.arraycopy(digest, 0, result, pos, size - pos);
+                break;
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
index f3cf3ea..1967c1a 100644
--- a/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
+++ b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
@@ -15,7 +15,6 @@
 use crate::jnames::{SEND_REQUEST_MNAME, SEND_REQUEST_MSIG};
 use crate::unique_jvm;
 use anyhow::anyhow;
-use async_trait::async_trait;
 use jni::errors::Error as JNIError;
 use jni::objects::{GlobalRef, JMethodID, JObject, JValue};
 use jni::signature::TypeSignature;
@@ -26,11 +25,7 @@
 use std::collections::HashMap;
 use std::sync::{
     atomic::{AtomicI64, Ordering},
-    Arc,
-};
-use tokio::{
-    runtime::Runtime,
-    sync::{mpsc, Mutex},
+    Arc, Mutex,
 };
 
 /// Macro capturing the name of the function calling this macro.
@@ -65,7 +60,7 @@
     HANDLE_RN.fetch_add(1, Ordering::SeqCst)
 }
 
-async fn insert_platform_handle(handle: i64, item: Arc<Mutex<JavaPlatform>>) {
+fn insert_platform_handle(handle: i64, item: Arc<Mutex<JavaPlatform>>) {
     if 0 == handle {
         // Init once
         logger::init(
@@ -75,14 +70,22 @@
                 .with_filter("trace,jni=info"),
         );
     }
-    HANDLE_MAPPING.lock().await.insert(handle, Arc::clone(&item));
+    HANDLE_MAPPING.lock().unwrap().insert(handle, Arc::clone(&item));
 }
 
-#[async_trait]
+pub trait ResponseCallback {
+    fn on_response(&mut self, response: Vec<u8>);
+    fn on_error(&mut self, error_code: i32);
+}
+
 pub trait Platform {
     /// Send a binary message to the remote with the given connection id and return the response.
-    async fn send_request(&mut self, connection_id: i32, request: &[u8])
-        -> anyhow::Result<Vec<u8>>;
+    fn send_request(
+        &mut self,
+        connection_id: i32,
+        request: &[u8],
+        callback: Box<dyn ResponseCallback + Send>,
+    ) -> anyhow::Result<()>;
 }
 //////////////////////////////////
 
@@ -91,13 +94,13 @@
     vm: &'static Arc<JavaVM>,
     platform_native_obj: GlobalRef,
     send_request_method_id: JMethodID,
-    map_futures: Mutex<HashMap<i64, mpsc::Sender<Vec<u8>>>>,
+    map_futures: Mutex<HashMap<i64, Box<dyn ResponseCallback + Send>>>,
     atomic_handle: AtomicI64,
 }
 
 impl JavaPlatform {
     // Method to create JavaPlatform
-    pub async fn create(
+    pub fn create(
         java_platform_native: JObject<'_>,
     ) -> Result<Arc<Mutex<impl Platform>>, JNIError> {
         let platform_handle = generate_platform_handle();
@@ -106,7 +109,7 @@
             unique_jvm::get_static_ref().ok_or(JNIError::InvalidCtorReturn)?,
             java_platform_native,
         )?));
-        insert_platform_handle(platform_handle, Arc::clone(&platform)).await;
+        insert_platform_handle(platform_handle, Arc::clone(&platform));
         Ok(Arc::clone(&platform))
     }
 
@@ -133,19 +136,18 @@
     }
 }
 
-#[async_trait]
 impl Platform for JavaPlatform {
-    async fn send_request(
+    fn send_request(
         &mut self,
         connection_id: i32,
         request: &[u8],
-    ) -> anyhow::Result<Vec<u8>> {
+        callback: Box<dyn ResponseCallback + Send>,
+    ) -> anyhow::Result<()> {
         let type_signature = TypeSignature::from_str(SEND_REQUEST_MSIG)
             .map_err(|e| anyhow!("JNI: Invalid type signature: {:?}", e))?;
 
-        let (tx, mut rx) = mpsc::channel(1);
         let response_handle = self.atomic_handle.fetch_add(1, Ordering::SeqCst);
-        self.map_futures.lock().await.insert(response_handle, tx);
+        self.map_futures.lock().unwrap().insert(response_handle, callback);
         self.vm
             .attach_current_thread()
             .and_then(|env| {
@@ -172,21 +174,20 @@
                 ))
             })
             .map_err(|e| anyhow!("JNI: Failed to attach current thread: {:?}", e))?;
-
-        rx.recv().await.ok_or(anyhow!("{} failed in awaiting for a result", function_name!()))
+        Ok(())
     }
 }
 
 impl JavaPlatform {
-    async fn on_send_request_success(&mut self, response: &[u8], response_handle: i64) {
+    fn on_send_request_success(&mut self, response: &[u8], response_handle: i64) {
         info!(
             "{} completed successfully {}:{}",
             function_name!(),
             self.platform_handle,
             response_handle
         );
-        if let Some(tx) = self.map_futures.lock().await.remove(&response_handle) {
-            let _ = tx.send(response.to_vec()).await;
+        if let Some(mut callback) = self.map_futures.lock().unwrap().remove(&response_handle) {
+            callback.on_response(response.to_vec());
         } else {
             error!(
                 "Failed to find TX for {} and {}:{}",
@@ -197,7 +198,7 @@
         }
     }
 
-    async fn on_send_request_error(&self, error_code: i32, response_handle: i64) {
+    fn on_send_request_error(&self, error_code: i32, response_handle: i64) {
         error!(
             "{} completed with error {} {}:{}",
             function_name!(),
@@ -205,12 +206,11 @@
             self.platform_handle,
             response_handle
         );
-        if let Some(tx) = self.map_futures.lock().await.remove(&response_handle) {
-            // `rx.recv()` ends with `Err`
-            drop(tx);
+        if let Some(mut callback) = self.map_futures.lock().unwrap().remove(&response_handle) {
+            callback.on_error(error_code);
         } else {
             error!(
-                "Failed to find TX for {} and {}:{}",
+                "Failed to find callback for {} and {}:{}",
                 function_name!(),
                 self.platform_handle,
                 response_handle
@@ -228,25 +228,20 @@
     response_handle: jlong,
 ) {
     debug!("{}: enter", function_name!());
-    Runtime::new().unwrap().block_on(native_on_send_request_success(
-        env,
-        app_response,
-        platform_handle,
-        response_handle,
-    ));
+    native_on_send_request_success(env, app_response, platform_handle, response_handle);
 }
 
-async fn native_on_send_request_success(
+fn native_on_send_request_success(
     env: JNIEnv<'_>,
     app_response: jbyteArray,
     platform_handle: jlong,
     response_handle: jlong,
 ) {
-    if let Some(platform) = HANDLE_MAPPING.lock().await.get(&platform_handle) {
+    if let Some(platform) = HANDLE_MAPPING.lock().unwrap().get(&platform_handle) {
         let response =
             env.convert_byte_array(app_response).map_err(|_| JNIError::InvalidCtorReturn).unwrap();
-        let mut platform = (*platform).lock().await;
-        platform.on_send_request_success(&response, response_handle).await;
+        let mut platform = (*platform).lock().unwrap();
+        platform.on_send_request_success(&response, response_handle);
     } else {
         let _ = env.throw_new(
             "com/android/server/remoteauth/jni/BadHandleException",
@@ -264,23 +259,18 @@
     response_handle: jlong,
 ) {
     debug!("{}: enter", function_name!());
-    Runtime::new().unwrap().block_on(native_on_send_request_error(
-        env,
-        error_code,
-        platform_handle,
-        response_handle,
-    ));
+    native_on_send_request_error(env, error_code, platform_handle, response_handle);
 }
 
-async fn native_on_send_request_error(
+fn native_on_send_request_error(
     env: JNIEnv<'_>,
     error_code: jint,
     platform_handle: jlong,
     response_handle: jlong,
 ) {
-    if let Some(platform) = HANDLE_MAPPING.lock().await.get(&platform_handle) {
-        let platform = (*platform).lock().await;
-        platform.on_send_request_error(error_code, response_handle).await;
+    if let Some(platform) = HANDLE_MAPPING.lock().unwrap().get(&platform_handle) {
+        let platform = (*platform).lock().unwrap();
+        platform.on_send_request_error(error_code, response_handle);
     } else {
         let _ = env.throw_new(
             "com/android/server/remoteauth/jni/BadHandleException",
diff --git a/remoteauth/tests/unit/Android.bp b/remoteauth/tests/unit/Android.bp
index 4b92d84..629d360 100644
--- a/remoteauth/tests/unit/Android.bp
+++ b/remoteauth/tests/unit/Android.bp
@@ -35,13 +35,20 @@
     static_libs: [
         "androidx.test.ext.junit",
         "androidx.test.rules",
+        "com.uwb.support.generic",
         "framework-remoteauth-static",
         "junit",
         "libprotobuf-java-lite",
+        "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "service-remoteauth-pre-jarjar",
         "truth-prebuilt",
     ],
+    // these are needed for Extended Mockito
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
     test_suites: [
         "general-tests",
         "mts-tethering",
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingCapabilitiesTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingCapabilitiesTest.java
index e6b6e3b..8135b4f 100644
--- a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingCapabilitiesTest.java
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingCapabilitiesTest.java
@@ -28,16 +28,25 @@
 /** Unit test for {@link RangingCapabilities}. */
 @RunWith(AndroidJUnit4.class)
 public class RangingCapabilitiesTest {
+    private static final androidx.core.uwb.backend.impl.internal.RangingCapabilities
+            TEST_UWB_RANGING_CAPABILITIES =
+                    new androidx.core.uwb.backend.impl.internal.RangingCapabilities(
+                            /* supportsDistance= */ true,
+                            /* supportsAzimuthalAngle= */ true,
+                            /* supportsElevationAngle= */ true);
 
     @Test
     public void testBuildingRangingCapabilities_success() {
         final RangingCapabilities rangingCapabilities =
                 new RangingCapabilities.Builder()
                         .addSupportedRangingMethods(RANGING_METHOD_UWB)
+                        .setUwbRangingCapabilities(TEST_UWB_RANGING_CAPABILITIES)
                         .build();
 
         assertEquals(rangingCapabilities.getSupportedRangingMethods().size(), 1);
         assertEquals(
                 (int) rangingCapabilities.getSupportedRangingMethods().get(0), RANGING_METHOD_UWB);
+        assertEquals(
+                rangingCapabilities.getUwbRangingCapabilities(), TEST_UWB_RANGING_CAPABILITIES);
     }
 }
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingManagerTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingManagerTest.java
new file mode 100644
index 0000000..6e343bb
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingManagerTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.remoteauth.ranging;
+
+import static android.content.pm.PackageManager.FEATURE_UWB;
+import static android.uwb.UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE;
+
+import static androidx.core.uwb.backend.impl.internal.RangingCapabilities.FIRA_DEFAULT_SUPPORTED_CONFIG_IDS;
+import static androidx.core.uwb.backend.impl.internal.Utils.CONFIG_PROVISIONED_INDIVIDUAL_MULTICAST_DS_TWR;
+import static androidx.core.uwb.backend.impl.internal.Utils.CONFIG_PROVISIONED_MULTICAST_DS_TWR;
+import static androidx.core.uwb.backend.impl.internal.Utils.CONFIG_PROVISIONED_UNICAST_DS_TWR;
+import static androidx.core.uwb.backend.impl.internal.Utils.CONFIG_PROVISIONED_UNICAST_DS_TWR_NO_AOA;
+
+import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB;
+import static com.android.server.remoteauth.ranging.SessionParameters.DEVICE_ROLE_INITIATOR;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.uwb.UwbManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod;
+import com.android.server.remoteauth.ranging.SessionParameters.DeviceRole;
+
+import com.google.uwb.support.fira.FiraParams;
+import com.google.uwb.support.fira.FiraSpecificationParams;
+import com.google.uwb.support.generic.GenericSpecificationParams;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+
+/** Unit test for {@link RangingManager}. */
+@RunWith(AndroidJUnit4.class)
+public class RangingManagerTest {
+    private static final List<Integer> TEST_UWB_SUPPORTED_CHANNELS = List.of(8, 9);
+    private static final FiraSpecificationParams TEST_FIRA_SPEC =
+            new FiraSpecificationParams.Builder()
+                    .setSupportedChannels(TEST_UWB_SUPPORTED_CHANNELS)
+                    .setStsCapabilities(EnumSet.allOf(FiraParams.StsCapabilityFlag.class))
+                    .build();
+    private static final GenericSpecificationParams TEST_GENERIC_SPEC =
+            new GenericSpecificationParams.Builder()
+                    .setFiraSpecificationParams(TEST_FIRA_SPEC)
+                    .build();
+    private static final String TEST_DEVICE_ID = "test_device_id";
+    @RangingMethod private static final int TEST_RANGING_METHOD = RANGING_METHOD_UWB;
+    @DeviceRole private static final int TEST_DEVICE_ROLE = DEVICE_ROLE_INITIATOR;
+    private static final float TEST_LOWER_PROXIMITY_BOUNDARY_M = 1.0f;
+    private static final float TEST_UPPER_PROXIMITY_BOUNDARY_M = 2.5f;
+    private static final boolean TEST_AUTO_DERIVE_PARAMS = true;
+    private static final byte[] TEST_BASE_KEY =
+            new byte[] {
+                0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
+                0x0e, 0x0f
+            };
+    private static final byte[] TEST_SYNC_DATA =
+            new byte[] {
+                0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+                0x0f, 0x00
+            };
+    private static final SessionParameters TEST_SESSION_PARAMETER =
+            new SessionParameters.Builder()
+                    .setDeviceId(TEST_DEVICE_ID)
+                    .setRangingMethod(TEST_RANGING_METHOD)
+                    .setDeviceRole(TEST_DEVICE_ROLE)
+                    .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+                    .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+                    .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
+                    .setBaseKey(TEST_BASE_KEY)
+                    .setSyncData(TEST_SYNC_DATA)
+                    .build();
+
+    @Mock private Context mContext;
+    @Mock private PackageManager mPackageManager;
+    @Mock private UwbManager mUwbManager;
+
+    private RangingManager mRangingManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getSystemService(UwbManager.class)).thenReturn(mUwbManager);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(false);
+        when(mUwbManager.getAdapterState()).thenReturn(STATE_ENABLED_INACTIVE);
+        when(mUwbManager.getSpecificationInfo()).thenReturn(TEST_GENERIC_SPEC.toBundle());
+    }
+
+    @Test
+    public void testConstruction() {
+        mRangingManager = new RangingManager(mContext);
+        verifyZeroInteractions(mUwbManager);
+    }
+
+    @Test
+    public void testConstruction_withUwbEnabled() {
+        when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(true);
+
+        mRangingManager = new RangingManager(mContext);
+
+        verify(mUwbManager).getAdapterState();
+        verify(mUwbManager).registerAdapterStateCallback(any(), any());
+    }
+
+    @Test
+    public void testShutdown_withUwbEnabled() {
+        when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(true);
+
+        mRangingManager = new RangingManager(mContext);
+        mRangingManager.shutdown();
+
+        verify(mUwbManager).registerAdapterStateCallback(any(), any());
+        verify(mUwbManager).unregisterAdapterStateCallback(any());
+    }
+
+    @Test
+    public void testGetRangingCapabilities() {
+        mRangingManager = new RangingManager(mContext);
+        RangingCapabilities capabilities = mRangingManager.getRangingCapabilities();
+
+        assertEquals(capabilities.getSupportedRangingMethods().size(), 0);
+        assertEquals(capabilities.getUwbRangingCapabilities(), null);
+    }
+
+    @Test
+    public void testGetRangingCapabilities_withUwbEnabled() {
+        when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(true);
+
+        mRangingManager = new RangingManager(mContext);
+        RangingCapabilities capabilities = mRangingManager.getRangingCapabilities();
+
+        List<Integer> supportedConfigIds = new ArrayList<>(FIRA_DEFAULT_SUPPORTED_CONFIG_IDS);
+        supportedConfigIds.add(CONFIG_PROVISIONED_UNICAST_DS_TWR);
+        supportedConfigIds.add(CONFIG_PROVISIONED_MULTICAST_DS_TWR);
+        supportedConfigIds.add(CONFIG_PROVISIONED_UNICAST_DS_TWR_NO_AOA);
+        supportedConfigIds.add(CONFIG_PROVISIONED_INDIVIDUAL_MULTICAST_DS_TWR);
+
+        verify(mUwbManager, times(1)).getSpecificationInfo();
+        assertEquals(capabilities.getSupportedRangingMethods().size(), 1);
+        assertEquals((int) capabilities.getSupportedRangingMethods().get(0), RANGING_METHOD_UWB);
+        androidx.core.uwb.backend.impl.internal.RangingCapabilities uwbCapabilities =
+                capabilities.getUwbRangingCapabilities();
+        assertNotNull(uwbCapabilities);
+        assertArrayEquals(
+                uwbCapabilities.getSupportedChannels().toArray(),
+                TEST_UWB_SUPPORTED_CHANNELS.toArray());
+        assertArrayEquals(
+                uwbCapabilities.getSupportedConfigIds().toArray(), supportedConfigIds.toArray());
+    }
+
+    @Test
+    public void testGetRangingCapabilities_multipleCalls() {
+        when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(true);
+
+        mRangingManager = new RangingManager(mContext);
+        RangingCapabilities capabilities1 = mRangingManager.getRangingCapabilities();
+        RangingCapabilities capabilities2 = mRangingManager.getRangingCapabilities();
+        RangingCapabilities capabilities3 = mRangingManager.getRangingCapabilities();
+
+        verify(mUwbManager, times(1)).getSpecificationInfo();
+        assertEquals(capabilities1, capabilities2);
+        assertEquals(capabilities2, capabilities3);
+    }
+
+    @Test
+    public void testCreateSession_nullSessionParameters() {
+        mRangingManager = new RangingManager(mContext);
+
+        assertThrows(NullPointerException.class, () -> mRangingManager.createSession(null));
+    }
+
+    @Test
+    public void testCreateSession_uwbSessionWithUwbDisabled() {
+        mRangingManager = new RangingManager(mContext);
+
+        assertNull(mRangingManager.createSession(TEST_SESSION_PARAMETER));
+    }
+
+    @Test
+    public void testCreateSession_uwbSession() {
+        when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(true);
+        mRangingManager = new RangingManager(mContext);
+
+        assertNotNull(mRangingManager.createSession(TEST_SESSION_PARAMETER));
+    }
+}
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingSessionTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingSessionTest.java
new file mode 100644
index 0000000..0e547d6
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingSessionTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.remoteauth.ranging;
+
+import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB;
+import static com.android.server.remoteauth.ranging.SessionParameters.DEVICE_ROLE_INITIATOR;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod;
+import com.android.server.remoteauth.ranging.SessionParameters.DeviceRole;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.Executor;
+
+/** Unit test for {@link RangingSession}. */
+@RunWith(AndroidJUnit4.class)
+public class RangingSessionTest {
+
+    private static final String TEST_DEVICE_ID = "test_device_id";
+    @RangingMethod private static final int TEST_RANGING_METHOD = RANGING_METHOD_UWB;
+    @DeviceRole private static final int TEST_DEVICE_ROLE = DEVICE_ROLE_INITIATOR;
+    private static final float TEST_LOWER_PROXIMITY_BOUNDARY_M = 1.0f;
+    private static final float TEST_UPPER_PROXIMITY_BOUNDARY_M = 2.5f;
+    private static final byte[] TEST_BASE_KEY =
+            new byte[] {
+                0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
+                0x0e, 0x0f
+            };
+    private static final byte[] TEST_BASE_KEY2 =
+            new byte[] {
+                0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0,
+                0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7
+            };
+    private static final byte[] TEST_SYNC_DATA =
+            new byte[] {
+                0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+                0x0f, 0x00
+            };
+    private static final byte[] TEST_SYNC_DATA2 =
+            new byte[] {
+                0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+                0x0f, 0x00
+            };
+
+    private static final SessionParameters TEST_SESSION_PARAMETER_WITH_AD =
+            new SessionParameters.Builder()
+                    .setDeviceId(TEST_DEVICE_ID)
+                    .setRangingMethod(TEST_RANGING_METHOD)
+                    .setDeviceRole(TEST_DEVICE_ROLE)
+                    .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+                    .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+                    .setAutoDeriveParams(true)
+                    .setBaseKey(TEST_BASE_KEY)
+                    .setSyncData(TEST_SYNC_DATA)
+                    .build();
+    private static final SessionParameters TEST_SESSION_PARAMETER_WO_AD =
+            new SessionParameters.Builder()
+                    .setDeviceId(TEST_DEVICE_ID)
+                    .setRangingMethod(TEST_RANGING_METHOD)
+                    .setDeviceRole(TEST_DEVICE_ROLE)
+                    .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+                    .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+                    .setAutoDeriveParams(false)
+                    .setBaseKey(TEST_BASE_KEY)
+                    .setSyncData(TEST_SYNC_DATA)
+                    .build();
+    private static final int TEST_DERIVE_DATA_LENGTH = 40;
+
+    /** Wrapper class for testing {@link RangingSession}. */
+    public static class RangingSessionWrapper extends RangingSession {
+        public RangingSessionWrapper(
+                Context context, SessionParameters sessionParameters, int derivedDataLength) {
+            super(context, sessionParameters, derivedDataLength);
+        }
+
+        @Override
+        public void start(
+                RangingParameters rangingParameters,
+                Executor executor,
+                RangingCallback rangingCallback) {}
+
+        @Override
+        public void stop() {}
+
+        @Override
+        public boolean updateDerivedData() {
+            return super.updateDerivedData();
+        }
+
+        public byte[] baseKey() {
+            return mBaseKey;
+        }
+
+        public byte[] syncData() {
+            return mSyncData;
+        }
+
+        public byte[] derivedData() {
+            return mDerivedData;
+        }
+
+        public int syncCounter() {
+            return mSyncCounter;
+        }
+    }
+
+    @Mock private Context mContext;
+
+    private RangingSessionWrapper mRangingSessionWithAD;
+    private RangingSessionWrapper mRangingSessionWithoutAD;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mRangingSessionWithAD =
+                new RangingSessionWrapper(
+                        mContext, TEST_SESSION_PARAMETER_WITH_AD, TEST_DERIVE_DATA_LENGTH);
+        mRangingSessionWithoutAD =
+                new RangingSessionWrapper(mContext, TEST_SESSION_PARAMETER_WO_AD, 0);
+    }
+
+    @Test
+    public void testResetBaseKey_autoDeriveDisabled() {
+        assertNull(mRangingSessionWithoutAD.baseKey());
+        mRangingSessionWithoutAD.resetBaseKey(TEST_BASE_KEY2);
+        assertNull(mRangingSessionWithoutAD.baseKey());
+    }
+
+    @Test
+    public void testResetBaseKey_nullBaseKey() {
+        assertThrows(NullPointerException.class, () -> mRangingSessionWithAD.resetBaseKey(null));
+    }
+
+    @Test
+    public void testResetBaseKey_invalidBaseKey() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mRangingSessionWithAD.resetBaseKey(new byte[] {0x1, 0x2, 0x3, 0x4}));
+    }
+
+    @Test
+    public void testResetBaseKey_success() {
+        mRangingSessionWithAD.resetBaseKey(TEST_BASE_KEY2);
+        assertArrayEquals(mRangingSessionWithAD.baseKey(), TEST_BASE_KEY2);
+        assertEquals(mRangingSessionWithAD.syncCounter(), 2);
+
+        mRangingSessionWithAD.resetBaseKey(TEST_BASE_KEY);
+        assertArrayEquals(mRangingSessionWithAD.baseKey(), TEST_BASE_KEY);
+        assertEquals(mRangingSessionWithAD.syncCounter(), 3);
+    }
+
+    @Test
+    public void testResetSyncData_autoDeriveDisabled() {
+        assertNull(mRangingSessionWithoutAD.syncData());
+        mRangingSessionWithoutAD.resetSyncData(TEST_SYNC_DATA2);
+        assertNull(mRangingSessionWithoutAD.syncData());
+    }
+
+    @Test
+    public void testResetSyncData_nullSyncData() {
+        assertThrows(NullPointerException.class, () -> mRangingSessionWithAD.resetSyncData(null));
+    }
+
+    @Test
+    public void testResetSyncData_invalidSyncData() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mRangingSessionWithAD.resetSyncData(new byte[] {0x1, 0x2, 0x3, 0x4}));
+    }
+
+    @Test
+    public void testResetSyncData_success() {
+        mRangingSessionWithAD.resetSyncData(TEST_SYNC_DATA2);
+        assertArrayEquals(mRangingSessionWithAD.syncData(), TEST_SYNC_DATA2);
+        assertEquals(mRangingSessionWithAD.syncCounter(), 1);
+
+        mRangingSessionWithAD.resetSyncData(TEST_SYNC_DATA);
+        assertArrayEquals(mRangingSessionWithAD.syncData(), TEST_SYNC_DATA);
+        assertEquals(mRangingSessionWithAD.syncCounter(), 1);
+    }
+
+    @Test
+    public void testUpdateDerivedData_autoDeriveDisabled() {
+        assertFalse(mRangingSessionWithoutAD.updateDerivedData());
+        assertEquals(mRangingSessionWithoutAD.syncCounter(), 0);
+    }
+
+    @Test
+    public void testUpdateDerivedData_hkdfFailed() {
+        // Max derivedDataLength is 32*255
+        RangingSessionWrapper rangingSession =
+                new RangingSessionWrapper(
+                        mContext, TEST_SESSION_PARAMETER_WITH_AD, /* derivedDataLength= */ 10000);
+        assertNull(rangingSession.derivedData());
+        assertFalse(rangingSession.updateDerivedData());
+        assertEquals(rangingSession.syncCounter(), 0);
+        assertNull(rangingSession.derivedData());
+    }
+
+    @Test
+    public void testUpdateDerivedData_success() {
+        assertNotNull(mRangingSessionWithAD.derivedData());
+        assertTrue(mRangingSessionWithAD.updateDerivedData());
+        assertEquals(mRangingSessionWithAD.syncCounter(), 2);
+        assertNotNull(mRangingSessionWithAD.derivedData());
+    }
+}
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java
index 357fdf9..522623e 100644
--- a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java
@@ -17,6 +17,7 @@
 package com.android.server.remoteauth.ranging;
 
 import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB;
+import static com.android.server.remoteauth.ranging.SessionParameters.DEVICE_ROLE_INITIATOR;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -25,6 +26,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod;
+import com.android.server.remoteauth.ranging.SessionParameters.DeviceRole;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -35,6 +37,7 @@
 
     private static final String TEST_DEVICE_ID = "test_device_id";
     @RangingMethod private static final int TEST_RANGING_METHOD = RANGING_METHOD_UWB;
+    @DeviceRole private static final int TEST_DEVICE_ROLE = DEVICE_ROLE_INITIATOR;
     private static final float TEST_LOWER_PROXIMITY_BOUNDARY_M = 1.0f;
     private static final float TEST_UPPER_PROXIMITY_BOUNDARY_M = 2.5f;
     private static final boolean TEST_AUTO_DERIVE_PARAMS = true;
@@ -55,6 +58,7 @@
                 new SessionParameters.Builder()
                         .setDeviceId(TEST_DEVICE_ID)
                         .setRangingMethod(TEST_RANGING_METHOD)
+                        .setDeviceRole(TEST_DEVICE_ROLE)
                         .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
                         .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
                         .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
@@ -82,6 +86,7 @@
         final SessionParameters.Builder builder =
                 new SessionParameters.Builder()
                         .setRangingMethod(TEST_RANGING_METHOD)
+                        .setDeviceRole(TEST_DEVICE_ROLE)
                         .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
                         .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
                         .setBaseKey(TEST_BASE_KEY)
@@ -95,6 +100,21 @@
         final SessionParameters.Builder builder =
                 new SessionParameters.Builder()
                         .setDeviceId(TEST_DEVICE_ID)
+                        .setDeviceRole(TEST_DEVICE_ROLE)
+                        .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+                        .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+                        .setBaseKey(TEST_BASE_KEY)
+                        .setSyncData(TEST_SYNC_DATA);
+
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testBuildingSessionParameters_invalidDeviceRole() {
+        final SessionParameters.Builder builder =
+                new SessionParameters.Builder()
+                        .setDeviceId(TEST_DEVICE_ID)
+                        .setRangingMethod(TEST_RANGING_METHOD)
                         .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
                         .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
                         .setBaseKey(TEST_BASE_KEY)
@@ -109,6 +129,7 @@
                 new SessionParameters.Builder()
                         .setDeviceId(TEST_DEVICE_ID)
                         .setRangingMethod(TEST_RANGING_METHOD)
+                        .setDeviceRole(TEST_DEVICE_ROLE)
                         .setLowerProximityBoundaryM(-1.0f)
                         .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
                         .setBaseKey(TEST_BASE_KEY)
@@ -123,6 +144,7 @@
                 new SessionParameters.Builder()
                         .setDeviceId(TEST_DEVICE_ID)
                         .setRangingMethod(TEST_RANGING_METHOD)
+                        .setDeviceRole(TEST_DEVICE_ROLE)
                         .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
                         .setUpperProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M - 0.1f)
                         .setBaseKey(TEST_BASE_KEY)
@@ -138,6 +160,7 @@
                 new SessionParameters.Builder()
                         .setDeviceId(TEST_DEVICE_ID)
                         .setRangingMethod(TEST_RANGING_METHOD)
+                        .setDeviceRole(TEST_DEVICE_ROLE)
                         .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
                         .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
                         .setAutoDeriveParams(autoDeriveParams)
@@ -154,6 +177,7 @@
                 new SessionParameters.Builder()
                         .setDeviceId(TEST_DEVICE_ID)
                         .setRangingMethod(TEST_RANGING_METHOD)
+                        .setDeviceRole(TEST_DEVICE_ROLE)
                         .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
                         .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
                         .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
@@ -168,6 +192,7 @@
                 new SessionParameters.Builder()
                         .setDeviceId(TEST_DEVICE_ID)
                         .setRangingMethod(TEST_RANGING_METHOD)
+                        .setDeviceRole(TEST_DEVICE_ROLE)
                         .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
                         .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
                         .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
@@ -183,6 +208,7 @@
                 new SessionParameters.Builder()
                         .setDeviceId(TEST_DEVICE_ID)
                         .setRangingMethod(TEST_RANGING_METHOD)
+                        .setDeviceRole(TEST_DEVICE_ROLE)
                         .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
                         .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
                         .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
@@ -197,6 +223,7 @@
                 new SessionParameters.Builder()
                         .setDeviceId(TEST_DEVICE_ID)
                         .setRangingMethod(TEST_RANGING_METHOD)
+                        .setDeviceRole(TEST_DEVICE_ROLE)
                         .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
                         .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
                         .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/util/CryptoTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/util/CryptoTest.java
new file mode 100644
index 0000000..eb7a8c5
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/util/CryptoTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.remoteauth.util;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for {@link Crypto}. */
+@RunWith(AndroidJUnit4.class)
+public class CryptoTest {
+    private static final byte[] TEST_IKM =
+            new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
+    private static final byte[] TEST_SALT =
+            new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00};
+    private static final int TEST_SIZE = 40;
+
+    @Test
+    public void testComputeHkdf_exceedMaxSize() {
+        // Max size is 32*255
+        assertNull(Crypto.computeHkdf(TEST_IKM, TEST_SALT, /* size= */ 10000));
+    }
+
+    @Test
+    public void testComputeHkdf_emptySalt() {
+        assertNull(Crypto.computeHkdf(TEST_IKM, new byte[] {}, TEST_SIZE));
+    }
+
+    @Test
+    public void testComputeHkdf_emptyIkm() {
+        assertNull(Crypto.computeHkdf(new byte[] {}, TEST_SALT, TEST_SIZE));
+    }
+
+    @Test
+    public void testComputeHkdf_success() {
+        assertNotNull(Crypto.computeHkdf(TEST_IKM, TEST_SALT, TEST_SIZE));
+    }
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index b89ab1f..3358fd7 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -48,7 +48,7 @@
     @BeforeClassWithInfo
     public static void setUpOnceBase(TestInformation testInfo) throws Exception {
         DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(testInfo.getDevice());
-        String testApk = deviceSdkLevel.isDeviceAtLeastT() ? TEST_APK_NEXT : TEST_APK;
+        String testApk = deviceSdkLevel.isDeviceAtLeastV() ? TEST_APK_NEXT : TEST_APK;
 
         uninstallPackage(testInfo, TEST_PKG, false);
         installPackage(testInfo, testApk);