Merge "[remoteauth] Implement UwbRangingSession" into main
This commit is contained in:
@@ -15,5 +15,67 @@
|
||||
*/
|
||||
package com.android.server.remoteauth.ranging;
|
||||
|
||||
/** The set of parameters to start ranging. */
|
||||
public class RangingParameters {}
|
||||
import androidx.core.uwb.backend.impl.internal.UwbAddress;
|
||||
|
||||
/** The set of parameters to initiate {@link RangingSession#start}. */
|
||||
public class RangingParameters {
|
||||
|
||||
/** Parameters for {@link UwbRangingSession}. */
|
||||
private final UwbAddress mUwbLocalAddress;
|
||||
|
||||
private final androidx.core.uwb.backend.impl.internal.RangingParameters mUwbRangingParameters;
|
||||
|
||||
public UwbAddress getUwbLocalAddress() {
|
||||
return mUwbLocalAddress;
|
||||
}
|
||||
|
||||
public androidx.core.uwb.backend.impl.internal.RangingParameters getUwbRangingParameters() {
|
||||
return mUwbRangingParameters;
|
||||
}
|
||||
|
||||
private RangingParameters(
|
||||
UwbAddress uwbLocalAddress,
|
||||
androidx.core.uwb.backend.impl.internal.RangingParameters uwbRangingParameters) {
|
||||
mUwbLocalAddress = uwbLocalAddress;
|
||||
mUwbRangingParameters = uwbRangingParameters;
|
||||
}
|
||||
|
||||
/** Builder class for {@link RangingParameters}. */
|
||||
public static final class Builder {
|
||||
private UwbAddress mUwbLocalAddress;
|
||||
private androidx.core.uwb.backend.impl.internal.RangingParameters mUwbRangingParameters;
|
||||
|
||||
/**
|
||||
* Sets the uwb local address.
|
||||
*
|
||||
* <p>Only required if {@link SessionParameters#getRangingMethod}=={@link
|
||||
* RANGING_METHOD_UWB} and {@link SessionParameters#getAutoDeriveParams} == false
|
||||
*/
|
||||
public Builder setUwbLocalAddress(UwbAddress uwbLocalAddress) {
|
||||
mUwbLocalAddress = uwbLocalAddress;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the uwb ranging parameters.
|
||||
*
|
||||
* <p>Only required if {@link SessionParameters#getRangingMethod}=={@link
|
||||
* RANGING_METHOD_UWB}.
|
||||
*
|
||||
* <p>If {@link SessionParameters#getAutoDeriveParams} == true, all required uwb parameters
|
||||
* including uwbLocalAddress, complexChannel, peerAddresses, and sessionKeyInfo will be
|
||||
* automatically derived, so unnecessary to provide and the other uwb parameters are
|
||||
* optional.
|
||||
*/
|
||||
public Builder setUwbRangingParameters(
|
||||
androidx.core.uwb.backend.impl.internal.RangingParameters uwbRangingParameters) {
|
||||
mUwbRangingParameters = uwbRangingParameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Builds {@link RangingParameters}. */
|
||||
public RangingParameters build() {
|
||||
return new RangingParameters(mUwbLocalAddress, mUwbRangingParameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ import java.util.concurrent.Executor;
|
||||
* <p>A session can be started and stopped multiple times. After starting, updates ({@link
|
||||
* RangingReport}, {@link RangingError}, etc) will be reported via the provided {@link
|
||||
* RangingCallback}. BaseKey and SyncData are used for auto derivation of supported ranging
|
||||
* parameters, which will be implementation specific.
|
||||
* parameters, which will be implementation specific. All session creation shall only be conducted
|
||||
* via {@link RangingManager#createSession}.
|
||||
*
|
||||
* <p>Ranging method specific implementation shall be implemented in the extended class.
|
||||
*/
|
||||
|
||||
@@ -15,30 +15,219 @@
|
||||
*/
|
||||
package com.android.server.remoteauth.ranging;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import static androidx.core.uwb.backend.impl.internal.RangingDevice.SESSION_ID_UNSET;
|
||||
import static androidx.core.uwb.backend.impl.internal.Utils.STATUS_OK;
|
||||
import static androidx.core.uwb.backend.impl.internal.Utils.SUPPORTED_BPRF_PREAMBLE_INDEX;
|
||||
import static androidx.core.uwb.backend.impl.internal.UwbAddress.SHORT_ADDRESS_LENGTH;
|
||||
|
||||
import static com.android.server.remoteauth.ranging.RangingReport.PROXIMITY_STATE_INSIDE;
|
||||
import static com.android.server.remoteauth.ranging.RangingReport.PROXIMITY_STATE_OUTSIDE;
|
||||
import static com.android.server.remoteauth.ranging.SessionParameters.DEVICE_ROLE_INITIATOR;
|
||||
|
||||
import static com.google.uwb.support.fira.FiraParams.UWB_CHANNEL_9;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.RequiresApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.uwb.backend.impl.internal.RangingController;
|
||||
import androidx.core.uwb.backend.impl.internal.RangingDevice;
|
||||
import androidx.core.uwb.backend.impl.internal.RangingPosition;
|
||||
import androidx.core.uwb.backend.impl.internal.RangingSessionCallback;
|
||||
import androidx.core.uwb.backend.impl.internal.RangingSessionCallback.RangingSuspendedReason;
|
||||
import androidx.core.uwb.backend.impl.internal.UwbAddress;
|
||||
import androidx.core.uwb.backend.impl.internal.UwbComplexChannel;
|
||||
import androidx.core.uwb.backend.impl.internal.UwbDevice;
|
||||
import androidx.core.uwb.backend.impl.internal.UwbServiceImpl;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
/** UWB (ultra wide-band) implementation of {@link RangingSession}. */
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/** UWB (ultra wide-band) specific implementation of {@link RangingSession}. */
|
||||
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
|
||||
public class UwbRangingSession extends RangingSession {
|
||||
private static final int DERIVED_DATA_LENGTH = 1;
|
||||
private static final String TAG = "UwbRangingSession";
|
||||
|
||||
private static final int COMPLEX_CHANNEL_LENGTH = 1;
|
||||
private static final int STS_KEY_LENGTH = 16;
|
||||
private static final int DERIVED_DATA_LENGTH =
|
||||
COMPLEX_CHANNEL_LENGTH + SHORT_ADDRESS_LENGTH + SHORT_ADDRESS_LENGTH + STS_KEY_LENGTH;
|
||||
|
||||
private final UwbServiceImpl mUwbServiceImpl;
|
||||
private final RangingDevice mRangingDevice;
|
||||
|
||||
private Executor mExecutor;
|
||||
private RangingCallback mRangingCallback;
|
||||
|
||||
public UwbRangingSession(
|
||||
@NonNull Context context,
|
||||
@NonNull SessionParameters sessionParameters,
|
||||
@NonNull UwbServiceImpl uwbServiceImpl) {
|
||||
super(context, sessionParameters, DERIVED_DATA_LENGTH);
|
||||
Preconditions.checkNotNull(uwbServiceImpl);
|
||||
mUwbServiceImpl = uwbServiceImpl;
|
||||
if (sessionParameters.getDeviceRole() == DEVICE_ROLE_INITIATOR) {
|
||||
mRangingDevice = (RangingDevice) mUwbServiceImpl.getController(context);
|
||||
} else {
|
||||
mRangingDevice = (RangingDevice) mUwbServiceImpl.getControlee(context);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(
|
||||
@NonNull RangingParameters rangingParameters,
|
||||
@NonNull Executor executor,
|
||||
@NonNull RangingCallback rangingCallback) {}
|
||||
@NonNull RangingCallback rangingCallback) {
|
||||
Preconditions.checkNotNull(rangingParameters, "rangingParameters must not be null");
|
||||
Preconditions.checkNotNull(executor, "executor must not be null");
|
||||
Preconditions.checkNotNull(rangingCallback, "rangingCallback must not be null");
|
||||
|
||||
setUwbRangingParameters(rangingParameters);
|
||||
int status =
|
||||
mRangingDevice.startRanging(
|
||||
convertCallback(rangingCallback, executor),
|
||||
Executors.newSingleThreadExecutor());
|
||||
if (status != STATUS_OK) {
|
||||
Log.w(TAG, String.format("Uwb ranging start failed with status %d", status));
|
||||
executor.execute(
|
||||
() -> rangingCallback.onError(mSessionInfo, RANGING_ERROR_FAILED_TO_START));
|
||||
return;
|
||||
}
|
||||
mExecutor = executor;
|
||||
mRangingCallback = rangingCallback;
|
||||
Log.i(TAG, "start");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {}
|
||||
public void stop() {
|
||||
if (mRangingCallback == null) {
|
||||
Log.w(TAG, String.format("Failed to stop unstarted session"));
|
||||
return;
|
||||
}
|
||||
int status = mRangingDevice.stopRanging();
|
||||
if (status != STATUS_OK) {
|
||||
Log.w(TAG, String.format("Uwb ranging stop failed with status %d", status));
|
||||
mExecutor.execute(
|
||||
() -> mRangingCallback.onError(mSessionInfo, RANGING_ERROR_FAILED_TO_STOP));
|
||||
return;
|
||||
}
|
||||
mRangingCallback = null;
|
||||
Log.i(TAG, "stop");
|
||||
}
|
||||
|
||||
private void setUwbRangingParameters(RangingParameters rangingParameters) {
|
||||
androidx.core.uwb.backend.impl.internal.RangingParameters params =
|
||||
rangingParameters.getUwbRangingParameters();
|
||||
Preconditions.checkNotNull(params, "uwbRangingParameters must not be null");
|
||||
if (mAutoDeriveParams) {
|
||||
Preconditions.checkArgument(mDerivedData.length == DERIVED_DATA_LENGTH);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(mDerivedData);
|
||||
|
||||
byte complexChannelByte = buffer.get();
|
||||
int preambleIndex =
|
||||
SUPPORTED_BPRF_PREAMBLE_INDEX.get(
|
||||
Math.abs(complexChannelByte) % SUPPORTED_BPRF_PREAMBLE_INDEX.size());
|
||||
// Selecting channel 9 since it's the only mandatory channel.
|
||||
UwbComplexChannel complexChannel = new UwbComplexChannel(UWB_CHANNEL_9, preambleIndex);
|
||||
|
||||
byte[] localAddress = new byte[SHORT_ADDRESS_LENGTH];
|
||||
byte[] peerAddress = new byte[SHORT_ADDRESS_LENGTH];
|
||||
if (mRangingDevice instanceof RangingController) {
|
||||
((RangingController) mRangingDevice).setComplexChannel(complexChannel);
|
||||
buffer.get(localAddress);
|
||||
buffer.get(peerAddress);
|
||||
} else {
|
||||
buffer.get(peerAddress);
|
||||
buffer.get(localAddress);
|
||||
}
|
||||
byte[] stsKey = new byte[STS_KEY_LENGTH];
|
||||
buffer.get(stsKey);
|
||||
|
||||
mRangingDevice.setLocalAddress(UwbAddress.fromBytes(localAddress));
|
||||
mRangingDevice.setRangingParameters(
|
||||
new androidx.core.uwb.backend.impl.internal.RangingParameters(
|
||||
params.getUwbConfigId(),
|
||||
SESSION_ID_UNSET,
|
||||
/* subSessionId= */ SESSION_ID_UNSET,
|
||||
stsKey,
|
||||
/* subSessionInfo= */ new byte[] {},
|
||||
complexChannel,
|
||||
List.of(UwbAddress.fromBytes(peerAddress)),
|
||||
params.getRangingUpdateRate(),
|
||||
params.getUwbRangeDataNtfConfig(),
|
||||
params.getSlotDuration(),
|
||||
params.isAoaDisabled()));
|
||||
} else {
|
||||
UwbAddress localAddress = rangingParameters.getUwbLocalAddress();
|
||||
Preconditions.checkNotNull(localAddress, "localAddress must not be null");
|
||||
UwbComplexChannel complexChannel = params.getComplexChannel();
|
||||
Preconditions.checkNotNull(complexChannel, "complexChannel must not be null");
|
||||
mRangingDevice.setLocalAddress(localAddress);
|
||||
if (mRangingDevice instanceof RangingController) {
|
||||
((RangingController) mRangingDevice).setComplexChannel(complexChannel);
|
||||
}
|
||||
mRangingDevice.setRangingParameters(params);
|
||||
}
|
||||
}
|
||||
|
||||
private RangingSessionCallback convertCallback(RangingCallback callback, Executor executor) {
|
||||
return new RangingSessionCallback() {
|
||||
|
||||
@Override
|
||||
public void onRangingInitialized(UwbDevice device) {
|
||||
Log.i(TAG, "onRangingInitialized");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRangingResult(UwbDevice device, RangingPosition position) {
|
||||
float distanceM = position.getDistance().getValue();
|
||||
int proximityState =
|
||||
(mLowerProximityBoundaryM <= distanceM
|
||||
&& distanceM <= mUpperProximityBoundaryM)
|
||||
? PROXIMITY_STATE_INSIDE
|
||||
: PROXIMITY_STATE_OUTSIDE;
|
||||
position.getDistance().getValue();
|
||||
RangingReport rangingReport =
|
||||
new RangingReport.Builder()
|
||||
.setDistanceM(distanceM)
|
||||
.setProximityState(proximityState)
|
||||
.build();
|
||||
executor.execute(() -> callback.onRangingReport(mSessionInfo, rangingReport));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRangingSuspended(UwbDevice device, @RangingSuspendedReason int reason) {
|
||||
executor.execute(() -> callback.onError(mSessionInfo, convertError(reason)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@RangingError
|
||||
private static int convertError(@RangingSuspendedReason int reason) {
|
||||
if (reason == RangingSessionCallback.REASON_WRONG_PARAMETERS) {
|
||||
return RANGING_ERROR_INVALID_PARAMETERS;
|
||||
}
|
||||
if (reason == RangingSessionCallback.REASON_STOP_RANGING_CALLED) {
|
||||
return RANGING_ERROR_STOPPED_BY_REQUEST;
|
||||
}
|
||||
if (reason == RangingSessionCallback.REASON_STOPPED_BY_PEER) {
|
||||
return RANGING_ERROR_STOPPED_BY_PEER;
|
||||
}
|
||||
if (reason == RangingSessionCallback.REASON_FAILED_TO_START) {
|
||||
return RANGING_ERROR_FAILED_TO_START;
|
||||
}
|
||||
if (reason == RangingSessionCallback.REASON_SYSTEM_POLICY) {
|
||||
return RANGING_ERROR_SYSTEM_ERROR;
|
||||
}
|
||||
if (reason == RangingSessionCallback.REASON_MAX_RANGING_ROUND_RETRY_REACHED) {
|
||||
return RANGING_ERROR_SYSTEM_TIMEOUT;
|
||||
}
|
||||
return RANGING_ERROR_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 androidx.core.uwb.backend.impl.internal.RangingDevice.SESSION_ID_UNSET;
|
||||
import static androidx.core.uwb.backend.impl.internal.Utils.CONFIG_PROVISIONED_UNICAST_DS_TWR;
|
||||
import static androidx.core.uwb.backend.impl.internal.Utils.DURATION_1_MS;
|
||||
import static androidx.core.uwb.backend.impl.internal.Utils.NORMAL;
|
||||
|
||||
import static com.google.uwb.support.fira.FiraParams.UWB_CHANNEL_9;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import androidx.core.uwb.backend.impl.internal.UwbAddress;
|
||||
import androidx.core.uwb.backend.impl.internal.UwbComplexChannel;
|
||||
import androidx.core.uwb.backend.impl.internal.UwbRangeDataNtfConfig;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Unit test for {@link RangingParameters}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class RangingParametersTest {
|
||||
|
||||
private static final UwbAddress TEST_UWB_LOCAL_ADDRESS =
|
||||
UwbAddress.fromBytes(new byte[] {0x00, 0x01});
|
||||
private static final androidx.core.uwb.backend.impl.internal.RangingParameters
|
||||
TEST_UWB_RANGING_PARAMETERS =
|
||||
new androidx.core.uwb.backend.impl.internal.RangingParameters(
|
||||
CONFIG_PROVISIONED_UNICAST_DS_TWR,
|
||||
/* sessionId= */ SESSION_ID_UNSET,
|
||||
/* subSessionId= */ SESSION_ID_UNSET,
|
||||
/* SessionInfo= */ new byte[] {},
|
||||
/* subSessionInfo= */ new byte[] {},
|
||||
new UwbComplexChannel(UWB_CHANNEL_9, /* preambleIndex= */ 9),
|
||||
List.of(UwbAddress.fromBytes(new byte[] {0x00, 0x02})),
|
||||
/* rangingUpdateRate= */ NORMAL,
|
||||
new UwbRangeDataNtfConfig.Builder().build(),
|
||||
/* slotDuration= */ DURATION_1_MS,
|
||||
/* isAoaDisabled= */ false);
|
||||
|
||||
@Test
|
||||
public void testBuildingRangingParameters_success() {
|
||||
final RangingParameters rangingParameters =
|
||||
new RangingParameters.Builder()
|
||||
.setUwbLocalAddress(TEST_UWB_LOCAL_ADDRESS)
|
||||
.setUwbRangingParameters(TEST_UWB_RANGING_PARAMETERS)
|
||||
.build();
|
||||
|
||||
assertEquals(rangingParameters.getUwbLocalAddress(), TEST_UWB_LOCAL_ADDRESS);
|
||||
assertEquals(rangingParameters.getUwbRangingParameters(), TEST_UWB_RANGING_PARAMETERS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
* 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 androidx.core.uwb.backend.impl.internal.RangingDevice.SESSION_ID_UNSET;
|
||||
import static androidx.core.uwb.backend.impl.internal.RangingMeasurement.CONFIDENCE_HIGH;
|
||||
import static androidx.core.uwb.backend.impl.internal.Utils.CONFIG_PROVISIONED_UNICAST_DS_TWR;
|
||||
import static androidx.core.uwb.backend.impl.internal.Utils.DURATION_1_MS;
|
||||
import static androidx.core.uwb.backend.impl.internal.Utils.NORMAL;
|
||||
import static androidx.core.uwb.backend.impl.internal.Utils.STATUS_ERROR;
|
||||
import static androidx.core.uwb.backend.impl.internal.Utils.STATUS_OK;
|
||||
|
||||
import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB;
|
||||
import static com.android.server.remoteauth.ranging.RangingReport.PROXIMITY_STATE_INSIDE;
|
||||
import static com.android.server.remoteauth.ranging.RangingSession.RANGING_ERROR_FAILED_TO_START;
|
||||
import static com.android.server.remoteauth.ranging.RangingSession.RANGING_ERROR_FAILED_TO_STOP;
|
||||
import static com.android.server.remoteauth.ranging.SessionParameters.DEVICE_ROLE_INITIATOR;
|
||||
import static com.android.server.remoteauth.ranging.SessionParameters.DEVICE_ROLE_RESPONDER;
|
||||
|
||||
import static com.google.uwb.support.fira.FiraParams.UWB_CHANNEL_9;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
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 androidx.core.uwb.backend.impl.internal.RangingControlee;
|
||||
import androidx.core.uwb.backend.impl.internal.RangingController;
|
||||
import androidx.core.uwb.backend.impl.internal.RangingMeasurement;
|
||||
import androidx.core.uwb.backend.impl.internal.RangingPosition;
|
||||
import androidx.core.uwb.backend.impl.internal.RangingSessionCallback;
|
||||
import androidx.core.uwb.backend.impl.internal.UwbAddress;
|
||||
import androidx.core.uwb.backend.impl.internal.UwbComplexChannel;
|
||||
import androidx.core.uwb.backend.impl.internal.UwbDevice;
|
||||
import androidx.core.uwb.backend.impl.internal.UwbRangeDataNtfConfig;
|
||||
import androidx.core.uwb.backend.impl.internal.UwbServiceImpl;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod;
|
||||
import com.android.server.remoteauth.ranging.RangingSession.RangingCallback;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/** Unit test for {@link UwbRangingSession}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class UwbRangingSessionTest {
|
||||
|
||||
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
|
||||
};
|
||||
private static final SessionParameters TEST_SESSION_PARAMETER_INITIATOR =
|
||||
new SessionParameters.Builder()
|
||||
.setDeviceId(TEST_DEVICE_ID)
|
||||
.setRangingMethod(TEST_RANGING_METHOD)
|
||||
.setDeviceRole(DEVICE_ROLE_INITIATOR)
|
||||
.setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
|
||||
.setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
|
||||
.build();
|
||||
private static final SessionParameters TEST_SESSION_PARAMETER_RESPONDER =
|
||||
new SessionParameters.Builder()
|
||||
.setDeviceId(TEST_DEVICE_ID)
|
||||
.setRangingMethod(TEST_RANGING_METHOD)
|
||||
.setDeviceRole(DEVICE_ROLE_RESPONDER)
|
||||
.setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
|
||||
.setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
|
||||
.build();
|
||||
private static final SessionParameters TEST_SESSION_PARAMETER_INITIATOR_W_AD =
|
||||
new SessionParameters.Builder()
|
||||
.setDeviceId(TEST_DEVICE_ID)
|
||||
.setRangingMethod(TEST_RANGING_METHOD)
|
||||
.setDeviceRole(DEVICE_ROLE_INITIATOR)
|
||||
.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();
|
||||
private static final UwbAddress TEST_UWB_LOCAL_ADDRESS =
|
||||
UwbAddress.fromBytes(new byte[] {0x00, 0x01});
|
||||
private static final UwbAddress TEST_UWB_PEER_ADDRESS =
|
||||
UwbAddress.fromBytes(new byte[] {0x00, 0x02});
|
||||
private static final UwbComplexChannel TEST_UWB_COMPLEX_CHANNEL =
|
||||
new UwbComplexChannel(UWB_CHANNEL_9, /* preambleIndex= */ 9);
|
||||
private static final androidx.core.uwb.backend.impl.internal.RangingParameters
|
||||
TEST_UWB_RANGING_PARAMETERS =
|
||||
new androidx.core.uwb.backend.impl.internal.RangingParameters(
|
||||
CONFIG_PROVISIONED_UNICAST_DS_TWR,
|
||||
/* sessionId= */ SESSION_ID_UNSET,
|
||||
/* subSessionId= */ SESSION_ID_UNSET,
|
||||
/* SessionInfo= */ new byte[] {},
|
||||
/* subSessionInfo= */ new byte[] {},
|
||||
TEST_UWB_COMPLEX_CHANNEL,
|
||||
List.of(TEST_UWB_PEER_ADDRESS),
|
||||
NORMAL,
|
||||
new UwbRangeDataNtfConfig.Builder().build(),
|
||||
DURATION_1_MS,
|
||||
/* isAoaDisabled= */ false);
|
||||
private static final RangingParameters TEST_RANGING_PARAMETERS =
|
||||
new RangingParameters.Builder()
|
||||
.setUwbLocalAddress(TEST_UWB_LOCAL_ADDRESS)
|
||||
.setUwbRangingParameters(TEST_UWB_RANGING_PARAMETERS)
|
||||
.build();
|
||||
private static final UwbAddress TEST_DERIVED_UWB_LOCAL_ADDRESS =
|
||||
UwbAddress.fromBytes(new byte[] {0x4C, (byte) 0xB4});
|
||||
private static final UwbAddress TEST_DERIVED_UWB_PEER_ADDRESS =
|
||||
UwbAddress.fromBytes(new byte[] {(byte) 0xAE, 0x2E});
|
||||
private static final UwbComplexChannel TEST_DERIVED_UWB_COMPLEX_CHANNEL =
|
||||
new UwbComplexChannel(UWB_CHANNEL_9, /* preambleIndex= */ 12);
|
||||
private static final byte[] TEST_DERIVED_STS_KEY =
|
||||
new byte[] {
|
||||
0x76,
|
||||
(byte) 0xD7,
|
||||
(byte) 0xB6,
|
||||
0x1A,
|
||||
(byte) 0x8D,
|
||||
0x29,
|
||||
0x1A,
|
||||
0x52,
|
||||
(byte) 0xBB,
|
||||
(byte) 0xBF,
|
||||
(byte) 0xE6,
|
||||
0x28,
|
||||
(byte) 0xAD,
|
||||
0x44,
|
||||
(byte) 0xFB,
|
||||
0x2E
|
||||
};
|
||||
|
||||
private static final UwbDevice TEST_UWB_DEVICE =
|
||||
UwbDevice.createForAddress(TEST_UWB_PEER_ADDRESS.toBytes());
|
||||
private static final float TEST_DISTANCE = 1.5f;
|
||||
private static final RangingMeasurement TEST_RANGING_MEASUREMENT =
|
||||
new RangingMeasurement(
|
||||
/* confidence= */ CONFIDENCE_HIGH,
|
||||
/* value= */ TEST_DISTANCE,
|
||||
/* valid= */ true);
|
||||
private static final RangingPosition TEST_RANGING_POSITION =
|
||||
new RangingPosition(
|
||||
TEST_RANGING_MEASUREMENT,
|
||||
/* azimuth= */ null,
|
||||
/* elevation= */ null,
|
||||
/* dlTdoaMeasurement= */ null,
|
||||
/* elapsedRealtimeNanos= */ 0,
|
||||
/* rssi= */ 0);
|
||||
|
||||
@Mock private Context mContext;
|
||||
@Mock private UwbServiceImpl mUwbServiceImpl;
|
||||
@Mock private RangingController mRangingController;
|
||||
@Mock private RangingControlee mRangingControlee;
|
||||
@Mock private RangingCallback mRangingCallback;
|
||||
@Mock private Executor mCallbackExecutor;
|
||||
|
||||
private UwbRangingSession mUwbRangingSession;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
when(mUwbServiceImpl.getController(mContext)).thenReturn(mRangingController);
|
||||
when(mUwbServiceImpl.getControlee(mContext)).thenReturn(mRangingControlee);
|
||||
when(mRangingController.startRanging(any(), any())).thenReturn(STATUS_OK);
|
||||
when(mRangingControlee.startRanging(any(), any())).thenReturn(STATUS_OK);
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
Runnable t = invocation.getArgument(0);
|
||||
t.run();
|
||||
return true;
|
||||
})
|
||||
.when(mCallbackExecutor)
|
||||
.execute(any(Runnable.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstruction_nullArgument() {
|
||||
assertThrows(
|
||||
NullPointerException.class,
|
||||
() ->
|
||||
new UwbRangingSession(
|
||||
null, TEST_SESSION_PARAMETER_INITIATOR, mUwbServiceImpl));
|
||||
assertThrows(
|
||||
NullPointerException.class,
|
||||
() -> new UwbRangingSession(mContext, null, mUwbServiceImpl));
|
||||
assertThrows(
|
||||
NullPointerException.class,
|
||||
() -> new UwbRangingSession(mContext, TEST_SESSION_PARAMETER_INITIATOR, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstruction_initiatorSuccess() {
|
||||
mUwbRangingSession =
|
||||
new UwbRangingSession(mContext, TEST_SESSION_PARAMETER_INITIATOR, mUwbServiceImpl);
|
||||
verify(mUwbServiceImpl, times(1)).getController(mContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstruction_responderSuccess() {
|
||||
mUwbRangingSession =
|
||||
new UwbRangingSession(mContext, TEST_SESSION_PARAMETER_RESPONDER, mUwbServiceImpl);
|
||||
verify(mUwbServiceImpl, times(1)).getControlee(mContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStart_nullArgument() {
|
||||
mUwbRangingSession =
|
||||
new UwbRangingSession(mContext, TEST_SESSION_PARAMETER_INITIATOR, mUwbServiceImpl);
|
||||
|
||||
assertThrows(
|
||||
NullPointerException.class,
|
||||
() -> mUwbRangingSession.start(TEST_RANGING_PARAMETERS, mCallbackExecutor, null));
|
||||
assertThrows(
|
||||
NullPointerException.class,
|
||||
() -> mUwbRangingSession.start(null, mCallbackExecutor, mRangingCallback));
|
||||
assertThrows(
|
||||
NullPointerException.class,
|
||||
() -> mUwbRangingSession.start(TEST_RANGING_PARAMETERS, null, mRangingCallback));
|
||||
assertThrows(
|
||||
NullPointerException.class,
|
||||
() ->
|
||||
mUwbRangingSession.start(
|
||||
new RangingParameters.Builder().build(),
|
||||
mCallbackExecutor,
|
||||
mRangingCallback));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStart_initiatorWithoutADFailed() {
|
||||
when(mRangingController.startRanging(any(), any())).thenReturn(STATUS_ERROR);
|
||||
|
||||
mUwbRangingSession =
|
||||
new UwbRangingSession(mContext, TEST_SESSION_PARAMETER_INITIATOR, mUwbServiceImpl);
|
||||
mUwbRangingSession.start(TEST_RANGING_PARAMETERS, mCallbackExecutor, mRangingCallback);
|
||||
|
||||
verify(mRangingController, times(1)).setComplexChannel(TEST_UWB_COMPLEX_CHANNEL);
|
||||
verify(mRangingController, times(1)).setLocalAddress(TEST_UWB_LOCAL_ADDRESS);
|
||||
verify(mRangingController, times(1)).setRangingParameters(TEST_UWB_RANGING_PARAMETERS);
|
||||
verify(mRangingController, times(1)).startRanging(any(), any());
|
||||
ArgumentCaptor<SessionInfo> captor = ArgumentCaptor.forClass(SessionInfo.class);
|
||||
verify(mRangingCallback, times(1))
|
||||
.onError(captor.capture(), eq(RANGING_ERROR_FAILED_TO_START));
|
||||
assertEquals(captor.getValue().getDeviceId(), TEST_DEVICE_ID);
|
||||
}
|
||||
|
||||
private void testRangingCallback() {
|
||||
Answer startRangingResponse =
|
||||
new Answer() {
|
||||
public Object answer(InvocationOnMock invocation) {
|
||||
Object[] args = invocation.getArguments();
|
||||
RangingSessionCallback cb = (RangingSessionCallback) args[0];
|
||||
cb.onRangingInitialized(TEST_UWB_DEVICE);
|
||||
cb.onRangingResult(TEST_UWB_DEVICE, TEST_RANGING_POSITION);
|
||||
return STATUS_OK;
|
||||
}
|
||||
};
|
||||
doAnswer(startRangingResponse)
|
||||
.when(mRangingController)
|
||||
.startRanging(any(RangingSessionCallback.class), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStart_initiatorWithADSucceed() {
|
||||
testRangingCallback();
|
||||
mUwbRangingSession =
|
||||
new UwbRangingSession(
|
||||
mContext, TEST_SESSION_PARAMETER_INITIATOR_W_AD, mUwbServiceImpl);
|
||||
mUwbRangingSession.start(TEST_RANGING_PARAMETERS, mCallbackExecutor, mRangingCallback);
|
||||
|
||||
verify(mRangingController, times(1)).setComplexChannel(TEST_DERIVED_UWB_COMPLEX_CHANNEL);
|
||||
verify(mRangingController, times(1)).setLocalAddress(TEST_DERIVED_UWB_LOCAL_ADDRESS);
|
||||
ArgumentCaptor<androidx.core.uwb.backend.impl.internal.RangingParameters> captor =
|
||||
ArgumentCaptor.forClass(
|
||||
androidx.core.uwb.backend.impl.internal.RangingParameters.class);
|
||||
verify(mRangingController, times(1)).setRangingParameters(captor.capture());
|
||||
assertEquals(
|
||||
captor.getValue().getUwbConfigId(), TEST_UWB_RANGING_PARAMETERS.getUwbConfigId());
|
||||
assertEquals(captor.getValue().getSessionId(), SESSION_ID_UNSET);
|
||||
assertEquals(captor.getValue().getSubSessionId(), SESSION_ID_UNSET);
|
||||
assertArrayEquals(captor.getValue().getSessionKeyInfo(), TEST_DERIVED_STS_KEY);
|
||||
assertArrayEquals(captor.getValue().getSubSessionKeyInfo(), new byte[] {});
|
||||
assertEquals(captor.getValue().getComplexChannel(), TEST_DERIVED_UWB_COMPLEX_CHANNEL);
|
||||
assertEquals(captor.getValue().getPeerAddresses().get(0), TEST_DERIVED_UWB_PEER_ADDRESS);
|
||||
assertEquals(
|
||||
captor.getValue().getRangingUpdateRate(),
|
||||
TEST_UWB_RANGING_PARAMETERS.getRangingUpdateRate());
|
||||
assertEquals(
|
||||
captor.getValue().getUwbRangeDataNtfConfig(),
|
||||
TEST_UWB_RANGING_PARAMETERS.getUwbRangeDataNtfConfig());
|
||||
assertEquals(
|
||||
captor.getValue().getSlotDuration(), TEST_UWB_RANGING_PARAMETERS.getSlotDuration());
|
||||
assertEquals(
|
||||
captor.getValue().isAoaDisabled(), TEST_UWB_RANGING_PARAMETERS.isAoaDisabled());
|
||||
verify(mRangingController, times(1)).startRanging(any(), any());
|
||||
ArgumentCaptor<SessionInfo> captor2 = ArgumentCaptor.forClass(SessionInfo.class);
|
||||
ArgumentCaptor<RangingReport> captor3 = ArgumentCaptor.forClass(RangingReport.class);
|
||||
verify(mRangingCallback, times(1)).onRangingReport(captor2.capture(), captor3.capture());
|
||||
assertEquals(captor2.getValue().getDeviceId(), TEST_DEVICE_ID);
|
||||
RangingReport rangingReport = captor3.getValue();
|
||||
assertEquals(rangingReport.getDistanceM(), TEST_DISTANCE, 0.0f);
|
||||
assertEquals(rangingReport.getProximityState(), PROXIMITY_STATE_INSIDE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStop_sessionNotStarted() {
|
||||
when(mRangingController.stopRanging()).thenReturn(STATUS_ERROR);
|
||||
|
||||
mUwbRangingSession =
|
||||
new UwbRangingSession(mContext, TEST_SESSION_PARAMETER_INITIATOR, mUwbServiceImpl);
|
||||
mUwbRangingSession.stop();
|
||||
|
||||
verifyZeroInteractions(mRangingController);
|
||||
verifyZeroInteractions(mRangingCallback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStop_failed() {
|
||||
when(mRangingController.stopRanging()).thenReturn(STATUS_ERROR);
|
||||
|
||||
mUwbRangingSession =
|
||||
new UwbRangingSession(mContext, TEST_SESSION_PARAMETER_INITIATOR, mUwbServiceImpl);
|
||||
mUwbRangingSession.start(TEST_RANGING_PARAMETERS, mCallbackExecutor, mRangingCallback);
|
||||
mUwbRangingSession.stop();
|
||||
|
||||
verify(mRangingController, times(1)).setComplexChannel(any());
|
||||
verify(mRangingController, times(1)).setLocalAddress(any());
|
||||
verify(mRangingController, times(1)).setRangingParameters(any());
|
||||
verify(mRangingController, times(1)).startRanging(any(), any());
|
||||
verify(mRangingController, times(1)).stopRanging();
|
||||
ArgumentCaptor<SessionInfo> captor = ArgumentCaptor.forClass(SessionInfo.class);
|
||||
verify(mRangingCallback, times(1))
|
||||
.onError(captor.capture(), eq(RANGING_ERROR_FAILED_TO_STOP));
|
||||
assertEquals(captor.getValue().getDeviceId(), TEST_DEVICE_ID);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user