Merge "[remoteauth] Implement UwbRangingSession" into main
This commit is contained in:
@@ -15,5 +15,67 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.server.remoteauth.ranging;
|
package com.android.server.remoteauth.ranging;
|
||||||
|
|
||||||
/** The set of parameters to start ranging. */
|
import androidx.core.uwb.backend.impl.internal.UwbAddress;
|
||||||
public class RangingParameters {}
|
|
||||||
|
/** 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
|
* <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
|
* RangingReport}, {@link RangingError}, etc) will be reported via the provided {@link
|
||||||
* RangingCallback}. BaseKey and SyncData are used for auto derivation of supported ranging
|
* 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.
|
* <p>Ranging method specific implementation shall be implemented in the extended class.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,30 +15,219 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.server.remoteauth.ranging;
|
package com.android.server.remoteauth.ranging;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import static androidx.core.uwb.backend.impl.internal.RangingDevice.SESSION_ID_UNSET;
|
||||||
import android.content.Context;
|
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 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 {
|
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(
|
public UwbRangingSession(
|
||||||
@NonNull Context context,
|
@NonNull Context context,
|
||||||
@NonNull SessionParameters sessionParameters,
|
@NonNull SessionParameters sessionParameters,
|
||||||
@NonNull UwbServiceImpl uwbServiceImpl) {
|
@NonNull UwbServiceImpl uwbServiceImpl) {
|
||||||
super(context, sessionParameters, DERIVED_DATA_LENGTH);
|
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
|
@Override
|
||||||
public void start(
|
public void start(
|
||||||
@NonNull RangingParameters rangingParameters,
|
@NonNull RangingParameters rangingParameters,
|
||||||
@NonNull Executor executor,
|
@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
|
@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