diff --git a/remoteauth/service/Android.bp b/remoteauth/service/Android.bp index 737fa32624..3486d8c4b0 100644 --- a/remoteauth/service/Android.bp +++ b/remoteauth/service/Android.bp @@ -39,6 +39,7 @@ java_library { "framework-statsd", ], static_libs: [ + "guava", "libprotobuf-java-lite", "fast-pair-lite-protos", "modules-utils-build", 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 d61ca378f2..2b5efff641 100644 --- a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingCapabilities.java +++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingCapabilities.java @@ -17,10 +17,18 @@ package com.android.server.remoteauth.ranging; import androidx.annotation.IntDef; +import com.google.common.collect.ImmutableList; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + /** The ranging capabilities of the device. */ public class RangingCapabilities { /** Possible ranging methods */ + @Retention(RetentionPolicy.SOURCE) @IntDef( value = { RANGING_METHOD_UNKNOWN, @@ -33,4 +41,35 @@ public class RangingCapabilities { /** Ultra-wideband ranging. */ public static final int RANGING_METHOD_UWB = 0x1; + + private final ImmutableList mSupportedRangingMethods; + + /** + * Gets the list of supported ranging methods of the device. + * + * @return list of {@link RangingMethod} + */ + public ImmutableList getSupportedRangingMethods() { + return mSupportedRangingMethods; + } + + private RangingCapabilities(List supportedRangingMethods) { + mSupportedRangingMethods = ImmutableList.copyOf(supportedRangingMethods); + } + + /** Builder class for {@link RangingCapabilities}. */ + public static final class Builder { + private List mSupportedRangingMethods = new ArrayList<>(); + + /** Adds a supported {@link RangingMethod} */ + public Builder addSupportedRangingMethods(@RangingMethod int rangingMethod) { + mSupportedRangingMethods.add(rangingMethod); + return this; + } + + /** Builds {@link RangingCapabilities}. */ + public RangingCapabilities build() { + return new RangingCapabilities(mSupportedRangingMethods); + } + } } diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingManager.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingManager.java new file mode 100644 index 0000000000..989b5edbb4 --- /dev/null +++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingManager.java @@ -0,0 +1,50 @@ +/* + * 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.content.Context; + +/** + * Manages the creation of generic device to device ranging session and obtaining device's ranging + * capabilities. + * + *

Out-of-band channel for ranging capabilities/parameters exchange is assumed being handled + * outside of this class. + */ +public class RangingManager { + + public RangingManager(Context context) {} + + /** + * Gets the {@link RangingCapabilities} of this device. + * + * @return RangingCapabilities. + */ + public RangingCapabilities getRangingCapabilities() { + return null; + } + + /** + * Creates a {@link RangingSession} based on the given {@link SessionParameters}, which shall be + * provided based on the rangingCapabilities of the device. + * + * @param sessionParameters parameters used to setup the session. + * @return the created RangingSession. + */ + public RangingSession createSession(SessionParameters sessionParameters) { + return null; + } +} 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 d42ef51ab3..9ef6bda4fc 100644 --- a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java +++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java @@ -19,6 +19,8 @@ import android.annotation.NonNull; import androidx.annotation.IntDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; /** @@ -35,6 +37,7 @@ import java.util.concurrent.Executor; public abstract class RangingSession { /** Types of ranging error. */ + @Retention(RetentionPolicy.SOURCE) @IntDef( value = { RANGING_ERROR_UNKNOWN, diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java new file mode 100644 index 0000000000..33c3203ab6 --- /dev/null +++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java @@ -0,0 +1,222 @@ +/* + * 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_UNKNOWN; + +import android.annotation.NonNull; + +import com.android.internal.util.Preconditions; +import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod; + +/** + * The set of parameters to create a ranging session. + * + *

Required parameters must be provided, else {@link Builder} will throw an exception. The + * optional parameters only need to be provided if the functionality is necessary to the session, + * see the setter functions of the {@link Builder} for detailed info of each parameter. + */ +public class SessionParameters { + + /* Required parameters */ + private final String mDeviceId; + @RangingMethod private final int mRangingMethod; + + /* Optional parameters */ + private final float mLowerProximityBoundaryM; + private final float mUpperProximityBoundaryM; + private final boolean mAutoDeriveParams; + private final byte[] mBaseKey; + private final byte[] mSyncData; + + public String getDeviceId() { + return mDeviceId; + } + + @RangingMethod + public int getRangingMethod() { + return mRangingMethod; + } + + public float getLowerProximityBoundaryM() { + return mLowerProximityBoundaryM; + } + + public float getUpperProximityBoundaryM() { + return mUpperProximityBoundaryM; + } + + public boolean getAutoDeriveParams() { + return mAutoDeriveParams; + } + + public byte[] getBaseKey() { + return mBaseKey; + } + + public byte[] getSyncData() { + return mSyncData; + } + + private SessionParameters( + String deviceId, + @RangingMethod int rangingMethod, + float lowerProximityBoundaryM, + float upperProximityBoundaryM, + boolean autoDeriveParams, + byte[] baseKey, + byte[] syncData) { + mDeviceId = deviceId; + mRangingMethod = rangingMethod; + mLowerProximityBoundaryM = lowerProximityBoundaryM; + mUpperProximityBoundaryM = upperProximityBoundaryM; + mAutoDeriveParams = autoDeriveParams; + mBaseKey = baseKey; + mSyncData = syncData; + } + + /** Builder class for {@link SessionParameters}. */ + public static final class Builder { + private String mDeviceId = new String(""); + @RangingMethod private int mRangingMethod = RANGING_METHOD_UNKNOWN; + private float mLowerProximityBoundaryM; + private float mUpperProximityBoundaryM; + private boolean mAutoDeriveParams = false; + private byte[] mBaseKey = new byte[] {}; + private byte[] mSyncData = new byte[] {}; + + /** + * Sets the device id. + * + *

This is used as the identity included in the {@link SessionInfo} for all {@link + * RangingCallback}s. + */ + public Builder setDeviceId(@NonNull String deviceId) { + mDeviceId = deviceId; + return this; + } + + /** + * Sets the {@link RangingMethod} to be used for the {@link RangingSession}. + * + *

Note: The ranging method should be ones in the list return by {@link + * RangingCapabilities#getSupportedRangingMethods}; + */ + public Builder setRangingMethod(@RangingMethod int rangingMethod) { + mRangingMethod = rangingMethod; + return this; + } + + /** + * Sets the lower proximity boundary in meters, must be greater than or equals to zero. + * + *

This value is used to compute the {@link ProximityState} = {@link + * PROXIMITY_STATE_INSIDE} if lowerProximityBoundaryM <= proximity <= + * upperProximityBoundaryM, else {@link PROXIMITY_STATE_OUTSIDE}. + */ + public Builder setLowerProximityBoundaryM(float lowerProximityBoundaryM) { + mLowerProximityBoundaryM = lowerProximityBoundaryM; + return this; + } + + /** + * Sets the upper proximity boundary in meters, must be greater than or equals to + * lowerProximityBoundaryM. + * + *

This value is used to compute the {@link ProximityState} = {@link + * PROXIMITY_STATE_INSIDE} if lowerProximityBoundaryM <= proximity <= + * upperProximityBoundaryM, else {@link PROXIMITY_STATE_OUTSIDE}. + */ + public Builder setUpperProximityBoundaryM(float upperProximityBoundaryM) { + mUpperProximityBoundaryM = upperProximityBoundaryM; + return this; + } + + /** + * Sets the auto derive ranging parameters flag. Defaults to false. + * + *

This enables the {@link RangingSession} to automatically derive all possible {@link + * RangingParameters} at each {@link RangingSession#start} using the provided {@link + * #setBaseKey} and {@link #setSyncData}, which shall be securely shared between the ranging + * devices out of band. + */ + public Builder setAutoDeriveParams(boolean autoDeriveParams) { + mAutoDeriveParams = autoDeriveParams; + return this; + } + + /** + * Sets the base key. Only required if {@link #setAutoDeriveParams} is set to true. + * + * @param baseKey baseKey must be 16 or 32 bytes. + * @throws NullPointerException if baseKey is null + */ + public Builder setBaseKey(@NonNull byte[] baseKey) { + Preconditions.checkNotNull(baseKey); + mBaseKey = baseKey; + return this; + } + + /** + * Sets the sync data. Only required if {@link #setAutoDeriveParams} is set to true. + * + * @param syncData syncData must be 16 bytes. + * @throws NullPointerException if syncData is null + */ + public Builder setSyncData(@NonNull byte[] syncData) { + Preconditions.checkNotNull(syncData); + mSyncData = syncData; + return this; + } + + /** + * Builds {@link SessionParameters}. + * + * @throws IllegalArgumentException if any parameter is invalid. + */ + public SessionParameters build() { + Preconditions.checkArgument(!mDeviceId.isEmpty(), "deviceId must not be empty."); + Preconditions.checkArgument( + mRangingMethod != RANGING_METHOD_UNKNOWN, "Unknown rangingMethod"); + Preconditions.checkArgument( + mLowerProximityBoundaryM >= 0, + "Negative lowerProximityBoundaryM: " + mLowerProximityBoundaryM); + Preconditions.checkArgument( + mLowerProximityBoundaryM <= mUpperProximityBoundaryM, + "lowerProximityBoundaryM is greater than upperProximityBoundaryM: " + + mLowerProximityBoundaryM + + " > " + + mUpperProximityBoundaryM); + // If mAutoDeriveParams is false, mBaseKey and mSyncData will not be used. + if (mAutoDeriveParams) { + Preconditions.checkArgument( + mBaseKey.length == 16 || mBaseKey.length == 32, + "Invalid baseKey length: " + mBaseKey.length); + Preconditions.checkArgument( + mSyncData.length == 16, "Invalid syncData length: " + mSyncData.length); + } + + return new SessionParameters( + mDeviceId, + mRangingMethod, + mLowerProximityBoundaryM, + mUpperProximityBoundaryM, + mAutoDeriveParams, + mBaseKey, + mSyncData); + } + } +} 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 new file mode 100644 index 0000000000..e6b6e3b9a0 --- /dev/null +++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingCapabilitiesTest.java @@ -0,0 +1,43 @@ +/* + * 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 org.junit.Assert.assertEquals; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit test for {@link RangingCapabilities}. */ +@RunWith(AndroidJUnit4.class) +public class RangingCapabilitiesTest { + + @Test + public void testBuildingRangingCapabilities_success() { + final RangingCapabilities rangingCapabilities = + new RangingCapabilities.Builder() + .addSupportedRangingMethods(RANGING_METHOD_UWB) + .build(); + + assertEquals(rangingCapabilities.getSupportedRangingMethods().size(), 1); + assertEquals( + (int) rangingCapabilities.getSupportedRangingMethods().get(0), RANGING_METHOD_UWB); + } +} 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 new file mode 100644 index 0000000000..357fdf976f --- /dev/null +++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java @@ -0,0 +1,208 @@ +/* + * 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 org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link SessionParameters}. */ +@RunWith(AndroidJUnit4.class) +public class SessionParametersTest { + + private static final String TEST_DEVICE_ID = "test_device_id"; + @RangingMethod private static final int TEST_RANGING_METHOD = RANGING_METHOD_UWB; + 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 + }; + + @Test + public void testBuildingSessionParameters_success() { + final SessionParameters sessionParameters = + new SessionParameters.Builder() + .setDeviceId(TEST_DEVICE_ID) + .setRangingMethod(TEST_RANGING_METHOD) + .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(); + + assertEquals(sessionParameters.getDeviceId(), TEST_DEVICE_ID); + assertEquals(sessionParameters.getRangingMethod(), TEST_RANGING_METHOD); + assertEquals( + sessionParameters.getLowerProximityBoundaryM(), + TEST_LOWER_PROXIMITY_BOUNDARY_M, + 0.0f); + assertEquals( + sessionParameters.getUpperProximityBoundaryM(), + TEST_UPPER_PROXIMITY_BOUNDARY_M, + 0.0f); + assertEquals(sessionParameters.getAutoDeriveParams(), TEST_AUTO_DERIVE_PARAMS); + assertArrayEquals(sessionParameters.getBaseKey(), TEST_BASE_KEY); + assertArrayEquals(sessionParameters.getSyncData(), TEST_SYNC_DATA); + } + + @Test + public void testBuildingSessionParameters_invalidDeviceId() { + final SessionParameters.Builder builder = + new SessionParameters.Builder() + .setRangingMethod(TEST_RANGING_METHOD) + .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_invalidRangingMethod() { + final SessionParameters.Builder builder = + new SessionParameters.Builder() + .setDeviceId(TEST_DEVICE_ID) + .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_invalidLowerProximityBoundaryM() { + final SessionParameters.Builder builder = + new SessionParameters.Builder() + .setDeviceId(TEST_DEVICE_ID) + .setRangingMethod(TEST_RANGING_METHOD) + .setLowerProximityBoundaryM(-1.0f) + .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M) + .setBaseKey(TEST_BASE_KEY) + .setSyncData(TEST_SYNC_DATA); + + assertThrows(IllegalArgumentException.class, () -> builder.build()); + } + + @Test + public void testBuildingSessionParameters_invalidUpperProximityBoundaryM() { + final SessionParameters.Builder builder = + new SessionParameters.Builder() + .setDeviceId(TEST_DEVICE_ID) + .setRangingMethod(TEST_RANGING_METHOD) + .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M) + .setUpperProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M - 0.1f) + .setBaseKey(TEST_BASE_KEY) + .setSyncData(TEST_SYNC_DATA); + + assertThrows(IllegalArgumentException.class, () -> builder.build()); + } + + @Test + public void testBuildingSessionParameters_disableAutoDeriveParams() { + final boolean autoDeriveParams = false; + final SessionParameters sessionParameters = + new SessionParameters.Builder() + .setDeviceId(TEST_DEVICE_ID) + .setRangingMethod(TEST_RANGING_METHOD) + .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M) + .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M) + .setAutoDeriveParams(autoDeriveParams) + .build(); + + assertEquals(sessionParameters.getAutoDeriveParams(), autoDeriveParams); + assertArrayEquals(sessionParameters.getBaseKey(), new byte[] {}); + assertArrayEquals(sessionParameters.getSyncData(), new byte[] {}); + } + + @Test + public void testBuildingSessionParameters_emptyBaseKey() { + 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) + .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS) + .setSyncData(TEST_SYNC_DATA); + + assertThrows(IllegalArgumentException.class, () -> builder.build()); + } + + @Test + public void testBuildingSessionParameters_invalidBaseKey() { + 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) + .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS) + .setBaseKey(new byte[] {0x00, 0x01, 0x02, 0x13}) + .setSyncData(TEST_SYNC_DATA); + + assertThrows(IllegalArgumentException.class, () -> builder.build()); + } + + @Test + public void testBuildingSessionParameters_emptySyncData() { + 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) + .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS) + .setBaseKey(TEST_BASE_KEY); + + assertThrows(IllegalArgumentException.class, () -> builder.build()); + } + + @Test + public void testBuildingSessionParameters_invalidSyncData() { + 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) + .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS) + .setBaseKey(TEST_BASE_KEY) + .setSyncData(new byte[] {0x00, 0x01, 0x02, 0x13}); + + assertThrows(IllegalArgumentException.class, () -> builder.build()); + } +}