diff options
127 files changed, 8255 insertions, 2340 deletions
diff --git a/androidx_backend/Android.bp b/androidx_backend/Android.bp index 4c56514f..c0c77c7d 100644 --- a/androidx_backend/Android.bp +++ b/androidx_backend/Android.bp @@ -25,7 +25,8 @@ aidl_interface { ], backend: { java: { - platform_apis: true, + enabled: true, + min_sdk_version: "31", }, }, visibility: ["//visibility:public"], diff --git a/androidx_backend/AndroidManifest.xml b/androidx_backend/AndroidManifest.xml index 4e3180b3..0ef9b1fb 100644 --- a/androidx_backend/AndroidManifest.xml +++ b/androidx_backend/AndroidManifest.xml @@ -7,7 +7,6 @@ <uses-permission android:name="android.permission.UWB_RANGING"/> <application - android:name="androidx.core.uwb.backend.service" android:persistent="true" android:directBootAware="true" android:defaultToDeviceProtectedStorage="true"> diff --git a/androidx_backend/src/androidx/core/uwb/backend/impl/UwbClient.java b/androidx_backend/src/androidx/core/uwb/backend/impl/UwbClient.java new file mode 100644 index 00000000..3ec83a9a --- /dev/null +++ b/androidx_backend/src/androidx/core/uwb/backend/impl/UwbClient.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2022 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 androidx.core.uwb.backend.impl; + +import android.os.RemoteException; + +import androidx.core.uwb.backend.IRangingSessionCallback; +import androidx.core.uwb.backend.IUwbClient; +import androidx.core.uwb.backend.RangingCapabilities; +import androidx.core.uwb.backend.RangingMeasurement; +import androidx.core.uwb.backend.RangingParameters; +import androidx.core.uwb.backend.UwbAddress; +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.UwbDevice; +import androidx.core.uwb.backend.impl.internal.UwbServiceImpl; + +import java.util.ArrayList; +import java.util.List; + +/** Implements operations of IUwbClient. */ +public abstract class UwbClient extends IUwbClient.Stub { + protected final UwbServiceImpl mUwbService; + protected final RangingDevice mDevice; + + protected UwbClient(RangingDevice device, UwbServiceImpl uwbService) { + mDevice = device; + mUwbService = uwbService; + } + + @Override + public boolean isAvailable() throws RemoteException { + return mUwbService.isAvailable(); + } + + @Override + public RangingCapabilities getRangingCapabilities() throws RemoteException { + androidx.core.uwb.backend.impl.internal.RangingCapabilities cap = + mUwbService.getRangingCapabilities(); + RangingCapabilities rangingCapabilities = new RangingCapabilities(); + rangingCapabilities.supportsAzimuthalAngle = cap.supportsAzimuthalAngle(); + rangingCapabilities.supportsDistance = cap.supportsDistance(); + rangingCapabilities.supportsElevationAngle = cap.supportsElevationAngle(); + return rangingCapabilities; + } + + @Override + public UwbAddress getLocalAddress() throws RemoteException { + androidx.core.uwb.backend.impl.internal.UwbAddress address = mDevice.getLocalAddress(); + UwbAddress uwbAddress = new UwbAddress(); + uwbAddress.address = address.toBytes(); + return uwbAddress; + } + + protected void setRangingParameters(RangingParameters parameters) throws RemoteException { + androidx.core.uwb.backend.impl.internal.UwbComplexChannel channel = + new androidx.core.uwb.backend.impl.internal.UwbComplexChannel( + parameters.complexChannel.channel, parameters.complexChannel.preambleIndex); + List<androidx.core.uwb.backend.impl.internal.UwbAddress> addresses = new ArrayList<>(); + for (androidx.core.uwb.backend.UwbDevice device : parameters.peerDevices) { + addresses.add(androidx.core.uwb.backend.impl.internal.UwbAddress + .fromBytes(device.address.address)); + } + mDevice.setRangingParameters( + new androidx.core.uwb.backend.impl.internal.RangingParameters( + parameters.uwbConfigId, parameters.sessionId, parameters.sessionKeyInfo, + channel, addresses, parameters.rangingUpdateRate)); + } + + protected androidx.core.uwb.backend.impl.internal.RangingSessionCallback convertCallback( + IRangingSessionCallback callback) { + return new RangingSessionCallback() { + @Override + public void onRangingInitialized(UwbDevice device) { + androidx.core.uwb.backend.UwbDevice backendDevice = + new androidx.core.uwb.backend.UwbDevice(); + backendDevice.address = new UwbAddress(); + backendDevice.address.address = device.getAddress().toBytes(); + try { + callback.onRangingInitialized(backendDevice); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @Override + public void onRangingResult(UwbDevice device, RangingPosition position) { + androidx.core.uwb.backend.UwbDevice backendDevice = + new androidx.core.uwb.backend.UwbDevice(); + backendDevice.address = new UwbAddress(); + backendDevice.address.address = device.getAddress().toBytes(); + androidx.core.uwb.backend.RangingPosition rangingPosition = + new androidx.core.uwb.backend.RangingPosition(); + RangingMeasurement distance = new RangingMeasurement(); + distance.confidence = position.getDistance().getConfidence(); + distance.value = position.getDistance().getValue(); + rangingPosition.distance = distance; + if (position.getAzimuth() != null) { + RangingMeasurement azimuth = new RangingMeasurement(); + azimuth.confidence = position.getAzimuth().getConfidence(); + azimuth.value = position.getAzimuth().getValue(); + rangingPosition.azimuth = azimuth; + } + if (position.getElevation() != null) { + RangingMeasurement elevation = new RangingMeasurement(); + elevation.confidence = position.getElevation().getConfidence(); + elevation.value = position.getElevation().getValue(); + rangingPosition.elevation = elevation; + } + rangingPosition.elapsedRealtimeNanos = position.getElapsedRealtimeNanos(); + try { + callback.onRangingResult(backendDevice, rangingPosition); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @Override + public void onRangingSuspended(UwbDevice device, int reason) { + androidx.core.uwb.backend.UwbDevice backendDevice = + new androidx.core.uwb.backend.UwbDevice(); + backendDevice.address = new UwbAddress(); + backendDevice.address.address = device.getAddress().toBytes(); + try { + callback.onRangingSuspended(backendDevice, reason); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + }; + } +} diff --git a/androidx_backend/src/androidx/core/uwb/backend/impl/UwbControleeClient.java b/androidx_backend/src/androidx/core/uwb/backend/impl/UwbControleeClient.java new file mode 100644 index 00000000..e76e1faf --- /dev/null +++ b/androidx_backend/src/androidx/core/uwb/backend/impl/UwbControleeClient.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2022 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 androidx.core.uwb.backend.impl; + +import static androidx.core.uwb.backend.impl.internal.Utils.STATUS_OK; +import static androidx.core.uwb.backend.impl.internal.Utils.TAG; + +import android.os.RemoteException; +import android.util.Log; + +import androidx.core.uwb.backend.IRangingSessionCallback; +import androidx.core.uwb.backend.RangingParameters; +import androidx.core.uwb.backend.UwbAddress; +import androidx.core.uwb.backend.UwbComplexChannel; +import androidx.core.uwb.backend.impl.internal.RangingControlee; +import androidx.core.uwb.backend.impl.internal.UwbServiceImpl; + +import java.util.concurrent.Executors; + +/** This class implement the operations of a uwb controlee. */ +public class UwbControleeClient extends UwbClient { + + public UwbControleeClient(RangingControlee rangingControlee, UwbServiceImpl uwbService) { + super(rangingControlee, uwbService); + } + + @Override + public UwbComplexChannel getComplexChannel() throws RemoteException { + return null; + } + + @Override + public void startRanging(RangingParameters parameters, IRangingSessionCallback callback) + throws RemoteException { + setRangingParameters(parameters); + int status = mDevice.startRanging( + convertCallback(callback), Executors.newSingleThreadExecutor()); + if (status != STATUS_OK) { + Log.w(TAG, String.format("Ranging start failed with status %d", status)); + } + } + + @Override + public void stopRanging(IRangingSessionCallback callback) throws RemoteException { + int status = mDevice.stopRanging(); + if (status != STATUS_OK) { + Log.w(TAG, String.format("Ranging stop failed with status %d", status)); + } + } + + @Override + public void addControlee(UwbAddress address) throws RemoteException { + } + + @Override + public void removeControlee(UwbAddress address) throws RemoteException { + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return null; + } +} diff --git a/androidx_backend/src/androidx/core/uwb/backend/impl/UwbControllerClient.java b/androidx_backend/src/androidx/core/uwb/backend/impl/UwbControllerClient.java new file mode 100644 index 00000000..d2c08ba0 --- /dev/null +++ b/androidx_backend/src/androidx/core/uwb/backend/impl/UwbControllerClient.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 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 androidx.core.uwb.backend.impl; + +import static androidx.core.uwb.backend.impl.internal.Utils.STATUS_OK; +import static androidx.core.uwb.backend.impl.internal.Utils.TAG; + +import android.os.RemoteException; +import android.util.Log; + +import androidx.core.uwb.backend.IRangingSessionCallback; +import androidx.core.uwb.backend.RangingParameters; +import androidx.core.uwb.backend.UwbAddress; +import androidx.core.uwb.backend.UwbComplexChannel; +import androidx.core.uwb.backend.impl.internal.RangingController; +import androidx.core.uwb.backend.impl.internal.UwbServiceImpl; + +import java.util.concurrent.Executors; + +/** This class implement the operations of a uwb controller. */ +public class UwbControllerClient extends UwbClient { + + public UwbControllerClient(RangingController rangingController, UwbServiceImpl uwbService) { + super(rangingController, uwbService); + } + + @Override + public UwbComplexChannel getComplexChannel() throws RemoteException { + RangingController controller = (RangingController) mDevice; + androidx.core.uwb.backend.impl.internal.UwbComplexChannel channel = + controller.getComplexChannel(); + UwbComplexChannel uwbComplexChannel = new UwbComplexChannel(); + uwbComplexChannel.channel = channel.getChannel(); + uwbComplexChannel.preambleIndex = channel.getPreambleIndex(); + return uwbComplexChannel; + } + + @Override + public void startRanging(RangingParameters parameters, IRangingSessionCallback callback) + throws RemoteException { + setRangingParameters(parameters); + int status = ((RangingController) mDevice) + .startRanging(convertCallback(callback), Executors.newSingleThreadExecutor()); + if (status != STATUS_OK) { + Log.w(TAG, String.format("Ranging start failed with status %d", status)); + } + } + + @Override + public void stopRanging(IRangingSessionCallback callback) throws RemoteException { + int status = ((RangingController) mDevice).stopRanging(); + if (status != STATUS_OK) { + Log.w(TAG, String.format("Ranging stop failed with status %d", status)); + } + } + + @Override + public void addControlee(UwbAddress address) throws RemoteException { + androidx.core.uwb.backend.impl.internal.UwbAddress uwbAddress = + androidx.core.uwb.backend.impl.internal.UwbAddress.fromBytes(address.address); + int status = ((RangingController) mDevice).addControlee(uwbAddress); + if (status != STATUS_OK) { + Log.w(TAG, String.format("Adding controlee failed with status %d", status)); + } + } + + @Override + public void removeControlee(UwbAddress address) throws RemoteException { + androidx.core.uwb.backend.impl.internal.UwbAddress uwbAddress = + androidx.core.uwb.backend.impl.internal.UwbAddress.fromBytes(address.address); + int status = ((RangingController) mDevice).removeControlee(uwbAddress); + if (status != STATUS_OK) { + Log.w(TAG, String.format("Removing controlee failed with status %d", status)); + } + } + + @Override + public int getInterfaceVersion() throws RemoteException { + return 0; + } + + @Override + public String getInterfaceHash() throws RemoteException { + return null; + } +} diff --git a/androidx_backend/src/androidx/core/uwb/backend/impl/UwbService.java b/androidx_backend/src/androidx/core/uwb/backend/impl/UwbService.java index 8b68a302..19f0db64 100644 --- a/androidx_backend/src/androidx/core/uwb/backend/impl/UwbService.java +++ b/androidx_backend/src/androidx/core/uwb/backend/impl/UwbService.java @@ -18,6 +18,7 @@ package androidx.core.uwb.backend.impl; import android.app.Service; import android.content.Intent; import android.os.IBinder; +import android.util.Log; import androidx.core.uwb.backend.IUwb; import androidx.core.uwb.backend.IUwbClient; @@ -26,15 +27,11 @@ import androidx.core.uwb.backend.impl.internal.UwbServiceImpl; /** Uwb service entry point of the backend. */ public class UwbService extends Service { - private final UwbServiceImpl mUwbServiceImpl; - - public UwbService() { - mUwbServiceImpl = new UwbServiceImpl(this); - } - + private UwbServiceImpl mUwbServiceImpl; @Override public void onCreate() { super.onCreate(); + mUwbServiceImpl = new UwbServiceImpl(this); } @Override @@ -53,15 +50,18 @@ public class UwbService extends Service { new IUwb.Stub() { @Override public IUwbClient getControleeClient() { - // TODO (b/234033640): Implement this. How do we reuse gmscore code here? - // Need to create a context from calling package. Then create a ranging device. - return null; + Log.i("UwbService", "Getting controleeClient"); + return new UwbControleeClient(mUwbServiceImpl + .getControlee(UwbService.this.getApplicationContext()), + mUwbServiceImpl); } @Override public IUwbClient getControllerClient() { - // TODO (b/234033640): Implement this. How do we reuse gmscore code here? - return null; + Log.i("UwbService", "Getting controllerClient"); + return new UwbControllerClient(mUwbServiceImpl + .getController(UwbService.this.getApplicationContext()), + mUwbServiceImpl); } @Override diff --git a/androidx_backend/tests/src/androidx/core/uwb/backend/impl/UwbControleeClientTest.java b/androidx_backend/tests/src/androidx/core/uwb/backend/impl/UwbControleeClientTest.java new file mode 100644 index 00000000..dc0b8a05 --- /dev/null +++ b/androidx_backend/tests/src/androidx/core/uwb/backend/impl/UwbControleeClientTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 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 androidx.core.uwb.backend.impl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; + +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import androidx.core.uwb.backend.IRangingSessionCallback; +import androidx.core.uwb.backend.RangingParameters; +import androidx.core.uwb.backend.UwbComplexChannel; +import androidx.core.uwb.backend.impl.internal.RangingControlee; +import androidx.core.uwb.backend.impl.internal.RangingSessionCallback; +import androidx.core.uwb.backend.impl.internal.UwbServiceImpl; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.concurrent.ExecutorService; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class UwbControleeClientTest { + + @Mock private RangingControlee mRangingControlee; + @Mock private UwbServiceImpl mUwbService; + @Mock private IRangingSessionCallback mRangingSessionCallback; + private UwbControleeClient mUwbControleeClient; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mUwbControleeClient = new UwbControleeClient(mRangingControlee, mUwbService); + } + + @Test + public void testStartRanging() throws RemoteException { + RangingParameters params = new RangingParameters(); + params.complexChannel = new UwbComplexChannel(); + params.complexChannel.channel = 9; + params.complexChannel.preambleIndex = 9; + params.peerDevices = new ArrayList<>(); + mUwbControleeClient.startRanging(params, mRangingSessionCallback); + verify(mRangingControlee).setRangingParameters(any()); + verify(mRangingControlee).startRanging( + any(RangingSessionCallback.class), any(ExecutorService.class)); + } + + @Test + public void testStopRanging() throws RemoteException { + mUwbControleeClient.stopRanging(mRangingSessionCallback); + verify(mRangingControlee).stopRanging(); + } +} diff --git a/androidx_backend/tests/src/androidx/core/uwb/backend/impl/UwbControllerClientTest.java b/androidx_backend/tests/src/androidx/core/uwb/backend/impl/UwbControllerClientTest.java new file mode 100644 index 00000000..0c5cfe68 --- /dev/null +++ b/androidx_backend/tests/src/androidx/core/uwb/backend/impl/UwbControllerClientTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2022 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 androidx.core.uwb.backend.impl; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import androidx.core.uwb.backend.IRangingSessionCallback; +import androidx.core.uwb.backend.RangingParameters; +import androidx.core.uwb.backend.UwbAddress; +import androidx.core.uwb.backend.impl.internal.RangingController; +import androidx.core.uwb.backend.impl.internal.RangingSessionCallback; +import androidx.core.uwb.backend.impl.internal.UwbComplexChannel; +import androidx.core.uwb.backend.impl.internal.UwbServiceImpl; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.concurrent.ExecutorService; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class UwbControllerClientTest { + + @Mock private RangingController mRangingController; + @Mock private UwbServiceImpl mUwbService; + @Mock private IRangingSessionCallback mRangingSessionCallback; + @Captor + ArgumentCaptor<androidx.core.uwb.backend.impl.internal.UwbAddress> mAddressCaptor; + private UwbControllerClient mUwbControllerClient; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mUwbControllerClient = new UwbControllerClient(mRangingController, mUwbService); + } + + @Test + public void testGetComplexChannel() throws RemoteException { + UwbComplexChannel channel = new UwbComplexChannel(9, 9); + when(mRangingController.getComplexChannel()).thenReturn(channel); + androidx.core.uwb.backend.UwbComplexChannel complexChannel = + mUwbControllerClient.getComplexChannel(); + assertEquals(complexChannel.channel, channel.getChannel()); + assertEquals(complexChannel.preambleIndex, channel.getPreambleIndex()); + } + + @Test + public void testStartRanging() throws RemoteException { + RangingParameters params = new RangingParameters(); + params.complexChannel = new androidx.core.uwb.backend.UwbComplexChannel(); + params.complexChannel.channel = 9; + params.complexChannel.preambleIndex = 9; + params.peerDevices = new ArrayList<>(); + mUwbControllerClient.startRanging(params, mRangingSessionCallback); + verify(mRangingController).setRangingParameters(any( + androidx.core.uwb.backend.impl.internal.RangingParameters.class)); + verify(mRangingController).startRanging( + any(RangingSessionCallback.class), any(ExecutorService.class)); + } + + @Test + public void testStopRanging() throws RemoteException { + mUwbControllerClient.stopRanging(mRangingSessionCallback); + verify(mRangingController).stopRanging(); + } + + @Test + public void testAddControlee() throws RemoteException { + UwbAddress address = new UwbAddress(); + address.address = new byte[] {0x1, 0x2}; + mUwbControllerClient.addControlee(address); + verify(mRangingController).addControlee(mAddressCaptor.capture()); + assertArrayEquals(address.address, mAddressCaptor.getValue().toBytes()); + } + + @Test + public void testRemoveControlee() throws RemoteException { + UwbAddress address = new UwbAddress(); + address.address = new byte[] {0x1, 0x2}; + mUwbControllerClient.removeControlee(address); + verify(mRangingController).removeControlee(mAddressCaptor.capture()); + assertArrayEquals(address.address, mAddressCaptor.getValue().toBytes()); + } +} diff --git a/apex/Android.bp b/apex/Android.bp index 9e86c9b9..b89804a9 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -108,6 +108,7 @@ bootclasspath_fragment { // result in a build failure due to inconsistent flags. package_prefixes: [ "com.android.x", + "android.uwb.util", ], }, } diff --git a/framework/Android.bp b/framework/Android.bp index 58bf8f69..7f75ec68 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -74,8 +74,6 @@ java_library { defaults: ["framework-uwb-defaults"], sdk_version: "module_Tiramisu", libs: ["framework-annotations-lib",], - // java_api_finder must accompany `srcs` (`srcs` defined in `framework-uwb-defaults`) - plugins: ["java_api_finder"], installable: false, } @@ -95,8 +93,6 @@ java_sdk_library { hostdex: true, // for hiddenapi check impl_library_visibility: [ - //TODO(b/244347268): Remove this after code move. - "//cts/tests/uwb:__subpackages__", "//external/sl4a/Common:__subpackages__", "//packages/modules/Uwb:__subpackages__", ], @@ -106,6 +102,7 @@ java_sdk_library { ], permitted_packages: [ "android.uwb", + "android.uwb.util", // Created by jarjar rules. "com.android.x.uwb", ], @@ -127,27 +124,6 @@ java_defaults { ], } -// TODO(b/186585880): Fix all @hide dependencies. -// defaults for CTS tests that need to build against framework-uwb's @hide APIs -java_defaults { - name: "framework-uwb-cts-defaults", - sdk_version: "core_current", - libs: [ - // order matters: classes in framework-uwb are resolved before framework, meaning - // @hide APIs in framework-uwb are resolved before @SystemApi stubs in framework - "framework-uwb.impl", - "framework", - - // if sdk_version="" this gets automatically included, but here we need to add manually. - "framework-res", - ], - defaults_visibility: [ - //TODO(b/244347268): Remove this after code move. - "//cts/tests/uwb:__subpackages__", - "//packages/modules/Uwb/tests/cts:__subpackages__", - ], -} - filegroup { name: "uwb-jarjar-rules", srcs: ["jarjar-rules.txt"], diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 5ce39a16..bc51295c 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -50,7 +50,6 @@ package android.uwb { method public long getElapsedRealtimeNanos(); method public int getLineOfSight(); method public int getMeasurementFocus(); - method @NonNull public android.os.PersistableBundle getRangingMeasurementMetadata(); method @NonNull public android.uwb.UwbAddress getRemoteDeviceAddress(); method @IntRange(from=android.uwb.RangingMeasurement.RSSI_UNKNOWN, to=android.uwb.RangingMeasurement.RSSI_MAX) public int getRssiDbm(); method public int getStatus(); @@ -80,7 +79,6 @@ package android.uwb { method @NonNull public android.uwb.RangingMeasurement.Builder setElapsedRealtimeNanos(long); method @NonNull public android.uwb.RangingMeasurement.Builder setLineOfSight(int); method @NonNull public android.uwb.RangingMeasurement.Builder setMeasurementFocus(int); - method @NonNull public android.uwb.RangingMeasurement.Builder setRangingMeasurementMetadata(@NonNull android.os.PersistableBundle); method @NonNull public android.uwb.RangingMeasurement.Builder setRemoteDeviceAddress(@NonNull android.uwb.UwbAddress); method @NonNull public android.uwb.RangingMeasurement.Builder setRssiDbm(@IntRange(from=android.uwb.RangingMeasurement.RSSI_UNKNOWN, to=android.uwb.RangingMeasurement.RSSI_MAX) int); method @NonNull public android.uwb.RangingMeasurement.Builder setStatus(int); @@ -89,7 +87,6 @@ package android.uwb { public final class RangingReport implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.List<android.uwb.RangingMeasurement> getMeasurements(); - method @NonNull public android.os.PersistableBundle getRangingReportMetadata(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.uwb.RangingReport> CREATOR; } @@ -98,7 +95,6 @@ package android.uwb { ctor public RangingReport.Builder(); method @NonNull public android.uwb.RangingReport.Builder addMeasurement(@NonNull android.uwb.RangingMeasurement); method @NonNull public android.uwb.RangingReport.Builder addMeasurements(@NonNull java.util.List<android.uwb.RangingMeasurement>); - method @NonNull public android.uwb.RangingReport.Builder addRangingReportMetadata(@NonNull android.os.PersistableBundle); method @NonNull public android.uwb.RangingReport build(); } @@ -184,14 +180,12 @@ package android.uwb { method @NonNull @RequiresPermission(allOf={android.Manifest.permission.UWB_PRIVILEGED, android.Manifest.permission.UWB_RANGING}) public android.os.CancellationSignal openRangingSession(@NonNull android.os.PersistableBundle, @NonNull java.util.concurrent.Executor, @NonNull android.uwb.RangingSession.Callback, @NonNull String); method public void provisionProfileAdfByScript(@NonNull android.os.PersistableBundle, @NonNull java.util.concurrent.Executor, @NonNull android.uwb.UwbManager.AdfProvisionStateCallback); method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void registerAdapterStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.uwb.UwbManager.AdapterStateCallback); - method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void registerUwbOemExtensionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.uwb.UwbManager.UwbOemExtensionCallback); method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void registerUwbVendorUciCallback(@NonNull java.util.concurrent.Executor, @NonNull android.uwb.UwbManager.UwbVendorUciCallback); method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int removeProfileAdf(@NonNull android.os.PersistableBundle); method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int removeServiceProfile(@NonNull android.os.PersistableBundle); method @NonNull @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public int sendVendorUciMessage(@IntRange(from=9, to=15) int, int, @NonNull byte[]); method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void setUwbEnabled(boolean); method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void unregisterAdapterStateCallback(@NonNull android.uwb.UwbManager.AdapterStateCallback); - method @RequiresPermission(android.Manifest.permission.UWB_PRIVILEGED) public void unregisterUwbOemExtensionCallback(@NonNull android.uwb.UwbManager.UwbOemExtensionCallback); method public void unregisterUwbVendorUciCallback(@NonNull android.uwb.UwbManager.UwbVendorUciCallback); field public static final int REMOVE_PROFILE_ADF_ERROR_INTERNAL = 2; // 0x2 field public static final int REMOVE_PROFILE_ADF_ERROR_UNKNOWN_SERVICE = 1; // 0x1 @@ -227,13 +221,6 @@ package android.uwb { field public static final int REASON_UNKNOWN = 3; // 0x3 } - public static interface UwbManager.UwbOemExtensionCallback { - method public void onDeviceStatusNotificationReceived(@NonNull android.os.PersistableBundle); - method @NonNull public android.uwb.RangingReport onRangingReportReceived(@NonNull android.uwb.RangingReport); - method @NonNull public int onSessionConfigurationComplete(@NonNull android.os.PersistableBundle); - method public void onSessionStatusNotificationReceived(@NonNull android.os.PersistableBundle); - } - public static interface UwbManager.UwbVendorUciCallback { method public void onVendorUciNotification(@IntRange(from=9, to=15) int, int, @NonNull byte[]); method public void onVendorUciResponse(@IntRange(from=9, to=15) int, int, @NonNull byte[]); diff --git a/framework/java/android/uwb/IUwbAdapter.aidl b/framework/java/android/uwb/IUwbAdapter.aidl index 4902ef7e..ad23183a 100644 --- a/framework/java/android/uwb/IUwbAdapter.aidl +++ b/framework/java/android/uwb/IUwbAdapter.aidl @@ -347,6 +347,8 @@ interface IUwbAdapter { int sendVendorUciMessage(int gid, int oid, in byte[] payload); + void onRangingRoundsUpdateDtTag(in SessionHandle sessionHandle, in PersistableBundle parameters); + /** * The maximum allowed time to open a ranging session. */ @@ -362,4 +364,9 @@ interface IUwbAdapter { * closed. */ const int RANGING_SESSION_CLOSE_THRESHOLD_MS = 3000; // Value TBD + + /** + * The maximum allowed time to configure ranging rounds update for DT Tag + */ + const int RANGING_ROUNDS_UPDATE_DT_TAG_THRESHOLD_MS = 3000; // Value TBD } diff --git a/framework/java/android/uwb/IUwbRangingCallbacks.aidl b/framework/java/android/uwb/IUwbRangingCallbacks.aidl index a961718b..44bd4d9e 100644 --- a/framework/java/android/uwb/IUwbRangingCallbacks.aidl +++ b/framework/java/android/uwb/IUwbRangingCallbacks.aidl @@ -258,4 +258,7 @@ oneway interface IUwbRangingCallbacks { void onServiceDiscovered(in SessionHandle sessionHandle, in PersistableBundle parameters); void onServiceConnected(in SessionHandle sessionHandle, in PersistableBundle parameters); + + void onRangingRoundsUpdateDtTagStatus(in SessionHandle sessionHandle, + in PersistableBundle parameters); } diff --git a/framework/java/android/uwb/RangingManager.java b/framework/java/android/uwb/RangingManager.java index 491808ae..adbbbae6 100644 --- a/framework/java/android/uwb/RangingManager.java +++ b/framework/java/android/uwb/RangingManager.java @@ -459,6 +459,21 @@ public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub { } } + @Override + public void onRangingRoundsUpdateDtTagStatus(SessionHandle sessionHandle, + @NonNull PersistableBundle parameters) { + synchronized (this) { + if (!hasSession(sessionHandle)) { + Log.w(mTag, "onRangingRoundsUpdateDtTagStatus - received unexpected " + + "SessionHandle: " + sessionHandle); + return; + } + + RangingSession session = mRangingSessionTable.get(sessionHandle); + session.onRangingRoundsUpdateDtTagStatus(parameters); + } + } + // TODO(b/211025367): Remove this conversion and use direct API values. @RangingSession.Callback.Reason private static int convertToReason(@RangingChangeReason int reason) { diff --git a/framework/java/android/uwb/RangingMeasurement.java b/framework/java/android/uwb/RangingMeasurement.java index d47e1020..cbd7e358 100644 --- a/framework/java/android/uwb/RangingMeasurement.java +++ b/framework/java/android/uwb/RangingMeasurement.java @@ -26,6 +26,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; import android.os.SystemClock; +import android.uwb.util.PersistableBundleUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -252,7 +253,7 @@ public final class RangingMeasurement implements Parcelable { /** * Gets ranging measurement metadata passed by vendor - * + * @hide * @return vendor data for ranging measurement */ @NonNull @@ -282,8 +283,9 @@ public final class RangingMeasurement implements Parcelable { other.getDestinationAngleOfArrivalMeasurement()) && mLineOfSight == other.getLineOfSight() && mMeasurementFocus == other.getMeasurementFocus() - && mRssiDbm == other.getRssiDbm(); - // TODO: Equality for RangingMeasurementMetadata + && mRssiDbm == other.getRssiDbm() + && PersistableBundleUtils.isEqual(mRangingMeasurementMetadata, + other.mRangingMeasurementMetadata); } return false; } @@ -295,7 +297,8 @@ public final class RangingMeasurement implements Parcelable { public int hashCode() { return Objects.hash(mRemoteDeviceAddress, mStatus, mElapsedRealtimeNanos, mDistanceMeasurement, mAngleOfArrivalMeasurement, - mDestinationAngleOfArrivalMeasurement, mLineOfSight, mMeasurementFocus, mRssiDbm); + mDestinationAngleOfArrivalMeasurement, mLineOfSight, mMeasurementFocus, mRssiDbm, + PersistableBundleUtils.getHashCode(mRangingMeasurementMetadata)); } @Override @@ -335,8 +338,9 @@ public final class RangingMeasurement implements Parcelable { builder.setLineOfSight(in.readInt()); builder.setMeasurementFocus(in.readInt()); builder.setRssiDbm(in.readInt()); - builder.setRangingMeasurementMetadata( - in.readPersistableBundle(getClass().getClassLoader())); + PersistableBundle metadata = + in.readPersistableBundle(getClass().getClassLoader()); + if (metadata != null) builder.setRangingMeasurementMetadata(metadata); return builder.build(); } @@ -489,7 +493,7 @@ public final class RangingMeasurement implements Parcelable { /** * Set Ranging measurement metadata - * + * @hide * @param rangingMeasurementMetadata vendor data per ranging measurement * * @throws IllegalStateException if rangingMeasurementMetadata is null diff --git a/framework/java/android/uwb/RangingReport.java b/framework/java/android/uwb/RangingReport.java index 4bce4b42..b3079786 100644 --- a/framework/java/android/uwb/RangingReport.java +++ b/framework/java/android/uwb/RangingReport.java @@ -22,6 +22,7 @@ import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; +import android.uwb.util.PersistableBundleUtils; import java.util.ArrayList; import java.util.List; @@ -60,7 +61,7 @@ public final class RangingReport implements Parcelable { /** * Gets ranging report metadata passed by vendor - * + * @hide * @return vendor data for ranging report */ @NonNull @@ -79,8 +80,9 @@ public final class RangingReport implements Parcelable { if (obj instanceof RangingReport) { RangingReport other = (RangingReport) obj; - return mRangingMeasurements.equals(other.getMeasurements()); - // TODO(b/256734264): Equality for RangingReportMetadata + return mRangingMeasurements.equals(other.getMeasurements()) + && PersistableBundleUtils.isEqual(mRangingReportMetadata, + other.getRangingReportMetadata()); } return false; } @@ -90,7 +92,8 @@ public final class RangingReport implements Parcelable { */ @Override public int hashCode() { - return Objects.hash(mRangingMeasurements); + return Objects.hash(mRangingMeasurements, PersistableBundleUtils + .getHashCode(mRangingReportMetadata)); } @Override @@ -110,8 +113,9 @@ public final class RangingReport implements Parcelable { public RangingReport createFromParcel(Parcel in) { Builder builder = new Builder(); builder.addMeasurements(in.createTypedArrayList(RangingMeasurement.CREATOR)); - builder.addRangingReportMetadata(in.readPersistableBundle( - getClass().getClassLoader())); + PersistableBundle metadata = + in.readPersistableBundle(getClass().getClassLoader()); + if (metadata != null) builder.addRangingReportMetadata(metadata); return builder.build(); } @@ -161,7 +165,7 @@ public final class RangingReport implements Parcelable { /** * Add ranging report metadata - * + * @hide * @param rangingReportMetadata vendor data per ranging report * * @throws IllegalStateException if rangingReportMetadata is null diff --git a/framework/java/android/uwb/RangingSession.java b/framework/java/android/uwb/RangingSession.java index d5844f55..1c05bd93 100644 --- a/framework/java/android/uwb/RangingSession.java +++ b/framework/java/android/uwb/RangingSession.java @@ -417,6 +417,16 @@ public final class RangingSession implements AutoCloseable { * @param parameters protocol specific params for connected service. */ default void onServiceConnected(@NonNull PersistableBundle parameters) {} + + /** + * @hide + * Invoked when a response/status is received for active ranging rounds update + * + * @param parameters bundle of ranging rounds update status + * {@link com.google.uwb.support.dltdoa.DlTDoARangingRoundsUpdateStatus} + */ + // TODO: Add @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) after ag/19901449 + default void onRangingRoundsUpdateDtTagStatus(@NonNull PersistableBundle parameters) {} } /** @@ -729,6 +739,32 @@ public final class RangingSession implements AutoCloseable { /** * @hide + * Update active ranging rounds for DT Tag + * + * <p> On successfully sending the command, + * {@link RangingSession.Callback#onRangingRoundsUpdateDtTag(PersistableBundle)} + * is invoked + * @param params Parameters to configure active ranging rounds + * {@link com.google.uwb.support.dltdoa.DlTDoARangingRoundsUpdate} + */ + // TODO: Add @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) after ag/19901449 + @RequiresPermission(Manifest.permission.UWB_PRIVILEGED) + public void onRangingRoundsUpdateDtTag(@NonNull PersistableBundle params) { + if (mState != State.ACTIVE) { + throw new IllegalStateException(); + } + + Log.v(mTag, "onRangingRoundsUpdateDtTag - sessionHandle: " + mSessionHandle); + try { + mAdapter.onRangingRoundsUpdateDtTag(mSessionHandle, params); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + + /** + * @hide */ public void onRangingOpened() { if (mState == State.CLOSED) { @@ -1056,6 +1092,19 @@ public final class RangingSession implements AutoCloseable { /** * @hide */ + public void onRangingRoundsUpdateDtTagStatus(@NonNull PersistableBundle params) { + if (!isOpen()) { + Log.w(mTag, "onDlTDoARangingRoundsUpdateStatus invoked for non-open session"); + return; + } + + Log.v(mTag, "onDlTDoARangingRoundsUpdateStatus - sessionHandle: " + mSessionHandle); + executeCallback(() -> mCallback.onRangingRoundsUpdateDtTagStatus(params)); + } + + /** + * @hide + */ private void executeCallback(@NonNull Runnable runnable) { final long identity = Binder.clearCallingIdentity(); try { diff --git a/framework/java/android/uwb/UwbManager.java b/framework/java/android/uwb/UwbManager.java index d2871294..c3f4e5b6 100644 --- a/framework/java/android/uwb/UwbManager.java +++ b/framework/java/android/uwb/UwbManager.java @@ -340,6 +340,7 @@ public final class UwbManager { /** * Interface for Oem extensions on ongoing session + * @hide */ // TODO: Add @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) after ag/19901449 public interface UwbOemExtensionCallback { @@ -456,6 +457,7 @@ public final class UwbManager { } /** + * @hide * Register an {@link UwbOemExtensionCallback} to listen for UWB oem extension callbacks * <p>The provided callback will be invoked by the given {@link Executor}. * @@ -470,6 +472,7 @@ public final class UwbManager { } /** + * @hide * Unregister the specified {@link UwbOemExtensionCallback} * * <p>The same {@link UwbOemExtensionCallback} object used when calling diff --git a/framework/java/android/uwb/util/PersistableBundleUtils.java b/framework/java/android/uwb/util/PersistableBundleUtils.java new file mode 100644 index 00000000..d81e7198 --- /dev/null +++ b/framework/java/android/uwb/util/PersistableBundleUtils.java @@ -0,0 +1,631 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.uwb.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.ParcelUuid; +import android.os.PersistableBundle; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.TreeSet; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** @hide */ +public class PersistableBundleUtils { +// private static final String LIST_KEY_FORMAT = "LIST_ITEM_%d"; +// private static final String COLLECTION_SIZE_KEY = "COLLECTION_LENGTH"; +// private static final String MAP_KEY_FORMAT = "MAP_KEY_%d"; +// private static final String MAP_VALUE_FORMAT = "MAP_VALUE_%d"; +// +// private static final String PARCEL_UUID_KEY = "PARCEL_UUID"; +// private static final String BYTE_ARRAY_KEY = "BYTE_ARRAY_KEY"; +// private static final String INTEGER_KEY = "INTEGER_KEY"; +// private static final String STRING_KEY = "STRING_KEY"; +// +// private final static char[] HEX_DIGITS = +// {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; +// private final static char[] HEX_LOWER_CASE_DIGITS = +// {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + +// /** +// * Functional interface to convert an object of the specified type to a PersistableBundle. +// * +// * @param <T> the type of the source object +// */ +// public interface Serializer<T> { +// /** +// * Converts this object to a PersistableBundle. +// * +// * @return the PersistableBundle representation of this object +// */ +// PersistableBundle toPersistableBundle(T obj); +// } +// +// /** +// * Functional interface used to create an object of the specified type from a PersistableBundle. +// * +// * @param <T> the type of the resultant object +// */ +// public interface Deserializer<T> { +// /** +// * Creates an instance of specified type from a PersistableBundle representation. +// * +// * @param in the PersistableBundle representation +// * @return an instance of the specified type +// */ +// T fromPersistableBundle(PersistableBundle in); +// } +// +// /** Serializer to convert an integer to a PersistableBundle. */ +// public static final Serializer<Integer> INTEGER_SERIALIZER = +// (i) -> { +// final PersistableBundle result = new PersistableBundle(); +// result.putInt(INTEGER_KEY, i); +// return result; +// }; +// +// /** Deserializer to convert a PersistableBundle to an integer. */ +// public static final Deserializer<Integer> INTEGER_DESERIALIZER = +// (bundle) -> { +// Objects.requireNonNull(bundle, "PersistableBundle is null"); +// return bundle.getInt(INTEGER_KEY); +// }; +// +// /** Serializer to convert s String to a PersistableBundle. */ +// public static final Serializer<String> STRING_SERIALIZER = +// (i) -> { +// final PersistableBundle result = new PersistableBundle(); +// result.putString(STRING_KEY, i); +// return result; +// }; +// +// /** Deserializer to convert a PersistableBundle to a String. */ +// public static final Deserializer<String> STRING_DESERIALIZER = +// (bundle) -> { +// Objects.requireNonNull(bundle, "PersistableBundle is null"); +// return bundle.getString(STRING_KEY); +// }; +// +// /** +// * Converts a ParcelUuid to a PersistableBundle. +// * +// * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned +// * PersistableBundle object. +// * +// * @param uuid a ParcelUuid instance to persist +// * @return the PersistableBundle instance +// */ +// public static PersistableBundle fromParcelUuid(ParcelUuid uuid) { +// final PersistableBundle result = new PersistableBundle(); +// +// result.putString(PARCEL_UUID_KEY, uuid.toString()); +// +// return result; +// } +// +// /** +// * Converts from a PersistableBundle to a ParcelUuid. +// * +// * @param bundle the PersistableBundle containing the ParcelUuid +// * @return the ParcelUuid instance +// */ +// public static ParcelUuid toParcelUuid(PersistableBundle bundle) { +// return ParcelUuid.fromString(bundle.getString(PARCEL_UUID_KEY)); +// } +// +// /** +// * Converts from a list of Persistable objects to a single PersistableBundle. +// * +// * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned +// * PersistableBundle object. +// * +// * @param <T> the type of the objects to convert to the PersistableBundle +// * @param in the list of objects to be serialized into a PersistableBundle +// * @param serializer an implementation of the {@link Serializer} functional interface that +// * converts an object of type T to a PersistableBundle +// */ +// @NonNull +// public static <T> PersistableBundle fromList( +// @NonNull List<T> in, @NonNull Serializer<T> serializer) { +// final PersistableBundle result = new PersistableBundle(); +// +// result.putInt(COLLECTION_SIZE_KEY, in.size()); +// for (int i = 0; i < in.size(); i++) { +// final String key = String.format(LIST_KEY_FORMAT, i); +// result.putPersistableBundle(key, serializer.toPersistableBundle(in.get(i))); +// } +// return result; +// } +// +// /** +// * Converts from a PersistableBundle to a list of objects. +// * +// * @param <T> the type of the objects to convert from a PersistableBundle +// * @param in the PersistableBundle containing the persisted list +// * @param deserializer an implementation of the {@link Deserializer} functional interface that +// * builds the relevant type of objects. +// */ +// @NonNull +// public static <T> List<T> toList( +// @NonNull PersistableBundle in, @NonNull Deserializer<T> deserializer) { +// final int listLength = in.getInt(COLLECTION_SIZE_KEY); +// final ArrayList<T> result = new ArrayList<>(listLength); +// +// for (int i = 0; i < listLength; i++) { +// final String key = String.format(LIST_KEY_FORMAT, i); +// final PersistableBundle item = in.getPersistableBundle(key); +// +// result.add(deserializer.fromPersistableBundle(item)); +// } +// return result; +// } +// +// // TODO: b/170513329 Delete #fromByteArray and #toByteArray once BaseBundle#putByteArray and +// // BaseBundle#getByteArray are exposed. +// +// /** +// * Converts a byte array to a PersistableBundle. +// * +// * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned +// * PersistableBundle object. +// * +// * @param array a byte array instance to persist +// * @return the PersistableBundle instance +// */ +// public static PersistableBundle fromByteArray(byte[] array) { +// final PersistableBundle result = new PersistableBundle(); +// +// result.putString(BYTE_ARRAY_KEY, toHexString(array)); +// +// return result; +// } +// +// // Copied from com.android.internal.util.HexDump +// @UnsupportedAppUsage +// public static String toHexString(byte[] array, int offset, int length) { +// return toHexString(array, offset, length, true); +// } +// +// public static String toHexString(byte[] array, int offset, int length, boolean upperCase) { +// char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS; +// char[] buf = new char[length * 2]; +// +// int bufIndex = 0; +// for (int i = offset; i < offset + length; i++) { +// byte b = array[i]; +// buf[bufIndex++] = digits[(b >>> 4) & 0x0F]; +// buf[bufIndex++] = digits[b & 0x0F]; +// } +// +// return new String(buf); +// } +// +// @UnsupportedAppUsage +// public static String toHexString(byte[] array) { +// return toHexString(array, 0, array.length, true); +// } +// +// @UnsupportedAppUsage +// public static String toHexString(int i) { +// return toHexString(toByteArray(i)); +// } +// +// public static byte[] toByteArray(int i) { +// byte[] array = new byte[4]; +// +// array[3] = (byte) (i & 0xFF); +// array[2] = (byte) ((i >> 8) & 0xFF); +// array[1] = (byte) ((i >> 16) & 0xFF); +// array[0] = (byte) ((i >> 24) & 0xFF); +// +// return array; +// } +// +// @UnsupportedAppUsage +// public static byte[] hexStringToByteArray(String hexString) { +// int length = hexString.length(); +// byte[] buffer = new byte[length / 2]; +// +// for (int i = 0; i < length; i += 2) { +// buffer[i / 2] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte( +// hexString.charAt(i + 1))); +// } +// +// return buffer; +// } +// +// private static int toByte(char c) { +// if (c >= '0' && c <= '9') return (c - '0'); +// if (c >= 'A' && c <= 'F') return (c - 'A' + 10); +// if (c >= 'a' && c <= 'f') return (c - 'a' + 10); +// +// throw new RuntimeException("Invalid hex char '" + c + "'"); +// } +// +// /** +// * Converts from a PersistableBundle to a byte array. +// * +// * @param bundle the PersistableBundle containing the byte array +// * @return the byte array instance +// */ +// public static byte[] toByteArray(PersistableBundle bundle) { +// Objects.requireNonNull(bundle, "PersistableBundle is null"); +// +// String hex = bundle.getString(BYTE_ARRAY_KEY); +// if (hex == null || hex.length() % 2 != 0) { +// throw new IllegalArgumentException("PersistableBundle contains invalid byte array"); +// } +// +// return hexStringToByteArray(hex); +// } +// +// /** +// * Converts from a Map of Persistable objects to a single PersistableBundle. +// * +// * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned +// * PersistableBundle object. +// * +// * @param <K> the type of the map-key to convert to the PersistableBundle +// * @param <V> the type of the map-value to convert to the PersistableBundle +// * @param in the Map of objects implementing the {@link Persistable} interface +// * @param keySerializer an implementation of the {@link Serializer} functional interface that +// * converts a map-key of type T to a PersistableBundle +// * @param valueSerializer an implementation of the {@link Serializer} functional interface that +// * converts a map-value of type E to a PersistableBundle +// */ +// @NonNull +// public static <K, V> PersistableBundle fromMap( +// @NonNull Map<K, V> in, +// @NonNull Serializer<K> keySerializer, +// @NonNull Serializer<V> valueSerializer) { +// final PersistableBundle result = new PersistableBundle(); +// +// result.putInt(COLLECTION_SIZE_KEY, in.size()); +// int i = 0; +// for (Entry<K, V> entry : in.entrySet()) { +// final String keyKey = String.format(MAP_KEY_FORMAT, i); +// final String valueKey = String.format(MAP_VALUE_FORMAT, i); +// result.putPersistableBundle(keyKey, keySerializer.toPersistableBundle(entry.getKey())); +// result.putPersistableBundle( +// valueKey, valueSerializer.toPersistableBundle(entry.getValue())); +// +// i++; +// } +// +// return result; +// } +// +// /** +// * Converts from a PersistableBundle to a Map of objects. +// * +// * <p>In an attempt to preserve ordering, the returned map will be a LinkedHashMap. However, the +// * guarantees on the ordering can only ever be as strong as the map that was serialized in +// * {@link fromMap()}. If the initial map that was serialized had no ordering guarantees, the +// * deserialized map similarly may be of a non-deterministic order. +// * +// * @param <K> the type of the map-key to convert from a PersistableBundle +// * @param <V> the type of the map-value to convert from a PersistableBundle +// * @param in the PersistableBundle containing the persisted Map +// * @param keyDeserializer an implementation of the {@link Deserializer} functional interface +// * that builds the relevant type of map-key. +// * @param valueDeserializer an implementation of the {@link Deserializer} functional interface +// * that builds the relevant type of map-value. +// * @return An instance of the parsed map as a LinkedHashMap (in an attempt to preserve +// * ordering). +// */ +// @NonNull +// public static <K, V> LinkedHashMap<K, V> toMap( +// @NonNull PersistableBundle in, +// @NonNull Deserializer<K> keyDeserializer, +// @NonNull Deserializer<V> valueDeserializer) { +// final int mapSize = in.getInt(COLLECTION_SIZE_KEY); +// final LinkedHashMap<K, V> result = new LinkedHashMap<>(mapSize); +// +// for (int i = 0; i < mapSize; i++) { +// final String keyKey = String.format(MAP_KEY_FORMAT, i); +// final String valueKey = String.format(MAP_VALUE_FORMAT, i); +// final PersistableBundle keyBundle = in.getPersistableBundle(keyKey); +// final PersistableBundle valueBundle = in.getPersistableBundle(valueKey); +// +// final K key = keyDeserializer.fromPersistableBundle(keyBundle); +// final V value = valueDeserializer.fromPersistableBundle(valueBundle); +// result.put(key, value); +// } +// return result; +// } +// +// /** +// * Converts a PersistableBundle into a disk-stable byte array format +// * +// * @param bundle the PersistableBundle to be converted to a disk-stable format +// * @return the byte array representation of the PersistableBundle +// */ +// @Nullable +// public static byte[] toDiskStableBytes(@NonNull PersistableBundle bundle) throws IOException { +// final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); +// bundle.writeToStream(outputStream); +// return outputStream.toByteArray(); +// } +// +// /** +// * Converts from a disk-stable byte array format to a PersistableBundle +// * +// * @param bytes the disk-stable byte array +// * @return the PersistableBundle parsed from this byte array. +// */ +// public static PersistableBundle fromDiskStableBytes(@NonNull byte[] bytes) throws IOException { +// final ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); +// return PersistableBundle.readFromStream(inputStream); +// } +// +// /** +// * Ensures safe reading and writing of {@link PersistableBundle}s to and from disk. +// * +// * <p>This class will enforce exclusion between reads and writes using the standard semantics of +// * a ReadWriteLock. Specifically, concurrent readers ARE allowed, but reads/writes from/to the +// * file are mutually exclusive. In other words, for an unbounded number n, the acceptable states +// * are n readers, OR 1 writer (but not both). +// */ +// public static class LockingReadWriteHelper { +// private final ReadWriteLock mDiskLock = new ReentrantReadWriteLock(); +// private final String mPath; +// +// public LockingReadWriteHelper(@NonNull String path) { +// mPath = Objects.requireNonNull(path, "fileName was null"); +// } +// +// /** +// * Reads the {@link PersistableBundle} from the disk. +// * +// * @return the PersistableBundle, if the file existed, or null otherwise +// */ +// @Nullable +// public PersistableBundle readFromDisk() throws IOException { +// try { +// mDiskLock.readLock().lock(); +// final File file = new File(mPath); +// if (!file.exists()) { +// return null; +// } +// +// try (FileInputStream fis = new FileInputStream(file)) { +// return PersistableBundle.readFromStream(fis); +// } +// } finally { +// mDiskLock.readLock().unlock(); +// } +// } +// +// /** +// * Writes a {@link PersistableBundle} to disk. +// * +// * @param bundle the {@link PersistableBundle} to write to disk +// */ +// public void writeToDisk(@NonNull PersistableBundle bundle) throws IOException { +// Objects.requireNonNull(bundle, "bundle was null"); +// +// try { +// mDiskLock.writeLock().lock(); +// final File file = new File(mPath); +// if (!file.exists()) { +// file.getParentFile().mkdirs(); +// } +// +// try (FileOutputStream fos = new FileOutputStream(file)) { +// bundle.writeToStream(fos); +// } +// } finally { +// mDiskLock.writeLock().unlock(); +// } +// } +// } +// +// /** +// * Returns a copy of the persistable bundle with only the specified keys +// * +// * <p>This allows for holding minimized copies for memory-saving purposes. +// */ +// @NonNull +// public static PersistableBundle minimizeBundle( +// @NonNull PersistableBundle bundle, String... keys) { +// final PersistableBundle minimized = new PersistableBundle(); +// +// if (bundle == null) { +// return minimized; +// } +// +// for (String key : keys) { +// if (bundle.containsKey(key)) { +// final Object value = bundle.get(key); +// if (value == null) { +// continue; +// } +// +// if (value instanceof Boolean) { +// minimized.putBoolean(key, (Boolean) value); +// } else if (value instanceof boolean[]) { +// minimized.putBooleanArray(key, (boolean[]) value); +// } else if (value instanceof Double) { +// minimized.putDouble(key, (Double) value); +// } else if (value instanceof double[]) { +// minimized.putDoubleArray(key, (double[]) value); +// } else if (value instanceof Integer) { +// minimized.putInt(key, (Integer) value); +// } else if (value instanceof int[]) { +// minimized.putIntArray(key, (int[]) value); +// } else if (value instanceof Long) { +// minimized.putLong(key, (Long) value); +// } else if (value instanceof long[]) { +// minimized.putLongArray(key, (long[]) value); +// } else if (value instanceof String) { +// minimized.putString(key, (String) value); +// } else if (value instanceof String[]) { +// minimized.putStringArray(key, (String[]) value); +// } else if (value instanceof PersistableBundle) { +// minimized.putPersistableBundle(key, (PersistableBundle) value); +// } else { +// continue; +// } +// } +// } +// +// return minimized; +// } + + /** Builds a stable hashcode */ + public static int getHashCode(@Nullable PersistableBundle bundle) { + if (bundle == null) { + return -1; + } + + int iterativeHashcode = 0; + TreeSet<String> treeSet = new TreeSet<>(bundle.keySet()); + for (String key : treeSet) { + Object val = bundle.get(key); + if (val instanceof PersistableBundle) { + iterativeHashcode = + Objects.hash(iterativeHashcode, key, getHashCode((PersistableBundle) val)); + } else { + iterativeHashcode = Objects.hash(iterativeHashcode, key, val); + } + } + + return iterativeHashcode; + } + + /** Checks for persistable bundle equality */ + public static boolean isEqual( + @Nullable PersistableBundle left, @Nullable PersistableBundle right) { + // Check for pointer equality & null equality + if (Objects.equals(left, right)) { + return true; + } + + // If only one of the two is null, but not the other, not equal by definition. + if (Objects.isNull(left) != Objects.isNull(right)) { + return false; + } + + if (!left.keySet().equals(right.keySet())) { + return false; + } + + for (String key : left.keySet()) { + Object leftVal = left.get(key); + Object rightVal = right.get(key); + + // Check for equality + if (Objects.equals(leftVal, rightVal)) { + continue; + } else if (Objects.isNull(leftVal) != Objects.isNull(rightVal)) { + // If only one of the two is null, but not the other, not equal by definition. + return false; + } else if (!Objects.equals(leftVal.getClass(), rightVal.getClass())) { + // If classes are different, not equal by definition. + return false; + } + if (leftVal instanceof PersistableBundle) { + if (!isEqual((PersistableBundle) leftVal, (PersistableBundle) rightVal)) { + return false; + } + } else if (leftVal.getClass().isArray()) { + if (leftVal instanceof boolean[]) { + if (!Arrays.equals((boolean[]) leftVal, (boolean[]) rightVal)) { + return false; + } + } else if (leftVal instanceof double[]) { + if (!Arrays.equals((double[]) leftVal, (double[]) rightVal)) { + return false; + } + } else if (leftVal instanceof int[]) { + if (!Arrays.equals((int[]) leftVal, (int[]) rightVal)) { + return false; + } + } else if (leftVal instanceof long[]) { + if (!Arrays.equals((long[]) leftVal, (long[]) rightVal)) { + return false; + } + } else if (!Arrays.equals((Object[]) leftVal, (Object[]) rightVal)) { + return false; + } + } else { + if (!Objects.equals(leftVal, rightVal)) { + return false; + } + } + } + + return true; + } + +// /** +// * Wrapper class around PersistableBundles to allow equality comparisons +// * +// * <p>This class exposes the minimal getters to retrieve values. +// */ +// public static class PersistableBundleWrapper { +// @NonNull +// private final PersistableBundle mBundle; +// +// public PersistableBundleWrapper(@NonNull PersistableBundle bundle) { +// mBundle = Objects.requireNonNull(bundle, "Bundle was null"); +// } +// +// /** +// * Retrieves the integer associated with the provided key. +// * +// * @param key the string key to query +// * @param defaultValue the value to return if key does not exist +// * @return the int value, or the default +// */ +// public int getInt(String key, int defaultValue) { +// return mBundle.getInt(key, defaultValue); +// } +// +// @Override +// public int hashCode() { +// return getHashCode(mBundle); +// } +// +// @Override +// public boolean equals(Object obj) { +// if (!(obj instanceof PersistableBundleWrapper)) { +// return false; +// } +// +// final PersistableBundleWrapper other = (PersistableBundleWrapper) obj; +// +// return isEqual(mBundle, other.mBundle); +// } +// } +} diff --git a/indev_uwb_adaptation/jni/src/api.rs b/indev_uwb_adaptation/jni/src/api.rs index b902dea2..937aff9d 100644 --- a/indev_uwb_adaptation/jni/src/api.rs +++ b/indev_uwb_adaptation/jni/src/api.rs @@ -412,8 +412,8 @@ fn send_raw_vendor_cmd( return Err(Error::Parse(format!("Failed to convert payload {:?}", err))); } }; - let vendor_message = uwb_service.send_vendor_cmd(gid, oid, payload); - // TODO(cante): figure out if we send RawVendorMessage back in a callback + let vendor_message = uwb_service.raw_uci_cmd(gid, oid, payload); + // TODO(cante): figure out if we send RawUciMessage back in a callback todo!(); } diff --git a/service/Android.bp b/service/Android.bp index 4df8983a..2e49b29b 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -45,10 +45,8 @@ java_library { installable: false, defaults: ["service-uwb-common-defaults"], srcs: [ ":service-uwb-srcs" ], - // java_api_finder must accompany `srcs` - plugins: ["java_api_finder"], required: ["libuwb_uci_jni_rust"], - sdk_version: "system_server_Tiramisu", + sdk_version: "system_server_current", lint: { strict_updatability_linting: true, @@ -71,6 +69,7 @@ java_library { "com.uwb.support.multichip", "com.uwb.support.profile", "com.uwb.support.oemextension", + "com.uwb.support.dltdoa", "guava", "modules-utils-shell-command-handler", "modules-utils-handlerexecutor", @@ -109,7 +108,7 @@ java_library { "framework-connectivity.stubs.module_lib", ], - sdk_version: "system_server_Tiramisu", + sdk_version: "system_server_current", jarjar_rules: ":uwb-jarjar-rules", optimize: { diff --git a/service/java/com/android/server/uwb/DeviceConfigFacade.java b/service/java/com/android/server/uwb/DeviceConfigFacade.java index db2662a3..500c2d8b 100644 --- a/service/java/com/android/server/uwb/DeviceConfigFacade.java +++ b/service/java/com/android/server/uwb/DeviceConfigFacade.java @@ -50,7 +50,7 @@ public class DeviceConfigFacade { mRangingResultLogIntervalMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_UWB, "ranging_result_log_interval_ms", DEFAULT_RANGING_RESULT_LOG_INTERVAL_MS); mDeviceErrorBugreportEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_UWB, - "device_error_bugreport_enabled", true); + "device_error_bugreport_enabled", false); mBugReportMinIntervalMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_UWB, "bug_report_min_interval_ms", DEFAULT_BUG_REPORT_MIN_INTERVAL_MS); } diff --git a/service/java/com/android/server/uwb/UwbConfigurationManager.java b/service/java/com/android/server/uwb/UwbConfigurationManager.java index 788c2289..c62f22aa 100644 --- a/service/java/com/android/server/uwb/UwbConfigurationManager.java +++ b/service/java/com/android/server/uwb/UwbConfigurationManager.java @@ -83,7 +83,7 @@ public class UwbConfigurationManager { UwbTlvData getAppConfig = mNativeUwbManager.getAppConfigurations(sessionId, appConfigIds.length, appConfigIds.length, appConfigIds, chipId); Log.i(TAG, "getAppConfigurations respData: " - + getAppConfig != null ? getAppConfig.toString() : "null"); + + (getAppConfig != null ? getAppConfig.toString() : "null")); return decodeTLV(protocolName, getAppConfig, paramType); } @@ -95,7 +95,8 @@ public class UwbConfigurationManager { Log.d(TAG, "getCapsInfo for protocol: " + protocolName); UwbTlvData capsInfo = mNativeUwbManager.getCapsInfo(chipId); - Log.i(TAG, "getCapsInfo respData: " + capsInfo != null ? capsInfo.toString() : "null"); + Log.i(TAG, "getCapsInfo respData: " + + (capsInfo != null ? capsInfo.toString() : "null")); return decodeTLV(protocolName, capsInfo, paramType); } diff --git a/service/java/com/android/server/uwb/UwbMetrics.java b/service/java/com/android/server/uwb/UwbMetrics.java index 0e369f1e..f2302ab7 100644 --- a/service/java/com/android/server/uwb/UwbMetrics.java +++ b/service/java/com/android/server/uwb/UwbMetrics.java @@ -66,6 +66,7 @@ public class UwbMetrics { private long mStartTimeSinceBootMs; private int mInitLatencyMs; private int mInitStatus; + private int mRangingStatus; private int mActiveDuration; private int mRangingCount; private int mValidRangingCount; @@ -133,6 +134,42 @@ public class UwbMetrics { } } + private void convertRangingStatus(int status) { + mRangingStatus = UwbStatsLog.UWB_START_RANGING__STATUS__RANGING_GENERAL_FAILURE; + switch (status) { + case UwbUciConstants.STATUS_CODE_OK: + case UwbUciConstants.STATUS_CODE_OK_NEGATIVE_DISTANCE_REPORT: + mRangingStatus = UwbStatsLog.UWB_START_RANGING__STATUS__RANGING_SUCCESS; + break; + case UwbUciConstants.STATUS_CODE_RANGING_TX_FAILED: + mRangingStatus = UwbStatsLog.UWB_START_RANGING__STATUS__TX_FAILED; + break; + case UwbUciConstants.STATUS_CODE_RANGING_RX_PHY_DEC_FAILED: + mRangingStatus = UwbStatsLog.UWB_START_RANGING__STATUS__RX_PHY_DEC_FAILED; + break; + case UwbUciConstants.STATUS_CODE_RANGING_RX_PHY_TOA_FAILED: + mRangingStatus = UwbStatsLog.UWB_START_RANGING__STATUS__RX_PHY_TOA_FAILED; + break; + case UwbUciConstants.STATUS_CODE_RANGING_RX_PHY_STS_FAILED: + mRangingStatus = UwbStatsLog.UWB_START_RANGING__STATUS__RX_PHY_STS_FAILED; + break; + case UwbUciConstants.STATUS_CODE_RANGING_RX_MAC_DEC_FAILED: + mRangingStatus = UwbStatsLog.UWB_START_RANGING__STATUS__RX_MAC_DEC_FAILED; + break; + case UwbUciConstants.STATUS_CODE_RANGING_RX_MAC_IE_DEC_FAILED: + mRangingStatus = UwbStatsLog.UWB_START_RANGING__STATUS__RX_MAC_IE_DEC_FAILED; + break; + case UwbUciConstants.STATUS_CODE_RANGING_RX_MAC_IE_MISSING: + mRangingStatus = UwbStatsLog.UWB_START_RANGING__STATUS__RX_MAC_IE_MISSING; + break; + case UwbUciConstants.STATUS_CODE_INVALID_PARAM: + case UwbUciConstants.STATUS_CODE_INVALID_RANGE: + case UwbUciConstants.STATUS_CODE_INVALID_MESSAGE_SIZE: + mRangingStatus = UwbStatsLog.UWB_START_RANGING__STATUS__RANGING_BAD_PARAMS; + break; + } + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -249,7 +286,12 @@ public class UwbMetrics { session.mHasValidRangingSinceStart = false; return; } + session.convertRangingStatus(status); session.mStartTimeSinceBootMs = mUwbInjector.getElapsedSinceBootMillis(); + UwbStatsLog.write(UwbStatsLog.UWB_RANGING_START, uwbSession.getProfileType(), + session.mStsType, session.mIsInitiator, + session.mIsController, session.mIsDiscoveredByFramework, session.mIsOutOfBand, + session.mRangingStatus); } } @@ -374,8 +416,7 @@ public class UwbMetrics { session.mRangingCount++; } - int rangingStatus = measurement.getRangingStatus(); - if (rangingStatus != UwbUciConstants.STATUS_CODE_OK) { + if (!measurement.isStatusCodeOk()) { return; } diff --git a/service/java/com/android/server/uwb/UwbServiceCore.java b/service/java/com/android/server/uwb/UwbServiceCore.java index fa3ca1a5..f4b5f2b2 100644 --- a/service/java/com/android/server/uwb/UwbServiceCore.java +++ b/service/java/com/android/server/uwb/UwbServiceCore.java @@ -606,6 +606,14 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification, return status; } + public void rangingRoundsUpdateDtTag(SessionHandle sessionHandle, + PersistableBundle params) throws RemoteException { + if (!isUwbEnabled()) { + throw new IllegalStateException("Uwb is not enabled"); + } + mSessionManager.rangingRoundsUpdateDtTag(sessionHandle, params); + } + private class EnableDisableTask extends Handler { EnableDisableTask(Looper looper) { diff --git a/service/java/com/android/server/uwb/UwbServiceImpl.java b/service/java/com/android/server/uwb/UwbServiceImpl.java index a97eaea4..63dbb030 100644 --- a/service/java/com/android/server/uwb/UwbServiceImpl.java +++ b/service/java/com/android/server/uwb/UwbServiceImpl.java @@ -19,6 +19,7 @@ package com.android.server.uwb; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.annotation.NonNull; +import android.app.admin.SecurityLog; import android.content.AttributionSource; import android.content.BroadcastReceiver; import android.content.Context; @@ -29,6 +30,7 @@ import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; +import android.os.UserManager; import android.provider.Settings; import android.util.Log; import android.uwb.IUwbAdapter; @@ -66,6 +68,8 @@ public class UwbServiceImpl extends IUwbAdapter.Stub { private final UwbSettingsStore mUwbSettingsStore; private final UwbServiceCore mUwbServiceCore; + private boolean mUwbUserRestricted; + UwbServiceImpl(@NonNull Context context, @NonNull UwbInjector uwbInjector) { mContext = context; @@ -73,6 +77,8 @@ public class UwbServiceImpl extends IUwbAdapter.Stub { mUwbSettingsStore = uwbInjector.getUwbSettingsStore(); mUwbServiceCore = uwbInjector.getUwbServiceCore(); registerAirplaneModeReceiver(); + mUwbUserRestricted = isUwbUserRestricted(); + registerUserRestrictionsReceiver(); } /** @@ -129,6 +135,31 @@ public class UwbServiceImpl extends IUwbAdapter.Stub { "UwbService"); } + private void onUserRestrictionsChanged() { + if (mUwbUserRestricted == isUwbUserRestricted()) { + return; + } + + Log.i(TAG, "Disallow UWB user restriction changed from " + mUwbUserRestricted + " to " + + !mUwbUserRestricted + "."); + mUwbUserRestricted = !mUwbUserRestricted; + logSecurityUwbUserRestrictionChanged(mUwbUserRestricted); + + try { + mUwbServiceCore.setEnabled(isUwbEnabled()); + } catch (Exception e) { + Log.e(TAG, "Unable to set UWB Adapter state.", e); + } + } + + private void logSecurityUwbUserRestrictionChanged(boolean restricted) { + if (restricted) { + SecurityLog.writeEvent(SecurityLog.TAG_USER_RESTRICTION_ADDED); + } else { + SecurityLog.writeEvent(SecurityLog.TAG_USER_RESTRICTION_ADDED); + } + } + @Override public void registerAdapterStateCallbacks(IUwbAdapterStateCallbacks adapterStateCallbacks) throws RemoteException { @@ -283,6 +314,17 @@ public class UwbServiceImpl extends IUwbAdapter.Stub { mUwbServiceCore.sendData(sessionHandle, remoteDeviceAddress, params, data); } + // TODO: Add @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) after ag/19901449 + @Override + public void onRangingRoundsUpdateDtTag(SessionHandle sessionHandle, + PersistableBundle parameters) throws RemoteException { + if (!SdkLevel.isAtLeastU()) { + throw new UnsupportedOperationException(); + } + enforceUwbPrivilegedPermission(); + mUwbServiceCore.rangingRoundsUpdateDtTag(sessionHandle, parameters); + } + @Override public synchronized int getAdapterState() throws RemoteException { return mUwbServiceCore.getAdapterState(); @@ -292,7 +334,8 @@ public class UwbServiceImpl extends IUwbAdapter.Stub { public synchronized void setEnabled(boolean enabled) throws RemoteException { enforceUwbPrivilegedPermission(); persistUwbToggleState(enabled); - // Shell command from rooted shell, we allow UWB toggle on even if APM mode is on. + // Shell command from rooted shell, we allow UWB toggle on even if APM mode and + // user restriction are on. if (Binder.getCallingUid() == Process.ROOT_UID) { mUwbServiceCore.setEnabled(isUwbToggleEnabled()); return; @@ -396,7 +439,7 @@ public class UwbServiceImpl extends IUwbAdapter.Stub { @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, @NonNull String[] args) { - UwbShellCommand shellCommand = mUwbInjector.makeUwbShellCommand(this); + UwbShellCommand shellCommand = mUwbInjector.makeUwbShellCommand(this); return shellCommand.exec(this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); } @@ -415,9 +458,25 @@ public class UwbServiceImpl extends IUwbAdapter.Stub { Settings.Global.AIRPLANE_MODE_ON, 0) == 1; } - /** Returns true if UWB is enabled - based on UWB and APM toggle */ + /** Returns true if UWB has user restriction set. */ + private boolean isUwbUserRestricted() { + if (!SdkLevel.isAtLeastU()) { + return false; // older platforms did not have a uwb user restriction. + } + final long ident = Binder.clearCallingIdentity(); + try { + return mUwbInjector.getUserManager().getUserRestrictions().getBoolean( + // Not available on tm-mainline-prod + // UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO); + "no_ultra_wideband_radio"); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + /** Returns true if UWB is enabled - based on UWB, APM toggle and user restriction */ private boolean isUwbEnabled() { - return isUwbToggleEnabled() && !isAirplaneModeOn(); + return isUwbToggleEnabled() && !isAirplaneModeOn() && !isUwbUserRestricted(); } private void registerAirplaneModeReceiver() { @@ -429,6 +488,18 @@ public class UwbServiceImpl extends IUwbAdapter.Stub { }, new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)); } + private void registerUserRestrictionsReceiver() { + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + onUserRestrictionsChanged(); + } + }, + new IntentFilter(UserManager.ACTION_USER_RESTRICTIONS_CHANGED) + ); + } + private void handleAirplaneModeEvent() { try { mUwbServiceCore.setEnabled(isUwbEnabled()); diff --git a/service/java/com/android/server/uwb/UwbSessionManager.java b/service/java/com/android/server/uwb/UwbSessionManager.java index dad92670..3f9e8184 100644 --- a/service/java/com/android/server/uwb/UwbSessionManager.java +++ b/service/java/com/android/server/uwb/UwbSessionManager.java @@ -17,8 +17,15 @@ package com.android.server.uwb; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; +import static com.android.server.uwb.data.UwbUciConstants.MAC_ADDRESSING_MODE_EXTENDED; +import static com.android.server.uwb.data.UwbUciConstants.MAC_ADDRESSING_MODE_SHORT; +import static com.android.server.uwb.data.UwbUciConstants.RANGING_DEVICE_ROLE_OBSERVER; import static com.android.server.uwb.data.UwbUciConstants.REASON_STATE_CHANGE_WITH_SESSION_MANAGEMENT_COMMANDS; +import static com.android.server.uwb.data.UwbUciConstants.ROUND_USAGE_OWR_AOA_MEASUREMENT; import static com.android.server.uwb.data.UwbUciConstants.UWB_DEVICE_EXT_MAC_ADDRESS_LEN; +import static com.android.server.uwb.data.UwbUciConstants.UWB_DEVICE_SHORT_MAC_ADDRESS_LEN; +import static com.android.server.uwb.data.UwbUciConstants.UWB_SESSION_STATE_ACTIVE; +import static com.android.server.uwb.util.DataTypeConversionUtil.macAddressByteArrayToLong; import static com.google.uwb.support.fira.FiraParams.MULTICAST_LIST_UPDATE_ACTION_ADD; import static com.google.uwb.support.fira.FiraParams.MULTICAST_LIST_UPDATE_ACTION_DELETE; @@ -49,6 +56,7 @@ import android.uwb.UwbAddress; import androidx.annotation.VisibleForTesting; import com.android.server.uwb.advertisement.UwbAdvertiseManager; +import com.android.server.uwb.data.DtTagUpdateRangingRoundsStatus; import com.android.server.uwb.data.UwbMulticastListUpdateStatus; import com.android.server.uwb.data.UwbOwrAoaMeasurement; import com.android.server.uwb.data.UwbRangingData; @@ -56,7 +64,6 @@ import com.android.server.uwb.data.UwbTwoWayMeasurement; import com.android.server.uwb.data.UwbUciConstants; import com.android.server.uwb.jni.INativeUwbManager; import com.android.server.uwb.jni.NativeUwbManager; -import com.android.server.uwb.params.TlvUtil; import com.android.server.uwb.proto.UwbStatsLog; import com.android.server.uwb.util.ArrayUtils; import com.android.server.uwb.util.UwbUtil; @@ -66,6 +73,8 @@ import com.google.uwb.support.ccc.CccOpenRangingParams; import com.google.uwb.support.ccc.CccParams; import com.google.uwb.support.ccc.CccRangingStartedParams; import com.google.uwb.support.ccc.CccStartRangingParams; +import com.google.uwb.support.dltdoa.DlTDoARangingRoundsUpdate; +import com.google.uwb.support.dltdoa.DlTDoARangingRoundsUpdateStatus; import com.google.uwb.support.fira.FiraOpenSessionParams; import com.google.uwb.support.fira.FiraParams; import com.google.uwb.support.fira.FiraRangingReconfigureParams; @@ -74,6 +83,7 @@ import com.google.uwb.support.oemextension.SessionStatus; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.List; @@ -82,7 +92,10 @@ import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; @@ -98,11 +111,13 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification @VisibleForTesting public static final int SESSION_RECONFIG_RANGING = 4; @VisibleForTesting - public static final int SESSION_CLOSE = 5; + public static final int SESSION_DEINIT = 5; @VisibleForTesting public static final int SESSION_ON_DEINIT = 6; @VisibleForTesting public static final int SESSION_SEND_DATA = 7; + @VisibleForTesting + public static final int SESSION_UPDATE_ACTIVE_RR_DT_TAG = 8; // TODO: don't expose the internal field for testing. @VisibleForTesting @@ -197,7 +212,7 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification if (rangingData.getRangingMeasuresType() == UwbUciConstants.RANGING_MEASUREMENT_TYPE_TWO_WAY) { for (UwbTwoWayMeasurement measure : rangingData.getRangingTwoWayMeasures()) { - if (measure.getRangingStatus() == UwbUciConstants.STATUS_CODE_OK) { + if (measure.isStatusCodeOk()) { return false; } } @@ -237,16 +252,18 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification public void onDataReceived( long sessionId, int status, long sequenceNum, byte[] address, int sourceEndPoint, int destEndPoint, byte[] data) { - Log.d(TAG, "onDataReceived - Data: " + UwbUtil.toHexString(data)); + Log.d(TAG, "onDataReceived - address: " + UwbUtil.toHexString(address) + + ", Data: " + UwbUtil.toHexString(data)); - // Size of address is always expected to be 8(EXTENDED_ADDRESS_BYTE_LENGTH). It can contain - // the MacAddress in short format however (2 LSB with MacAddress, 6 MSB zeroed out). + // Size of address in the UCI Packet for DATA_MESSAGE_RCV is always expected to be 8 + // (EXTENDED_ADDRESS_BYTE_LENGTH). It can contain the MacAddress in short format however + // (2 LSB with MacAddress, 6 MSB zeroed out). if (address.length != UWB_DEVICE_EXT_MAC_ADDRESS_LEN) { - Log.e(TAG, "onDataReceived(): Received data for sessionId=" + sessionId + Log.e(TAG, "onDataReceived(): Received data for sessionId = " + sessionId + ", with unexpected MacAddress length = " + address.length); return; } - Long longAddress = ByteBuffer.wrap(address).getLong(); + Long longAddress = macAddressByteArrayToLong(address); ReceivedDataInfo info = new ReceivedDataInfo(); info.sessionId = sessionId; @@ -455,7 +472,7 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification Log.i(TAG, "deinitSession() - sessionId: " + sessionId + ", sessionHandle: " + sessionHandle); UwbSession uwbSession = getUwbSession(sessionId); - mEventTask.execute(SESSION_CLOSE, uwbSession); + mEventTask.execute(SESSION_DEINIT, uwbSession); return; } @@ -556,10 +573,23 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification return; } + if (!isValidUwbSessionForOwrAoaRanging(uwbSession)) { + return; + } + + // Record the OWR Aoa Measurement from the RANGE_DATA_NTF. UwbOwrAoaMeasurement uwbOwrAoaMeasurement = rangingData.getRangingOwrAoaMeasure(); mAdvertiseManager.updateAdvertiseTarget(uwbOwrAoaMeasurement); - byte[] macAddress = uwbOwrAoaMeasurement.getMacAddress(); + byte[] macAddress = getValidMacAddressFromOwrAoaMeasurement( + rangingData, uwbOwrAoaMeasurement); + if (macAddress == null) { + Log.i(TAG, "OwR Aoa UwbSession: Invalid MacAddress for remote device"); + return; + } + uwbSession.setRemoteMacAddress(macAddress); + + // Get any application payload data received in this OWR AOA ranging session and notify it. ReceivedDataInfo receivedDataInfo = getReceivedDataInfo(macAddress); if (receivedDataInfo == null) { return; @@ -571,23 +601,30 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification } if (mAdvertiseManager.isPointedTarget(macAddress)) { - UwbAddress uwbAddress = UwbAddress.fromBytes(TlvUtil.getReverseBytes(macAddress)); + UwbAddress uwbAddress = UwbAddress.fromBytes(macAddress); mSessionNotificationManager.onDataReceived( uwbSession, uwbAddress, new PersistableBundle(), receivedDataInfo.payload); + mAdvertiseManager.removeAdvertiseTarget(macAddress); } } + @Nullable + private byte[] getValidMacAddressFromOwrAoaMeasurement(UwbRangingData rangingData, + UwbOwrAoaMeasurement uwbOwrAoaMeasurement) { + byte[] macAddress = uwbOwrAoaMeasurement.getMacAddress(); + if (rangingData.getMacAddressMode() == MAC_ADDRESSING_MODE_SHORT) { + return (macAddress.length == UWB_DEVICE_SHORT_MAC_ADDRESS_LEN) ? macAddress : null; + } else if (rangingData.getMacAddressMode() == MAC_ADDRESSING_MODE_EXTENDED) { + return (macAddress.length == UWB_DEVICE_EXT_MAC_ADDRESS_LEN) ? macAddress : null; + } + return null; + } + /** Get any received data for the given device MacAddress */ @VisibleForTesting public ReceivedDataInfo getReceivedDataInfo(byte[] macAddress) { - /* Extend the size of addr because addr size from onDataReceived() is always 8 */ - byte[] extendedAddr = new byte[] {0, 0, 0, 0, 0, 0, 0, 0}; - for (int i = 0; i < macAddress.length; i++) { - extendedAddr[i] = macAddress[i]; - } - - Long longAddress = ByteBuffer.wrap(extendedAddr).getLong(); - return mReceivedDataMap.get(longAddress); + // Convert the macAddress to a long as the address could be in short or extended format. + return mReceivedDataMap.get(macAddressByteArrayToLong(macAddress)); } public boolean isExistedSession(SessionHandle sessionHandle) { @@ -711,14 +748,94 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification public byte[] data; } + private static final class RangingRoundsUpdateDtTagInfo { + public SessionHandle sessionHandle; + public PersistableBundle params; + } + + /** DT Tag ranging round update */ + public void rangingRoundsUpdateDtTag(SessionHandle sessionHandle, + PersistableBundle bundle) { + RangingRoundsUpdateDtTagInfo info = new RangingRoundsUpdateDtTagInfo(); + info.sessionHandle = sessionHandle; + info.params = bundle; + + mEventTask.execute(SESSION_UPDATE_ACTIVE_RR_DT_TAG, info); + } + + /** Handle ranging rounds update for DT Tag */ + public void handleRangingRoundsUpdateDtTag(RangingRoundsUpdateDtTagInfo info) { + SessionHandle sessionHandle = info.sessionHandle; + Integer sessionId = getSessionId(sessionHandle); + if (sessionId == null) { + Log.i(TAG, "UwbSessionId not found"); + return; + } + UwbSession uwbSession = getUwbSession(sessionId); + if (uwbSession == null) { + Log.i(TAG, "UwbSession not found"); + return; + } + DlTDoARangingRoundsUpdate dlTDoARangingRoundsUpdate = DlTDoARangingRoundsUpdate + .fromBundle(info.params); + + if (dlTDoARangingRoundsUpdate.getSessionId() != getSessionId(sessionHandle)) { + throw new IllegalArgumentException("Wrong session ID"); + } + + FutureTask<DtTagUpdateRangingRoundsStatus> rangingRoundsUpdateTask = new FutureTask<>( + () -> { + synchronized (uwbSession.getWaitObj()) { + return mNativeUwbManager.sessionUpdateActiveRoundsDtTag( + (int) dlTDoARangingRoundsUpdate.getSessionId(), + dlTDoARangingRoundsUpdate.getNoOfActiveRangingRounds(), + dlTDoARangingRoundsUpdate.getRangingRoundIndexes(), + uwbSession.getChipId()); + } + } + ); + + DtTagUpdateRangingRoundsStatus status = new DtTagUpdateRangingRoundsStatus( + UwbUciConstants.STATUS_CODE_ERROR_ROUND_INDEX_NOT_ACTIVATED, + 0, + new byte[]{}); + + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.submit(rangingRoundsUpdateTask); + try { + status = rangingRoundsUpdateTask.get(IUwbAdapter + .RANGING_ROUNDS_UPDATE_DT_TAG_THRESHOLD_MS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + Log.i(TAG, "Failed to update ranging rounds for Dt tag - status : TIMEOUT"); + executor.shutdownNow(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + PersistableBundle params = new DlTDoARangingRoundsUpdateStatus.Builder() + .setStatus(status.getStatus()) + .setNoOfActiveRangingRounds(status.getNoOfActiveRangingRounds()) + .setRangingRoundIndexes(status.getRangingRoundIndexes()) + .build() + .toBundle(); + mSessionNotificationManager.onRangingRoundsUpdateStatus(uwbSession, params); + } + void removeSession(UwbSession uwbSession) { if (uwbSession != null) { uwbSession.getBinder().unlinkToDeath(uwbSession, 0); removeFromNonPrivilegedUidToFiraSessionTableIfNecessary(uwbSession); + removeAdvertiserData(uwbSession); mSessionTable.remove(uwbSession.getSessionId()); } } + private void removeAdvertiserData(UwbSession uwbSession) { + byte[] remoteMacAddress = uwbSession.getRemoteMacAddress(); + if (remoteMacAddress != null) { + mAdvertiseManager.removeAdvertiseTarget(remoteMacAddress); + } + } + void addToNonPrivilegedUidToFiraSessionTableIfNecessary(@NonNull UwbSession uwbSession) { if (getSessionType(uwbSession.getProtocolName()) == UwbUciConstants.SESSION_TYPE_RANGING) { AttributionSource nonPrivilegedAppAttrSource = @@ -806,9 +923,9 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification break; } - case SESSION_CLOSE: { + case SESSION_DEINIT: { UwbSession uwbSession = (UwbSession) msg.obj; - handleClose(uwbSession); + handleDeInit(uwbSession); break; } @@ -825,6 +942,13 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification break; } + case SESSION_UPDATE_ACTIVE_RR_DT_TAG: { + Log.d(TAG, "SESSION_UPDATE_ACTIVE_RR_DT_TAG"); + RangingRoundsUpdateDtTagInfo info = (RangingRoundsUpdateDtTagInfo) msg.obj; + handleRangingRoundsUpdateDtTag(info); + break; + } + default: { Log.d(TAG, "EventTask : Undefined Task"); break; @@ -1031,6 +1155,7 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification } // Reset all UWB session timers when the session is stopped. uwbSession.stopTimers(); + removeAdvertiserData(uwbSession); } private void handleReconfigure(UwbSession uwbSession, @Nullable Params param, @@ -1181,9 +1306,9 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification } } - private void handleClose(UwbSession uwbSession) { + private void handleDeInit(UwbSession uwbSession) { // TODO(b/211445008): Consolidate to a single uwb thread. - FutureTask<Integer> closeTask = new FutureTask<>( + FutureTask<Integer> deInitTask = new FutureTask<>( (Callable<Integer>) () -> { int status = UwbUciConstants.STATUS_CODE_FAILED; synchronized (uwbSession.getWaitObj()) { @@ -1202,7 +1327,7 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification int status = UwbUciConstants.STATUS_CODE_FAILED; try { - status = mUwbInjector.runTaskOnSingleThreadExecutor(closeTask, + status = mUwbInjector.runTaskOnSingleThreadExecutor(deInitTask, IUwbAdapter.RANGING_SESSION_CLOSE_THRESHOLD_MS); } catch (TimeoutException e) { Log.i(TAG, "Failed to Stop Ranging - status : TIMEOUT"); @@ -1212,7 +1337,7 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification } mUwbMetrics.logRangingCloseEvent(uwbSession, status); - // Reset all UWB session timers when the session is closed. + // Reset all UWB session timers when the session is de-initialized (ie, closed). uwbSession.stopTimers(); removeSession(uwbSession); Log.i(TAG, "deinit finish : status :" + status); @@ -1251,6 +1376,14 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification FutureTask<Integer> sendDataTask = new FutureTask<>((Callable<Integer>) () -> { int sendDataStatus = UwbUciConstants.STATUS_CODE_FAILED; synchronized (uwbSession.getWaitObj()) { + if (!isValidUwbSessionForApplicationDataTransfer(uwbSession)) { + sendDataStatus = UwbUciConstants.STATUS_CODE_FAILED; + Log.i(TAG, "UwbSession not in active state"); + mSessionNotificationManager.onDataSendFailed( + uwbSession, sendDataInfo.remoteDeviceAddress, sendDataStatus, + sendDataInfo.params); + return sendDataStatus; + } if (!isValidSendDataInfo(sendDataInfo)) { sendDataStatus = UwbUciConstants.STATUS_CODE_INVALID_PARAM; mSessionNotificationManager.onDataSendFailed( @@ -1296,6 +1429,31 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification } } + private boolean isValidUwbSessionForOwrAoaRanging(UwbSession uwbSession) { + Params params = uwbSession.getParams(); + if (params instanceof FiraOpenSessionParams) { + FiraOpenSessionParams firaParams = (FiraOpenSessionParams) params; + if (firaParams.getRangingRoundUsage() != ROUND_USAGE_OWR_AOA_MEASUREMENT) { + Log.i(TAG, "OwR Aoa UwbSession: Invalid ranging round usage value = " + + firaParams.getRangingRoundUsage()); + return false; + } + if (firaParams.getDeviceRole() != RANGING_DEVICE_ROLE_OBSERVER) { + Log.i(TAG, "OwR Aoa UwbSession: Invalid device role value = " + + firaParams.getDeviceRole()); + return false; + } + return true; + } + return false; + } + + private boolean isValidUwbSessionForApplicationDataTransfer(UwbSession uwbSession) { + // The session state must be SESSION_STATE_ACTIVE, as that's required to transmit or receive + // application data. + return uwbSession != null && uwbSession.getSessionState() == UWB_SESSION_STATE_ACTIVE; + } + private boolean isValidSendDataInfo(SendDataInfo sendDataInfo) { if (sendDataInfo.data == null) { return false; @@ -1325,6 +1483,7 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification private final AttributionSource mAttributionSource; private final SessionHandle mSessionHandle; private final int mSessionId; + private byte[] mRemoteMacAddress; private final IUwbRangingCallbacks mIUwbRangingCallbacks; private final String mProtocolName; private final IBinder mIBinder; @@ -1512,6 +1671,14 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification this.mSessionState = state; } + public byte[] getRemoteMacAddress() { + return mRemoteMacAddress; + } + + public void setRemoteMacAddress(byte[] remoteMacAddress) { + this.mRemoteMacAddress = Arrays.copyOf(remoteMacAddress, remoteMacAddress.length); + } + public void setMulticastListUpdateStatus( UwbMulticastListUpdateStatus multicastListUpdateStatus) { mMulticastListUpdateStatus = multicastListUpdateStatus; diff --git a/service/java/com/android/server/uwb/UwbSessionNotificationManager.java b/service/java/com/android/server/uwb/UwbSessionNotificationManager.java index d7bf201e..0c095dd8 100644 --- a/service/java/com/android/server/uwb/UwbSessionNotificationManager.java +++ b/service/java/com/android/server/uwb/UwbSessionNotificationManager.java @@ -30,6 +30,7 @@ import android.uwb.SessionHandle; import android.uwb.UwbAddress; import com.android.server.uwb.UwbSessionManager.UwbSession; +import com.android.server.uwb.data.UwbDlTDoAMeasurement; import com.android.server.uwb.data.UwbOwrAoaMeasurement; import com.android.server.uwb.data.UwbRangingData; import com.android.server.uwb.data.UwbTwoWayMeasurement; @@ -40,6 +41,7 @@ import com.android.server.uwb.util.UwbUtil; import com.google.uwb.support.base.Params; import com.google.uwb.support.ccc.CccParams; import com.google.uwb.support.ccc.CccRangingReconfiguredParams; +import com.google.uwb.support.dltdoa.DlTDoAMeasurement; import com.google.uwb.support.fira.FiraOpenSessionParams; import com.google.uwb.support.fira.FiraParams; import com.google.uwb.support.oemextension.RangingReportMetadata; @@ -375,12 +377,29 @@ public class UwbSessionNotificationManager { } } + /** Notify the response for Ranging rounds update status for Dt Tag. */ + public void onRangingRoundsUpdateStatus( + UwbSession uwbSession, PersistableBundle parameters) { + SessionHandle sessionHandle = uwbSession.getSessionHandle(); + IUwbRangingCallbacks uwbRangingCallbacks = uwbSession.getIUwbRangingCallbacks(); + try { + uwbRangingCallbacks.onRangingRoundsUpdateDtTagStatus(sessionHandle, + parameters); + Log.i(TAG, "IUwbRangingCallbacks - onRangingRoundsUpdateDtTagStatus"); + } catch (Exception e) { + Log.e(TAG, "IUwbRangingCallbacks - onRangingRoundsUpdateDtTagStatus : Failed"); + e.printStackTrace(); + } + } + private static RangingReport getRangingReport( @NonNull UwbRangingData rangingData, String protocolName, Params sessionParams, long elapsedRealtimeNanos) { if (rangingData.getRangingMeasuresType() != UwbUciConstants.RANGING_MEASUREMENT_TYPE_TWO_WAY && rangingData.getRangingMeasuresType() - != UwbUciConstants.RANGING_MEASUREMENT_TYPE_OWR_AOA) { + != UwbUciConstants.RANGING_MEASUREMENT_TYPE_OWR_AOA + && rangingData.getRangingMeasuresType() + != UwbUciConstants.RANGING_MEASUREMENT_TYPE_DL_TDOA) { return null; } boolean isAoaAzimuthEnabled = true; @@ -439,7 +458,7 @@ public class UwbSessionNotificationManager { List<RangingMeasurement> rangingMeasurements = new ArrayList<>(); UwbTwoWayMeasurement[] uwbTwoWayMeasurement = rangingData.getRangingTwoWayMeasures(); for (int i = 0; i < rangingData.getNoOfRangingMeasures(); ++i) { - int rangingStatus = uwbTwoWayMeasurement[i].getRangingStatus(); + int rangingStatus = uwbTwoWayMeasurement[i].convertStatusCode(); RangingMeasurement.Builder rangingMeasurementBuilder = buildRangingMeasurement( uwbTwoWayMeasurement[i].getMacAddress(), rangingStatus, @@ -449,7 +468,7 @@ public class UwbSessionNotificationManager { rangingMeasurementBuilder.setRssiDbm(rssi); } - if (rangingStatus == FiraParams.STATUS_CODE_OK) { + if (uwbTwoWayMeasurement[i].isStatusCodeOk()) { // Distance measurement is mandatory rangingMeasurementBuilder.setDistanceMeasurement( buildDistanceMeasurement(uwbTwoWayMeasurement[i].getDistance())); @@ -514,6 +533,57 @@ public class UwbSessionNotificationManager { } rangingReportBuilder.addMeasurement(rangingMeasurementBuilder.build()); + } else if (rangingData.getRangingMeasuresType() + == UwbUciConstants.RANGING_MEASUREMENT_TYPE_DL_TDOA) { + List<RangingMeasurement> rangingMeasurements = new ArrayList<>(); + UwbDlTDoAMeasurement[] uwbDlTDoAMeasurements = rangingData.getUwbDlTDoAMeasurements(); + for (int i = 0; i < rangingData.getNoOfRangingMeasures(); ++i) { + int rangingStatus = uwbDlTDoAMeasurements[i].getStatus(); + + RangingMeasurement.Builder rangingMeasurementBuilder = buildRangingMeasurement( + uwbDlTDoAMeasurements[i].getMacAddress(), rangingStatus, + elapsedRealtimeNanos, uwbDlTDoAMeasurements[i].getNLoS()); + int rssi = uwbDlTDoAMeasurements[i].getRssi(); + if (rssi < 0) { + rangingMeasurementBuilder.setRssiDbm(rssi); + } + if (rangingStatus == FiraParams.STATUS_CODE_OK) { + AngleOfArrivalMeasurement angleOfArrivalMeasurement = + computeAngleOfArrivalMeasurement( + isAoaAzimuthEnabled, isAoaElevationEnabled, + uwbDlTDoAMeasurements[i].getAoaAzimuth(), + uwbDlTDoAMeasurements[i].getAoaAzimuthFom(), + uwbDlTDoAMeasurements[i].getAoaElevation(), + uwbDlTDoAMeasurements[i].getAoaElevationFom()); + if (angleOfArrivalMeasurement != null) { + rangingMeasurementBuilder.setAngleOfArrivalMeasurement( + angleOfArrivalMeasurement); + } + } + DlTDoAMeasurement dlTDoAMeasurement = new DlTDoAMeasurement.Builder() + .setMessageType(uwbDlTDoAMeasurements[i].getMessageType()) + .setMessageControl(uwbDlTDoAMeasurements[i].getMessageControl()) + .setBlockIndex(uwbDlTDoAMeasurements[i].getBlockIndex()) + .setNLoS(uwbDlTDoAMeasurements[i].getNLoS()) + .setTxTimestamp(uwbDlTDoAMeasurements[i].getTxTimestamp()) + .setRxTimestamp(uwbDlTDoAMeasurements[i].getRxTimestamp()) + .setAnchorCfo(uwbDlTDoAMeasurements[i].getAnchorCfo()) + .setCfo(uwbDlTDoAMeasurements[i].getCfo()) + .setInitiatorReplyTime(uwbDlTDoAMeasurements[i].getInitiatorReplyTime()) + .setResponderReplyTime(uwbDlTDoAMeasurements[i].getResponderReplyTime()) + .setInitiatorResponderTof(uwbDlTDoAMeasurements[i] + .getInitiatorResponderTof()) + .setAnchorLocation(uwbDlTDoAMeasurements[i].getAnchorLocation()) + .setActiveRangingRounds(uwbDlTDoAMeasurements[i].getActiveRangingRounds()) + .build(); + + rangingMeasurementBuilder.setRangingMeasurementMetadata( + dlTDoAMeasurement.toBundle()); + + rangingMeasurements.add(rangingMeasurementBuilder.build()); + } + + rangingReportBuilder.addMeasurements(rangingMeasurements); } return rangingReportBuilder.build(); } diff --git a/service/java/com/android/server/uwb/UwbShellCommand.java b/service/java/com/android/server/uwb/UwbShellCommand.java index 599c0e66..af5b0c3d 100644 --- a/service/java/com/android/server/uwb/UwbShellCommand.java +++ b/service/java/com/android/server/uwb/UwbShellCommand.java @@ -329,6 +329,9 @@ public class UwbShellCommand extends BasicShellCommandHandler { public void onServiceDiscovered(SessionHandle sessionHandle, PersistableBundle params) {} public void onServiceConnected(SessionHandle sessionHandle, PersistableBundle params) {} + + public void onRangingRoundsUpdateDtTagStatus(SessionHandle sessionHandle, + PersistableBundle params) {} } diff --git a/service/java/com/android/server/uwb/UwbTestUtils.java b/service/java/com/android/server/uwb/UwbTestUtils.java index 53c02de9..c5358841 100644 --- a/service/java/com/android/server/uwb/UwbTestUtils.java +++ b/service/java/com/android/server/uwb/UwbTestUtils.java @@ -16,6 +16,8 @@ package com.android.server.uwb; +import static com.android.server.uwb.data.UwbUciConstants.MAC_ADDRESSING_MODE_SHORT; +import static com.android.server.uwb.data.UwbUciConstants.RANGING_MEASUREMENT_TYPE_DL_TDOA; import static com.android.server.uwb.data.UwbUciConstants.RANGING_MEASUREMENT_TYPE_OWR_AOA; import static com.android.server.uwb.data.UwbUciConstants.RANGING_MEASUREMENT_TYPE_TWO_WAY; import static com.android.server.uwb.util.UwbUtil.convertFloatToQFormat; @@ -30,31 +32,34 @@ import android.uwb.RangingMeasurement; import android.uwb.RangingReport; import android.uwb.UwbAddress; +import com.android.server.uwb.data.UwbDlTDoAMeasurement; import com.android.server.uwb.data.UwbOwrAoaMeasurement; import com.android.server.uwb.data.UwbRangingData; import com.android.server.uwb.data.UwbTwoWayMeasurement; import com.android.server.uwb.params.TlvUtil; +import com.google.uwb.support.dltdoa.DlTDoAMeasurement; import com.google.uwb.support.fira.FiraParams; import com.google.uwb.support.oemextension.RangingReportMetadata; public class UwbTestUtils { public static final int TEST_SESSION_ID = 7; public static final int TEST_SESSION_ID_2 = 8; - public static final byte[] PEER_SHORT_MAC_ADDRESS = {0x1, 0x3}; + public static final byte[] PEER_SHORT_MAC_ADDRESS = {0x35, 0x37}; + public static final byte[] PEER_EXTENDED_SHORT_MAC_ADDRESS = + {0x35, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; public static final byte[] PEER_EXTENDED_MAC_ADDRESS = - new byte[] {0x12, 0x14, 0x16, 0x18, 0x31, 0x33, 0x35, 0x37}; - public static final byte[] PEER_EXTENDED_MAC_ADDRESS_REVERSE_BYTES = - new byte[] {0x37, 0x35, 0x33, 0x31, 0x18, 0x16, 0x14, 0x12}; + {0x12, 0x14, 0x16, 0x18, 0x31, 0x33, 0x35, 0x37}; public static final byte[] PEER_EXTENDED_MAC_ADDRESS_2 = - new byte[] {0x2, 0x4, 0x6, 0x8, 0x1, 0x3, 0x5, 0x7}; - public static final byte[] PEER_BAD_MAC_ADDRESS = new byte[] {0x12, 0x14, 0x16, 0x18}; - public static final byte[] PEER_EXTENDED_SHORT_MAC_ADDRESS = - new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x37}; - public static final UwbAddress PEER_UWB_ADDRESS = UwbAddress.fromBytes( - PEER_EXTENDED_MAC_ADDRESS_REVERSE_BYTES); + {0x2, 0x4, 0x6, 0x8, 0x1, 0x3, 0x5, 0x7}; + public static final byte[] PEER_BAD_MAC_ADDRESS = {0x12, 0x14, 0x16, 0x18}; + public static final UwbAddress PEER_EXTENDED_UWB_ADDRESS = UwbAddress.fromBytes( + PEER_EXTENDED_MAC_ADDRESS); + public static final UwbAddress PEER_SHORT_UWB_ADDRESS = UwbAddress.fromBytes( + PEER_SHORT_MAC_ADDRESS); public static final PersistableBundle PERSISTABLE_BUNDLE = new PersistableBundle(); public static final byte[] DATA_PAYLOAD = new byte[] {0x13, 0x15, 0x18}; + public static final int RANGING_MEASUREMENT_TYPE_UNDEFINED = 0; // RFU in spec private static final byte[] TEST_RAW_NTF_DATA = {0x10, 0x01, 0x05}; private static final long TEST_SEQ_COUNTER = 5; @@ -76,20 +81,35 @@ public class UwbTestUtils { private static final int TEST_FRAME_SEQUENCE_NUMBER = 1; private static final int TEST_BLOCK_IDX = 100; private static final int TEST_SLOT_IDX = 10; + private static final int TEST_MESSAGE_TYPE = 1; + private static final int TEST_MESSAGE_CONTROL = 1331; + private static final int TEST_BLOCK_INDEX = 5; + private static final int TEST_ROUND_INDEX = 1; + private static final long TEST_TIMESTAMP = 500_000L; + private static final int TEST_ANCHOR_CFO = 100; + private static final int TEST_CFO = 200; + private static final long TEST_INTIATOR_REPLY_TIME = 500_000L; + private static final long TEST_RESPONDER_REPLY_TIME = 300_000L; + private static final int TEST_INITIATOR_RESPONDER_TOF = 500; + private static final byte[] TEST_ANCHOR_LOCATION = {0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}; + private static final byte[] TEST_ACTIVE_RANGING_ROUNDS = {0x02, 0x08}; private static final int TEST_RSSI = 150; private UwbTestUtils() {} /** Build UwbRangingData for all Ranging Measurement Type(s). */ public static UwbRangingData generateRangingData( - int rangingMeasurementType, int rangingStatus) { + int rangingMeasurementType, int macAddressingMode, int rangingStatus) { switch (rangingMeasurementType) { case RANGING_MEASUREMENT_TYPE_TWO_WAY: return generateTwoWayMeasurementRangingData(rangingStatus); case RANGING_MEASUREMENT_TYPE_OWR_AOA: - return generateOwrAoaMeasurementRangingData(rangingStatus); + return generateOwrAoaMeasurementRangingData(macAddressingMode, rangingStatus); + case RANGING_MEASUREMENT_TYPE_DL_TDOA: + return generateDlTDoAMeasurementRangingData(macAddressingMode, rangingStatus); default: - return null; + return generateDefaultRangingData(); } } @@ -109,30 +129,67 @@ public class UwbTestUtils { TEST_RAW_NTF_DATA); } - private static UwbRangingData generateOwrAoaMeasurementRangingData(int rangingStatus) { + private static UwbRangingData generateOwrAoaMeasurementRangingData( + int macAddressingMode, int rangingStatus) { final int noOfRangingMeasures = 1; + byte[] macAddress = (macAddressingMode == MAC_ADDRESSING_MODE_SHORT) + ? PEER_SHORT_MAC_ADDRESS : PEER_EXTENDED_MAC_ADDRESS; final UwbOwrAoaMeasurement uwbOwrAoaMeasurement = new UwbOwrAoaMeasurement( - PEER_EXTENDED_MAC_ADDRESS, rangingStatus, TEST_LOS, + macAddress, rangingStatus, TEST_LOS, TEST_FRAME_SEQUENCE_NUMBER, TEST_BLOCK_IDX, convertFloatToQFormat(TEST_AOA_AZIMUTH, 9, 7), TEST_AOA_AZIMUTH_FOM, convertFloatToQFormat(TEST_AOA_ELEVATION, 9, 7), TEST_AOA_ELEVATION_FOM); return new UwbRangingData(TEST_SEQ_COUNTER, TEST_SESSION_ID, TEST_RCR_INDICATION, TEST_CURR_RANGING_INTERVAL, RANGING_MEASUREMENT_TYPE_OWR_AOA, - TEST_MAC_ADDRESS_MODE, noOfRangingMeasures, uwbOwrAoaMeasurement, + macAddressingMode, noOfRangingMeasures, uwbOwrAoaMeasurement, + TEST_RAW_NTF_DATA); + } + + private static UwbRangingData generateDlTDoAMeasurementRangingData( + int macAddressingMode, int rangingStatus) { + final int noOfRangingMeasures = 1; + byte[] macAddress = (macAddressingMode == MAC_ADDRESSING_MODE_SHORT) + ? PEER_SHORT_MAC_ADDRESS : PEER_EXTENDED_MAC_ADDRESS; + final UwbDlTDoAMeasurement[] uwbDlTDoAMeasurements = + new UwbDlTDoAMeasurement[noOfRangingMeasures]; + uwbDlTDoAMeasurements[0] = new UwbDlTDoAMeasurement(macAddress, rangingStatus, + TEST_MESSAGE_TYPE, TEST_MESSAGE_CONTROL, TEST_BLOCK_INDEX, TEST_ROUND_INDEX, + TEST_LOS, convertFloatToQFormat(TEST_AOA_AZIMUTH, 9, 7), + TEST_AOA_AZIMUTH_FOM, convertFloatToQFormat(TEST_AOA_ELEVATION, 9, 7), + TEST_AOA_ELEVATION_FOM, TEST_RSSI, TEST_TIMESTAMP, TEST_TIMESTAMP, TEST_ANCHOR_CFO, + TEST_CFO, TEST_INTIATOR_REPLY_TIME, TEST_RESPONDER_REPLY_TIME, + TEST_INITIATOR_RESPONDER_TOF, TEST_ANCHOR_LOCATION, TEST_ACTIVE_RANGING_ROUNDS); + + return new UwbRangingData(TEST_SEQ_COUNTER, TEST_SESSION_ID, + TEST_RCR_INDICATION, TEST_CURR_RANGING_INTERVAL, RANGING_MEASUREMENT_TYPE_DL_TDOA, + macAddressingMode, noOfRangingMeasures, uwbDlTDoAMeasurements, + TEST_RAW_NTF_DATA); + } + + // Create a UwbRangingData with no measurements, for negative test cases (example: incorrect + // ranging measurement type). + private static UwbRangingData generateDefaultRangingData() { + final int noOfRangingMeasures = 0; + final UwbTwoWayMeasurement[] uwbEmptyTwoWayMeasurements = + new UwbTwoWayMeasurement[noOfRangingMeasures]; + return new UwbRangingData(TEST_SEQ_COUNTER, TEST_SESSION_ID, + TEST_RCR_INDICATION, TEST_CURR_RANGING_INTERVAL, RANGING_MEASUREMENT_TYPE_UNDEFINED, + TEST_MAC_ADDRESS_MODE, noOfRangingMeasures, uwbEmptyTwoWayMeasurements, TEST_RAW_NTF_DATA); } // Helper method to generate a UwbRangingData instance and corresponding RangingMeasurement public static Pair<UwbRangingData, RangingReport> generateRangingDataAndRangingReport( - byte[] macAddress, int rangingMeasurementType, + byte[] macAddress, int macAddressingMode, int rangingMeasurementType, boolean isAoaAzimuthEnabled, boolean isAoaElevationEnabled, boolean isDestAoaAzimuthEnabled, boolean isDestAoaElevationEnabled, long elapsedRealtimeNanos) { - UwbRangingData uwbRangingData = generateRangingData(rangingMeasurementType, TEST_STATUS); + UwbRangingData uwbRangingData = generateRangingData(rangingMeasurementType, + macAddressingMode, TEST_STATUS); - // TODO(b/246678053): Add rawNtfData[] for both TwoWay and OWR AoA Measurements.. PersistableBundle rangingReportMetadata = new RangingReportMetadata.Builder() .setSessionId(0) + .setRawNtfData(new byte[] {0x10, 0x01, 0x05}) .build() .toBundle(); @@ -212,6 +269,25 @@ public class UwbTestUtils { .setRangingMeasurementMetadata(rangingMeasurementMetadata); } + if (rangingMeasurementType == RANGING_MEASUREMENT_TYPE_DL_TDOA) { + DlTDoAMeasurement dlTDoAMeasurement = new DlTDoAMeasurement.Builder() + .setMessageType(TEST_MESSAGE_TYPE) + .setMessageControl(TEST_MESSAGE_CONTROL) + .setBlockIndex(TEST_BLOCK_INDEX) + .setNLoS(TEST_LOS) + .setTxTimestamp(TEST_TIMESTAMP) + .setRxTimestamp(TEST_TIMESTAMP) + .setAnchorCfo(TEST_ANCHOR_CFO) + .setCfo(TEST_CFO) + .setInitiatorReplyTime(TEST_INTIATOR_REPLY_TIME) + .setResponderReplyTime(TEST_RESPONDER_REPLY_TIME) + .setInitiatorResponderTof(TEST_INITIATOR_RESPONDER_TOF) + .setAnchorLocation(TEST_ANCHOR_LOCATION) + .setActiveRangingRounds(TEST_ACTIVE_RANGING_ROUNDS) + .build(); + rangingMeasurementBuilder.setRangingMeasurementMetadata(dlTDoAMeasurement.toBundle()); + } + return new RangingReport.Builder() .addMeasurement(rangingMeasurementBuilder.build()) .addRangingReportMetadata(rangingReportMetadata) diff --git a/service/java/com/android/server/uwb/advertisement/UwbAdvertiseManager.java b/service/java/com/android/server/uwb/advertisement/UwbAdvertiseManager.java index df8588e5..0355f604 100644 --- a/service/java/com/android/server/uwb/advertisement/UwbAdvertiseManager.java +++ b/service/java/com/android/server/uwb/advertisement/UwbAdvertiseManager.java @@ -15,7 +15,7 @@ */ package com.android.server.uwb.advertisement; -import static com.android.server.uwb.util.DataTypeConversionUtil.byteArrayToI16; +import static com.android.server.uwb.util.DataTypeConversionUtil.macAddressByteArrayToLong; import androidx.annotation.Nullable; @@ -23,7 +23,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.uwb.UwbInjector; import com.android.server.uwb.data.UwbOwrAoaMeasurement; -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -31,7 +30,7 @@ import java.util.concurrent.ConcurrentHashMap; public class UwbAdvertiseManager { private static final String TAG = "UwbAdvertiseManager"; - private final ConcurrentHashMap<Integer, UwbAdvertiseTarget> mAdvertiseTargetMap = + private final ConcurrentHashMap<Long, UwbAdvertiseTarget> mAdvertiseTargetMap = new ConcurrentHashMap<>(); // TODO(b/246678053): Use overlays to allow OEMs to modify these values. @@ -52,8 +51,9 @@ public class UwbAdvertiseManager { * Check if the current device is pointing at the remote device, from which we have received * One-way Ranging AoA Measurement(s). */ - public boolean isPointedTarget(byte[] macAddress) { - UwbAdvertiseTarget uwbAdvertiseTarget = getAdvertiseTarget(byteArrayToI16(macAddress)); + public boolean isPointedTarget(byte[] macAddressBytes) { + UwbAdvertiseTarget uwbAdvertiseTarget = getAdvertiseTarget( + macAddressByteArrayToLong(macAddressBytes)); if (uwbAdvertiseTarget == null) { return false; } @@ -75,9 +75,21 @@ public class UwbAdvertiseManager { * Store a One-way Ranging AoA Measurement from the remote device in a UWB ranging session. */ public void updateAdvertiseTarget(UwbOwrAoaMeasurement uwbOwrAoaMeasurement) { + // First check if there exists a stale UwbAdvertiseTarget for the device, and remove it. + checkAndRemoveStaleAdvertiseTarget(uwbOwrAoaMeasurement.mMacAddress); + + // Now store the new measurement for the device. updateAdvertiseTargetInfo(uwbOwrAoaMeasurement); } + /** + * Remove all the stored AdvertiseTarget data for the given device. + */ + public void removeAdvertiseTarget(byte[] macAddressBytes) { + long macAddress = macAddressByteArrayToLong(macAddressBytes); + mAdvertiseTargetMap.remove(macAddress); + } + private boolean isWithinCriterionVariance(UwbAdvertiseTarget uwbAdvertiseTarget) { if (!uwbAdvertiseTarget.isVarianceCalculated()) { return false; @@ -93,6 +105,18 @@ public class UwbAdvertiseManager { return true; } + private void checkAndRemoveStaleAdvertiseTarget(byte[] macAddressBytes) { + long macAddress = macAddressByteArrayToLong(macAddressBytes); + UwbAdvertiseTarget uwbAdvertiseTarget = getAdvertiseTarget(macAddress); + if (uwbAdvertiseTarget == null) { + return; + } + + if (!isWithinTimeThreshold(uwbAdvertiseTarget)) { + removeAdvertiseTarget(macAddressBytes); + } + } + private boolean isWithinTimeThreshold(UwbAdvertiseTarget uwbAdvertiseTarget) { long currentTime = mUwbInjector.getElapsedSinceBootMillis(); if (currentTime - uwbAdvertiseTarget.getLastUpdatedTime() > TIME_THRESHOLD) { @@ -104,8 +128,7 @@ public class UwbAdvertiseManager { private UwbAdvertiseTarget updateAdvertiseTargetInfo( UwbOwrAoaMeasurement uwbOwrAoaMeasurement) { long currentTime = mUwbInjector.getElapsedSinceBootMillis(); - ByteBuffer byteBuffer = ByteBuffer.wrap(uwbOwrAoaMeasurement.getMacAddress()); - int macAddress = (int) byteBuffer.getShort(); + long macAddress = macAddressByteArrayToLong(uwbOwrAoaMeasurement.getMacAddress()); UwbAdvertiseTarget advertiseTarget = getOrAddAdvertiseTarget(macAddress); advertiseTarget.calculateAoaVariance(uwbOwrAoaMeasurement); @@ -116,11 +139,11 @@ public class UwbAdvertiseManager { @VisibleForTesting @Nullable - public UwbAdvertiseTarget getAdvertiseTarget(int macAddress) { + public UwbAdvertiseTarget getAdvertiseTarget(long macAddress) { return isAdvertiseTargetExist(macAddress) ? mAdvertiseTargetMap.get(macAddress) : null; } - private UwbAdvertiseTarget getOrAddAdvertiseTarget(int macAddress) { + private UwbAdvertiseTarget getOrAddAdvertiseTarget(long macAddress) { UwbAdvertiseTarget uwbAdvertiseTarget; if (isAdvertiseTargetExist(macAddress)) { uwbAdvertiseTarget = mAdvertiseTargetMap.get(macAddress); @@ -130,18 +153,23 @@ public class UwbAdvertiseManager { return uwbAdvertiseTarget; } - private boolean isAdvertiseTargetExist(int macAddress) { + private boolean isAdvertiseTargetExist(long macAddress) { return mAdvertiseTargetMap.containsKey(macAddress); } - private UwbAdvertiseTarget addAdvertiseTarget(int macAddress) { + private UwbAdvertiseTarget addAdvertiseTarget(long macAddress) { UwbAdvertiseTarget advertiseTarget = new UwbAdvertiseTarget(macAddress); mAdvertiseTargetMap.put(macAddress, advertiseTarget); return advertiseTarget; } - private static class UwbAdvertiseTarget { - private final int mMacAddress; + /** + * Stored Owr Aoa Measurements for the remote devices. The data should be cleared when the + * UWB session is closed. + */ + @VisibleForTesting + public static class UwbAdvertiseTarget { + private final long mMacAddress; private final ArrayList<Double> mRecentAoaAzimuth = new ArrayList<>(); private final ArrayList<Double> mRecentAoaElevation = new ArrayList<>(); private double mVarianceOfAzimuth; @@ -149,7 +177,7 @@ public class UwbAdvertiseManager { private long mLastMeasuredTime; private boolean mIsVarianceCalculated; - private UwbAdvertiseTarget(int macAddress) { + private UwbAdvertiseTarget(long macAddress) { mMacAddress = macAddress; mIsVarianceCalculated = false; } diff --git a/service/java/com/android/server/uwb/config/CapabilityParam.java b/service/java/com/android/server/uwb/config/CapabilityParam.java index 525d6fe7..c1092c7d 100644 --- a/service/java/com/android/server/uwb/config/CapabilityParam.java +++ b/service/java/com/android/server/uwb/config/CapabilityParam.java @@ -51,6 +51,8 @@ public class CapabilityParam { UwbVendorCapabilityTlvTypes.SUPPORTED_RSSI_REPORTING; public static final int SUPPORTED_DIAGNOSTICS = UwbVendorCapabilityTlvTypes.SUPPORTED_DIAGNOSTICS; + public static final int SUPPORTED_MIN_SLOT_DURATION = + UwbVendorCapabilityTlvTypes.SUPPORTED_MIN_SLOT_DURATION; // CCC specific public static final int CCC_SUPPORTED_VERSIONS = diff --git a/service/java/com/android/server/uwb/data/DtTagUpdateRangingRoundsStatus.java b/service/java/com/android/server/uwb/data/DtTagUpdateRangingRoundsStatus.java new file mode 100644 index 00000000..9d4fdcbf --- /dev/null +++ b/service/java/com/android/server/uwb/data/DtTagUpdateRangingRoundsStatus.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 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.uwb.data; + +import java.util.Arrays; + +public class DtTagUpdateRangingRoundsStatus { + private final int mStatus; + private final int mNoOfActiveRangingRounds; + private final byte[] mRangingRoundIndexes; + + public DtTagUpdateRangingRoundsStatus(int status, int noOfActiveRangingRounds, + byte[] rangingRoundIndexes) { + mStatus = status; + mNoOfActiveRangingRounds = noOfActiveRangingRounds; + mRangingRoundIndexes = rangingRoundIndexes; + } + + public int getStatus() { + return mStatus; + } + + public int getNoOfActiveRangingRounds() { + return mNoOfActiveRangingRounds; + } + + public byte[] getRangingRoundIndexes() { + return mRangingRoundIndexes; + } + + @Override + public String toString() { + return "DtTagActiveRoundsStatus { " + + "Status = " + mStatus + + ", NoOfActiveRangingRounds =" + mNoOfActiveRangingRounds + + ", RangingRoundIndexes = " + Arrays.toString(mRangingRoundIndexes) + + '}'; + } +} diff --git a/service/java/com/android/server/uwb/data/UwbDlTDoAMeasurement.java b/service/java/com/android/server/uwb/data/UwbDlTDoAMeasurement.java new file mode 100644 index 00000000..5163bfd0 --- /dev/null +++ b/service/java/com/android/server/uwb/data/UwbDlTDoAMeasurement.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2022 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.uwb.data; + +import com.android.server.uwb.util.UwbUtil; + +import java.util.Arrays; + +public class UwbDlTDoAMeasurement { + public byte[] mMacAddress; + public int mStatus; + public int mMessageType; + public int mMessageControl; + public int mBlockIndex; + public int mRoundIndex; + public int mNLoS; + public float mAoaAzimuth; + public int mAoaAzimuthFom; + public float mAoaElevation; + public int mAoaElevationFom; + public int mRssi; + public long mTxTimestamp; + public long mRxTimestamp; + public int mAnchorCfo; + public int mCfo; + public long mInitiatorReplyTime; + public long mResponderReplyTime; + public int mInitiatorResponderTof; + public byte[] mAnchorLocation; + public byte[] mActiveRangingRounds; + + public UwbDlTDoAMeasurement(byte[] macAddress, int status, int messageType, int messageControl, + int blockIndex, int roundIndex, int nLoS, int aoaAzimuth, int aoaAzimuthFom, + int aoaElevation, int aoaElevationFom, int rssi, long txTimestamp, long rxTimestamp, + int anchorCfo, int cfo, long initiatorReplyTime, long responderReplyTime, + int initiatorResponderTof, byte[] anchorLocation, byte[] activeRangingRounds) { + mMacAddress = macAddress; + mStatus = status; + mMessageType = messageType; + mMessageControl = messageControl; + mBlockIndex = blockIndex; + mRoundIndex = roundIndex; + mNLoS = nLoS; + mAoaAzimuth = toFloatFromQFormat(aoaAzimuth); + mAoaAzimuthFom = aoaAzimuthFom; + mAoaElevation = toFloatFromQFormat(aoaElevation); + mAoaElevationFom = aoaElevationFom; + mRssi = rssi; + mTxTimestamp = txTimestamp; + mRxTimestamp = rxTimestamp; + mAnchorCfo = anchorCfo; + mCfo = cfo; + mInitiatorReplyTime = initiatorReplyTime; + mResponderReplyTime = responderReplyTime; + mInitiatorResponderTof = initiatorResponderTof; + mAnchorLocation = anchorLocation; + mActiveRangingRounds = activeRangingRounds; + } + + public byte[] getMacAddress() { + return mMacAddress; + } + + public int getStatus() { + return mStatus; + } + + public int getMessageType() { + return mMessageType; + } + + public int getMessageControl() { + return mMessageControl; + } + + public int getBlockIndex() { + return mBlockIndex; + } + + public int getRoundIndex() { + return mRoundIndex; + } + + public int getNLoS() { + return mNLoS; + } + + public float getAoaAzimuth() { + return mAoaAzimuth; + } + + public int getAoaAzimuthFom() { + return mAoaAzimuthFom; + } + + public float getAoaElevation() { + return mAoaElevation; + } + + public int getAoaElevationFom() { + return mAoaElevationFom; + } + + public int getRssi() { + return mRssi; + } + + public long getTxTimestamp() { + return mTxTimestamp; + } + + public long getRxTimestamp() { + return mRxTimestamp; + } + + public int getAnchorCfo() { + return mAnchorCfo; + } + + public int getCfo() { + return mCfo; + } + + public long getInitiatorReplyTime() { + return mInitiatorReplyTime; + } + + public long getResponderReplyTime() { + return mResponderReplyTime; + } + + public int getInitiatorResponderTof() { + return mInitiatorResponderTof; + } + + public byte[] getAnchorLocation() { + return mAnchorLocation; + } + + public byte[] getActiveRangingRounds() { + return mActiveRangingRounds; + } + + private float toFloatFromQFormat(int value) { + return UwbUtil.convertQFormatToFloat(UwbUtil.twos_compliment(value, 16), + 9, 7); + } + + @Override + public String toString() { + return "UwbDLTDoAMeasurement{" + + "MacAddress=" + Arrays.toString(mMacAddress) + + ", Status=" + mStatus + + ", MessageType=" + mMessageType + + ", MessageControl=" + mMessageControl + + ", BlockIndex=" + mBlockIndex + + ", RoundIndex=" + mRoundIndex + + ", NLos=" + mNLoS + + ", AoaAzimuth=" + mAoaAzimuth + + ", AoaAzimuthFom=" + mAoaAzimuthFom + + ", AoaElevation=" + mAoaElevation + + ", AoaElevationFom=" + mAoaElevationFom + + ", Rssi=" + mRssi + + ", TxTimestamp=" + mTxTimestamp + + ", RxTimestamp=" + mRxTimestamp + + ", AnchorCfo=" + mAnchorCfo + + ", Cfo=" + mCfo + + ", InitiatorReplyTime=" + mInitiatorReplyTime + + ", ResponderReplyTime=" + mResponderReplyTime + + ", InitiatorResponderTof=" + mInitiatorResponderTof + + ", AnchorLocation=" + Arrays.toString(mAnchorLocation) + + ", ActiveRangingRounds=" + Arrays.toString(mActiveRangingRounds) + + '}'; + } +} diff --git a/service/java/com/android/server/uwb/data/UwbRangingData.java b/service/java/com/android/server/uwb/data/UwbRangingData.java index 780a8cba..78b6dafb 100644 --- a/service/java/com/android/server/uwb/data/UwbRangingData.java +++ b/service/java/com/android/server/uwb/data/UwbRangingData.java @@ -28,6 +28,7 @@ public class UwbRangingData { public UwbTwoWayMeasurement[] mRangingTwoWayMeasures; public byte[] mRawNtfData; public UwbOwrAoaMeasurement mRangingOwrAoaMeasure; + public UwbDlTDoAMeasurement[] mUwbDlTDoAMeasurements; public UwbRangingData(long seqCounter, long sessionId, int rcrIndication, long currRangingInterval, int rangingMeasuresType, int macAddressMode, @@ -59,6 +60,21 @@ public class UwbRangingData { this.mRawNtfData = rawNtfData; } + public UwbRangingData(long seqCounter, long sessionId, int rcrIndication, + long currRangingInterval, int rangingMeasuresType, int macAddressMode, + int noOfRangingMeasures, UwbDlTDoAMeasurement[] uwbDlTDoAMeasurements, + byte[] rawNtfData) { + this.mSeqCounter = seqCounter; + this.mSessionId = sessionId; + this.mRcrIndication = rcrIndication; + this.mCurrRangingInterval = currRangingInterval; + this.mRangingMeasuresType = rangingMeasuresType; + this.mMacAddressMode = macAddressMode; + this.mNoOfRangingMeasures = noOfRangingMeasures; + this.mUwbDlTDoAMeasurements = uwbDlTDoAMeasurements; + this.mRawNtfData = rawNtfData; + } + public long getSequenceCounter() { return mSeqCounter; } @@ -99,6 +115,10 @@ public class UwbRangingData { return mRangingOwrAoaMeasure; } + public UwbDlTDoAMeasurement[] getUwbDlTDoAMeasurements() { + return mUwbDlTDoAMeasurements; + } + public String toString() { if (mRangingMeasuresType == UwbUciConstants.RANGING_MEASUREMENT_TYPE_TWO_WAY) { return "UwbRangingData { " @@ -124,6 +144,18 @@ public class UwbRangingData { + ", RangingOwrAoaMeasure = " + mRangingOwrAoaMeasure.toString() + ", RawNotificationData = " + Arrays.toString(mRawNtfData) + '}'; + } else if (mRangingMeasuresType == UwbUciConstants.RANGING_MEASUREMENT_TYPE_DL_TDOA) { + return "UwbRangingData { " + + " SeqCounter = " + mSeqCounter + + ", SessionId = " + mSessionId + + ", RcrIndication = " + mRcrIndication + + ", CurrRangingInterval = " + mCurrRangingInterval + + ", RangingMeasuresType = " + mRangingMeasuresType + + ", MacAddressMode = " + mMacAddressMode + + ", NoOfRangingMeasures = " + mNoOfRangingMeasures + + ", RangingDlTDoAMeasure = " + Arrays.toString(mUwbDlTDoAMeasurements) + + ", RawNotificationData = " + Arrays.toString(mRawNtfData) + + '}'; } else { // TODO(jh0.jang) : ONE WAY RANGING(TDOA)? return null; diff --git a/service/java/com/android/server/uwb/data/UwbTwoWayMeasurement.java b/service/java/com/android/server/uwb/data/UwbTwoWayMeasurement.java index fbe70817..f763ab80 100644 --- a/service/java/com/android/server/uwb/data/UwbTwoWayMeasurement.java +++ b/service/java/com/android/server/uwb/data/UwbTwoWayMeasurement.java @@ -15,7 +15,7 @@ */ package com.android.server.uwb.data; -import static android.uwb.RangingMeasurement.RSSI_MIN; +import android.uwb.RangingMeasurement; import com.android.server.uwb.util.UwbUtil; @@ -43,7 +43,9 @@ public class UwbTwoWayMeasurement { this.mMacAddress = macAddress; this.mStatus = status; this.mNLoS = nLoS; - this.mDistance = distance; + // Set distance to negative value if the status code indicates negative distance + this.mDistance = status == UwbUciConstants.STATUS_CODE_OK_NEGATIVE_DISTANCE_REPORT + ? -distance : distance; this.mAoaAzimuth = toFloatFromQFormat(aoaAzimuth); this.mAoaAzimuthFom = aoaAzimuthFom; this.mAoaElevation = toFloatFromQFormat(aoaElevation); @@ -59,7 +61,7 @@ public class UwbTwoWayMeasurement { * Just need to divide this number by two and take the negative value. * If the reported RSSI is lower than RSSI_MIN, set it to RSSI_MIN to avoid exceptions. */ - this.mRssi = Math.max(-rssiHalfDbmAbs / 2, RSSI_MIN); + this.mRssi = Math.max(-rssiHalfDbmAbs / 2, RangingMeasurement.RSSI_MIN); } public byte[] getMacAddress() { @@ -117,6 +119,26 @@ public class UwbTwoWayMeasurement { return mRssi; } + public boolean isStatusCodeOk() { + return mStatus == UwbUciConstants.STATUS_CODE_OK + || mStatus == UwbUciConstants.STATUS_CODE_OK_NEGATIVE_DISTANCE_REPORT; + } + + /** + * Convert the UCI status code to success, out of range, or unknown error + */ + public int convertStatusCode() { + switch (mStatus) { + case UwbUciConstants.STATUS_CODE_OK: + case UwbUciConstants.STATUS_CODE_OK_NEGATIVE_DISTANCE_REPORT: + return RangingMeasurement.RANGING_STATUS_SUCCESS; + case UwbUciConstants.STATUS_CODE_INVALID_RANGE: + return RangingMeasurement.RANGING_STATUS_FAILURE_OUT_OF_RANGE; + default: + return RangingMeasurement.RANGING_STATUS_FAILURE_UNKNOWN_ERROR; + } + } + private float toFloatFromQFormat(int value) { return UwbUtil.convertQFormatToFloat(UwbUtil.twos_compliment(value, 16), 9, 7); diff --git a/service/java/com/android/server/uwb/data/UwbUciConstants.java b/service/java/com/android/server/uwb/data/UwbUciConstants.java index 1e49d4b9..975a4924 100644 --- a/service/java/com/android/server/uwb/data/UwbUciConstants.java +++ b/service/java/com/android/server/uwb/data/UwbUciConstants.java @@ -87,6 +87,8 @@ public class UwbUciConstants { FiraParams.RANGING_ROUND_USAGE_SS_TWR_NON_DEFERRED_MODE; public static final int ROUND_USAGE_DS_TWR_NON_DEFERRED_MODE = FiraParams.RANGING_ROUND_USAGE_DS_TWR_NON_DEFERRED_MODE; + public static final int ROUND_USAGE_OWR_AOA_MEASUREMENT = + FiraParams.RANGING_ROUND_USAGE_OWR_AOA_MEASUREMENT; public static final int MULTI_NODE_MODE_UNICAST = FiraParams.MULTI_NODE_MODE_UNICAST; public static final int MULTI_NODE_MODE_ONE_TO_MANY = FiraParams.MULTI_NODE_MODE_ONE_TO_MANY; @@ -113,17 +115,28 @@ public class UwbUciConstants { public static final int RANGE_DATA_NTF_CONFIG_ENABLE_PROXIMITY = FiraParams.RANGE_DATA_NTF_CONFIG_ENABLE_PROXIMITY_LEVEL_TRIG; + /** + * Table 54: APP Configuration Parameter IDs + */ public static final int RANGING_DEVICE_ROLE_RESPONDER = FiraParams.RANGING_DEVICE_ROLE_RESPONDER; public static final int RANGING_DEVICE_ROLE_INITIATOR = FiraParams.RANGING_DEVICE_ROLE_INITIATOR; + public static final int RANGING_DEVICE_ROLE_ADVERTISER = + FiraParams.RANGING_DEVICE_ROLE_ADVERTISER; + public static final int RANGING_DEVICE_ROLE_OBSERVER = + FiraParams.RANGING_DEVICE_ROLE_OBSERVER; /** - * Table 22: Ranging Data Notification + * Table 37: Ranging Data Notification */ public static final byte RANGING_MEASUREMENT_TYPE_TWO_WAY = 0X01; + public static final byte RANGING_MEASUREMENT_TYPE_DL_TDOA = 0x02; public static final byte RANGING_MEASUREMENT_TYPE_OWR_AOA = 0x03; + public static final byte MAC_ADDRESSING_MODE_SHORT = 0x00; + public static final byte MAC_ADDRESSING_MODE_EXTENDED = 0x01; + /** * Table 32: Status Codes */ @@ -159,6 +172,8 @@ public class UwbUciConstants { FiraParams.STATUS_CODE_ERROR_ADDRESS_NOT_FOUND; public static final int STATUS_CODE_ERROR_ADDRESS_ALREADY_PRESENT = FiraParams.STATUS_CODE_ERROR_ADDRESS_ALREADY_PRESENT; + public static final int STATUS_CODE_OK_NEGATIVE_DISTANCE_REPORT = + FiraParams.STATUS_CODE_OK_NEGATIVE_DISTANCE_REPORT; /* UWB Ranging Session Specific Status Codes */ public static final int STATUS_CODE_RANGING_TX_FAILED = FiraParams.STATUS_CODE_RANGING_TX_FAILED; @@ -176,11 +191,21 @@ public class UwbUciConstants { FiraParams.STATUS_CODE_RANGING_RX_MAC_IE_DEC_FAILED; public static final int STATUS_CODE_RANGING_RX_MAC_IE_MISSING = FiraParams.STATUS_CODE_RANGING_RX_MAC_IE_MISSING; + public static final int STATUS_CODE_ERROR_ROUND_INDEX_NOT_ACTIVATED = + FiraParams.STATUS_CODE_ERROR_ROUND_INDEX_NOT_ACTIVATED; + public static final int STATUS_CODE_ERROR_NUMBER_OF_ACTIVE_RANGING_ROUNDS_EXCEEDED = + FiraParams.STATUS_CODE_ERROR_NUMBER_OF_ACTIVE_RANGING_ROUNDS_EXCEEDED; + public static final int STATUS_CODE_ERROR_ROUND_INDEX_NOT_SET_AS_INITIATOR = + FiraParams.STATUS_CODE_ERROR_ROUND_INDEX_NOT_SET_AS_INITIATOR; + public static final int + STATUS_CODE_ERROR_DL_TDOA_DEVICE_ADDRESS_NOT_MATCHING_IN_REPLY_TIME_LIST = + FiraParams.STATUS_CODE_ERROR_DL_TDOA_DEVICE_ADDRESS_NOT_MATCHING_IN_REPLY_TIME_LIST; public static final int STATUS_CODE_CCC_SE_BUSY = STATUS_ERROR_CCC_SE_BUSY; public static final int STATUS_CODE_CCC_LIFECYCLE = STATUS_ERROR_CCC_LIFECYCLE; /* UWB Device Extended Mac address length */ + public static final int UWB_DEVICE_SHORT_MAC_ADDRESS_LEN = 2; public static final int UWB_DEVICE_EXT_MAC_ADDRESS_LEN = 8; /* UWB Data Session Specific Status Codes */ diff --git a/service/java/com/android/server/uwb/jni/NativeUwbManager.java b/service/java/com/android/server/uwb/jni/NativeUwbManager.java index d9866a42..edf3b60a 100644 --- a/service/java/com/android/server/uwb/jni/NativeUwbManager.java +++ b/service/java/com/android/server/uwb/jni/NativeUwbManager.java @@ -21,6 +21,7 @@ import android.util.Log; import com.android.internal.annotations.Keep; import com.android.server.uwb.UciLogModeStore; import com.android.server.uwb.UwbInjector; +import com.android.server.uwb.data.DtTagUpdateRangingRoundsStatus; import com.android.server.uwb.data.UwbConfigStatusData; import com.android.server.uwb.data.UwbMulticastListUpdateStatus; import com.android.server.uwb.data.UwbRangingData; @@ -30,15 +31,13 @@ import com.android.server.uwb.data.UwbVendorUciResponse; import com.android.server.uwb.info.UwbPowerStats; import com.android.server.uwb.multchip.UwbMultichipData; +import java.util.Arrays; + @Keep public class NativeUwbManager { private static final String TAG = NativeUwbManager.class.getSimpleName(); - public final Object mSessionFnLock = new Object(); - public final Object mSessionCountFnLock = new Object(); - public final Object mGlobalStateFnLock = new Object(); - public final Object mGetSessionStatusFnLock = new Object(); - public final Object mSetAppConfigFnLock = new Object(); + public final Object mNativeLock = new Object(); private final UwbInjector mUwbInjector; private final UciLogModeStore mUciLogModeStore; private final UwbMultichipData mUwbMultichipData; @@ -57,7 +56,9 @@ public class NativeUwbManager { protected void loadLibrary() { System.loadLibrary("uwb_uci_jni_rust"); - nativeInit(); + synchronized (mNativeLock) { + nativeInit(); + } } public void setDeviceListener(INativeUwbManager.DeviceNotification deviceListener) { @@ -109,7 +110,8 @@ public class NativeUwbManager { * Vendor callback invoked via the JNI */ public void onVendorUciNotificationReceived(int gid, int oid, byte[] payload) { - Log.d(TAG, "onVendorUciNotificationReceived: " + gid + ", " + oid + ", " + payload); + Log.d(TAG, "onVendorUciNotificationReceived: " + gid + ", " + oid + ", " + + Arrays.toString(payload)); mVendorListener.onVendorUciNotificationReceived(gid, oid, payload); } @@ -118,14 +120,16 @@ public class NativeUwbManager { * * @return : If this returns true, UWB is on */ - public synchronized boolean doInitialize() { - mDispatcherPointer = nativeDispatcherNew(mUwbMultichipData.getChipIds().toArray()); - for (String chipId : mUwbMultichipData.getChipIds()) { - if (!nativeDoInitialize(chipId)) { - return false; + public boolean doInitialize() { + synchronized (mNativeLock) { + mDispatcherPointer = nativeDispatcherNew(mUwbMultichipData.getChipIds().toArray()); + for (String chipId : mUwbMultichipData.getChipIds()) { + if (!nativeDoInitialize(chipId)) { + return false; + } } + nativeSetLogMode(mUciLogModeStore.getMode()); } - nativeSetLogMode(mUciLogModeStore.getMode()); return true; } @@ -134,17 +138,24 @@ public class NativeUwbManager { * * @return : If this returns true, UWB is off */ - public synchronized boolean doDeinitialize() { - for (String chipId : mUwbMultichipData.getChipIds()) { - nativeDoDeinitialize(chipId); - } + public boolean doDeinitialize() { + synchronized (mNativeLock) { + for (String chipId : mUwbMultichipData.getChipIds()) { + nativeDoDeinitialize(chipId); + } - nativeDispatcherDestroy(); - mDispatcherPointer = 0L; + nativeDispatcherDestroy(); + mDispatcherPointer = 0L; + } return true; } - public synchronized long getTimestampResolutionNanos() { + /** + * Gets the timestamp resolution in nanosecond + * + * @return : timestamp resolution in nanosecond + */ + public long getTimestampResolutionNanos() { return 0L; /* TODO: Not Implemented in native stack return nativeGetTimestampResolutionNanos(); */ @@ -156,14 +167,18 @@ public class NativeUwbManager { * @return : Retrieves maximum number of UWB sessions concurrently */ public int getMaxSessionNumber() { - return nativeGetMaxSessionNumber(); + synchronized (mNativeLock) { + return nativeGetMaxSessionNumber(); + } } /** * Retrieves power related stats */ public UwbPowerStats getPowerStats(String chipId) { - return nativeGetPowerStats(chipId); + synchronized (mNativeLock) { + return nativeGetPowerStats(chipId); + } } /** @@ -177,7 +192,7 @@ public class NativeUwbManager { * @return : {@link UwbUciConstants} Status code */ public byte initSession(int sessionId, byte sessionType, String chipId) { - synchronized (mSessionFnLock) { + synchronized (mNativeLock) { return nativeSessionInit(sessionId, sessionType, chipId); } } @@ -190,7 +205,7 @@ public class NativeUwbManager { * @return : {@link UwbUciConstants} Status code */ public byte deInitSession(int sessionId, String chipId) { - synchronized (mSessionFnLock) { + synchronized (mNativeLock) { return nativeSessionDeInit(sessionId, chipId); } } @@ -203,7 +218,9 @@ public class NativeUwbManager { * @return : {@link UwbUciConstants} Status code */ public byte resetDevice(byte resetConfig, String chipId) { - return nativeResetDevice(resetConfig, chipId); + synchronized (mNativeLock) { + return nativeResetDevice(resetConfig, chipId); + } } /** @@ -213,7 +230,7 @@ public class NativeUwbManager { * @return : Number of UWB sessions present in the UWBS. */ public byte getSessionCount(String chipId) { - synchronized (mSessionCountFnLock) { + synchronized (mNativeLock) { return nativeGetSessionCount(chipId); } } @@ -226,7 +243,7 @@ public class NativeUwbManager { * @return : {@link UwbUciConstants} Session State */ public byte getSessionState(int sessionId, String chipId) { - synchronized (mGetSessionStatusFnLock) { + synchronized (mNativeLock) { return nativeGetSessionState(sessionId, chipId); } } @@ -239,7 +256,7 @@ public class NativeUwbManager { * @return : {@link UwbUciConstants} Status code */ public byte startRanging(int sessionId, String chipId) { - synchronized (mSessionFnLock) { + synchronized (mNativeLock) { return nativeRangingStart(sessionId, chipId); } } @@ -252,7 +269,7 @@ public class NativeUwbManager { * @return : {@link UwbUciConstants} Status code */ public byte stopRanging(int sessionId, String chipId) { - synchronized (mSessionFnLock) { + synchronized (mNativeLock) { return nativeRangingStop(sessionId, chipId); } } @@ -268,7 +285,7 @@ public class NativeUwbManager { */ public UwbConfigStatusData setAppConfigurations(int sessionId, int noOfParams, int appConfigParamLen, byte[] appConfigParams, String chipId) { - synchronized (mSetAppConfigFnLock) { + synchronized (mNativeLock) { return nativeSetAppConfigurations(sessionId, noOfParams, appConfigParamLen, appConfigParams, chipId); } @@ -285,7 +302,7 @@ public class NativeUwbManager { */ public UwbTlvData getAppConfigurations(int sessionId, int noOfParams, int appConfigParamLen, byte[] appConfigIds, String chipId) { - synchronized (mSetAppConfigFnLock) { + synchronized (mNativeLock) { return nativeGetAppConfigurations(sessionId, noOfParams, appConfigParamLen, appConfigIds, chipId); } @@ -298,7 +315,7 @@ public class NativeUwbManager { * @return : {@link UwbTlvData} : All tlvs that are to be decoded */ public UwbTlvData getCapsInfo(String chipId) { - synchronized (mGlobalStateFnLock) { + synchronized (mNativeLock) { return nativeGetCapsInfo(chipId); } } @@ -318,7 +335,7 @@ public class NativeUwbManager { */ public byte controllerMulticastListUpdateV1(int sessionId, int action, int noOfControlee, short[] addresses, int[] subSessionIds, String chipId) { - synchronized (mSessionFnLock) { + synchronized (mNativeLock) { return nativeControllerMulticastListUpdateV1(sessionId, (byte) action, (byte) noOfControlee, addresses, subSessionIds, chipId); } @@ -349,7 +366,7 @@ public class NativeUwbManager { public byte controllerMulticastListUpdateV2(int sessionId, int action, int noOfControlee, short[] addresses, int[] subSessionIds, int messageControl, int[] subSessionKeyList, String chipId) { - synchronized (mSessionFnLock) { + synchronized (mNativeLock) { return nativeControllerMulticastListUpdateV2(sessionId, (byte) action, (byte) noOfControlee, addresses, subSessionIds, messageControl, subSessionKeyList, chipId); @@ -364,7 +381,7 @@ public class NativeUwbManager { public byte setCountryCode(byte[] countryCode) { Log.i(TAG, "setCountryCode: " + new String(countryCode)); - synchronized (mGlobalStateFnLock) { + synchronized (mNativeLock) { for (String chipId : mUwbMultichipData.getChipIds()) { byte status = nativeSetCountryCode(countryCode, chipId); if (status != UwbUciConstants.STATUS_CODE_OK) { @@ -383,7 +400,9 @@ public class NativeUwbManager { */ public boolean setLogMode(String logModeStr) { if (mUciLogModeStore.storeMode(logModeStr)) { - return nativeSetLogMode(mUciLogModeStore.getMode()); + synchronized (mNativeLock) { + return nativeSetLogMode(mUciLogModeStore.getMode()); + } } else { return false; } @@ -391,7 +410,7 @@ public class NativeUwbManager { @NonNull public UwbVendorUciResponse sendRawVendorCmd(int gid, int oid, byte[] payload, String chipId) { - synchronized (mGlobalStateFnLock) { + synchronized (mNativeLock) { return nativeSendRawVendorCmd(gid, oid, payload, chipId); } } @@ -412,11 +431,32 @@ public class NativeUwbManager { */ public byte sendData( int sessionId, byte[] address, byte destEndPoint, int sequenceNum, byte[] appData) { - return nativeSendData(sessionId, address, destEndPoint, sequenceNum, appData); + synchronized (mNativeLock) { + return nativeSendData(sessionId, address, destEndPoint, sequenceNum, appData); + } + } + + + /** + * Update active Ranging Rounds for DT Tag + * + * @param sessionId Session ID to which ranging round to be updated + * @param noOfActiveRangingRounds new active ranging round + * @param rangingRoundIndexes Indexes of ranging rounds + * @return refer to SESSION_SET_APP_CONFIG_RSP + * in the Table 16: Control messages to set Application configurations + */ + public DtTagUpdateRangingRoundsStatus sessionUpdateActiveRoundsDtTag(int sessionId, + int noOfActiveRangingRounds, byte[] rangingRoundIndexes, String chipId) { + synchronized (mNativeLock) { + return nativeSessionUpdateActiveRoundsDtTag(sessionId, noOfActiveRangingRounds, + rangingRoundIndexes, chipId); + } } - private native byte nativeSendData(int sessionId, byte[] address, - byte destEndPoint, int sequenceNum, byte[] appData); + // TODO(b/259487023): no native implementation + private native byte nativeSendData(int sessionId, byte[] address, byte destEndPoint, + int sequenceNum, byte[] appData); private native long nativeDispatcherNew(Object[] chipIds); @@ -434,6 +474,7 @@ public class NativeUwbManager { private native int nativeGetMaxSessionNumber(); + // TODO(b/259487023): no native implementation private native byte nativeResetDevice(byte resetConfig, String chipId); private native byte nativeSessionInit(int sessionId, byte sessionType, String chipId); @@ -459,6 +500,7 @@ public class NativeUwbManager { private native byte nativeControllerMulticastListUpdateV1(int sessionId, byte action, byte noOfControlee, short[] address, int[] subSessionId, String chipId); + // TODO(b/259487023): no native implementation private native byte nativeControllerMulticastListUpdateV2(int sessionId, byte action, byte noOfControlee, short[] address, int[] subSessionId, int messageControl, int[] subSessionKeyList, String chipId); @@ -469,4 +511,7 @@ public class NativeUwbManager { private native UwbVendorUciResponse nativeSendRawVendorCmd(int gid, int oid, byte[] payload, String chipId); + + private native DtTagUpdateRangingRoundsStatus nativeSessionUpdateActiveRoundsDtTag( + int sessionId, int noOfActiveRangingRounds, byte[] rangingRoundIndexes, String chipId); } diff --git a/service/java/com/android/server/uwb/params/CccEncoder.java b/service/java/com/android/server/uwb/params/CccEncoder.java index 93a2f25e..1debeaa8 100644 --- a/service/java/com/android/server/uwb/params/CccEncoder.java +++ b/service/java/com/android/server/uwb/params/CccEncoder.java @@ -40,6 +40,7 @@ public class CccEncoder extends TlvEncoder { int hoppingSequence = params.getHoppingSequence(); int hoppingMode = CccParams.HOPPING_CONFIG_MODE_NONE; + byte[] protocolVer = params.getProtocolVersion().toBytes(); switch (hoppingConfig) { @@ -61,7 +62,7 @@ public class CccEncoder extends TlvEncoder { TlvBuffer tlvBuffer = new TlvBuffer.Builder() .putByte(ConfigParam.DEVICE_TYPE, - (byte) UwbUciConstants.DEVICE_TYPE_CONTROLEE) // DEVICE_TYPE + (byte) UwbUciConstants.DEVICE_TYPE_CONTROLLER) // DEVICE_TYPE .putByte(ConfigParam.STS_CONFIG, (byte) UwbUciConstants.STS_MODE_DYNAMIC) // STS_CONFIG .putByte(ConfigParam.CHANNEL_NUMBER, (byte) params.getChannel()) // CHANNEL_ID @@ -81,7 +82,7 @@ public class CccEncoder extends TlvEncoder { .putByte(ConfigParam.HOPPING_MODE, (byte) hoppingMode) // HOPPING_MODE .putByteArray(ConfigParam.RANGING_PROTOCOL_VER, ConfigParam.RANGING_PROTOCOL_VER_BYTE_COUNT, - params.getProtocolVersion().toBytes()) // RANGING_PROTOCOL_VER + new byte[] { protocolVer[1], protocolVer[0] }) // RANGING_PROTOCOL_VER .putShort(ConfigParam.UWB_CONFIG_ID, (short) params.getUwbConfig()) // UWB_CONFIG_ID .putByte(ConfigParam.PULSESHAPE_COMBO, params.getPulseShapeCombo().toBytes()[0]) // PULSESHAPE_COMBO @@ -97,4 +98,4 @@ public class CccEncoder extends TlvEncoder { return tlvBuffer; } -} +}
\ No newline at end of file diff --git a/service/java/com/android/server/uwb/params/FiraDecoder.java b/service/java/com/android/server/uwb/params/FiraDecoder.java index 466848a5..cce79e39 100644 --- a/service/java/com/android/server/uwb/params/FiraDecoder.java +++ b/service/java/com/android/server/uwb/params/FiraDecoder.java @@ -73,6 +73,7 @@ import static com.android.server.uwb.config.CapabilityParam.SUPPORTED_FIRA_MAC_V import static com.android.server.uwb.config.CapabilityParam.SUPPORTED_FIRA_PHY_VERSION_RANGE; import static com.android.server.uwb.config.CapabilityParam.SUPPORTED_HPRF_PARAMETER_SETS; import static com.android.server.uwb.config.CapabilityParam.SUPPORTED_MIN_RANGING_INTERVAL_MS; +import static com.android.server.uwb.config.CapabilityParam.SUPPORTED_MIN_SLOT_DURATION; import static com.android.server.uwb.config.CapabilityParam.SUPPORTED_MULTI_NODE_MODES; import static com.android.server.uwb.config.CapabilityParam.SUPPORTED_RANGE_DATA_NTF_CONFIG; import static com.android.server.uwb.config.CapabilityParam.SUPPORTED_RANGING_METHOD; @@ -134,6 +135,13 @@ public class FiraDecoder extends TlvDecoder { } try { + int minSlotDuration = tlvs.getInt(SUPPORTED_MIN_SLOT_DURATION); + builder.setMinRangingIntervalSupported(minSlotDuration); + } catch (IllegalArgumentException e) { + Log.w(TAG, "SUPPORTED_MIN_SLOT_DURATION not found."); + } + + try { byte rssiReporting = tlvs.getByte(SUPPORTED_RSSI_REPORTING); if (isBitSet(rssiReporting, RSSI_REPORTING)) { builder.hasRssiReportingSupport(true); diff --git a/service/java/com/android/server/uwb/params/TlvDecoderBuffer.java b/service/java/com/android/server/uwb/params/TlvDecoderBuffer.java index 9fc41e80..49d34752 100644 --- a/service/java/com/android/server/uwb/params/TlvDecoderBuffer.java +++ b/service/java/com/android/server/uwb/params/TlvDecoderBuffer.java @@ -125,7 +125,7 @@ public class TlvDecoderBuffer { private Tlv getTlv(int tagType) { byte[] tagTypeByte = ConfigParam.getTagBytes(tagType); if (tagTypeByte.length > 1) { - throw new IllegalArgumentException("Invalid tagType: " + tagTypeByte); + throw new IllegalArgumentException("Invalid tagType: " + Arrays.toString(tagTypeByte)); } Tlv tlv = mTlvs.get(tagTypeByte[0]); if (tlv == null) { diff --git a/service/java/com/android/server/uwb/util/DataTypeConversionUtil.java b/service/java/com/android/server/uwb/util/DataTypeConversionUtil.java index 2e5ff972..0f191412 100644 --- a/service/java/com/android/server/uwb/util/DataTypeConversionUtil.java +++ b/service/java/com/android/server/uwb/util/DataTypeConversionUtil.java @@ -15,6 +15,9 @@ */ package com.android.server.uwb.util; +import static com.android.server.uwb.data.UwbUciConstants.UWB_DEVICE_EXT_MAC_ADDRESS_LEN; +import static com.android.server.uwb.data.UwbUciConstants.UWB_DEVICE_SHORT_MAC_ADDRESS_LEN; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -137,5 +140,34 @@ public class DataTypeConversionUtil { return ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN).putInt(n).array(); } + /** + * Convert the byte array (in Little Endian format) to a long. The input array could be: of + * shorter size (eg: 2 bytes, to represent a shortMacAddress). It could also have length of 8 + * bytes, but have the MSB 6 bytes zeroed out (the 2 LSB bytes contain the MacAddress). + */ + public static long macAddressByteArrayToLong(byte[] bytes) { + if (bytes.length == 2) { + return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getShort(); + } else if (bytes.length == 4) { + return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt(); + } else if (bytes.length == 8) { + if (isExtendedMSBZeroedOut(bytes)) { + return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getShort(); + } else { + return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getLong(); + } + } else { + throw new NumberFormatException("Expected length one of (2, 4, 8) but was " + + bytes.length); + } + } + + // Check if the MSB bytes are zeroed out. + private static boolean isExtendedMSBZeroedOut(byte[] bytes) { + for (int i = UWB_DEVICE_SHORT_MAC_ADDRESS_LEN; i < UWB_DEVICE_EXT_MAC_ADDRESS_LEN; i++) { + if (bytes[i] != 0) return false; + } + return true; + } private DataTypeConversionUtil() {} } diff --git a/service/support_lib/Android.bp b/service/support_lib/Android.bp index 96083d0d..45eb875a 100644 --- a/service/support_lib/Android.bp +++ b/service/support_lib/Android.bp @@ -19,19 +19,15 @@ package { java_defaults { name: "support-lib-uwb-common-defaults", - defaults: ["uwb-module-sdk-version-defaults"], - sdk_version: "module_Tiramisu", - libs: [ - "framework-annotations-lib", - "framework-uwb-pre-jarjar", + sdk_version: "system_31", + static_libs: [ + "androidx.annotation_annotation", + "guava" ], apex_available: [ "com.android.uwb", ], visibility : [ - //TODO(b/244347268): Remove these 2 after code move. - "//cts/tests/uwb", - "//cts/hostsidetests/multidevices/uwb/snippet", "//external/sl4a/Common", "//packages/modules/Uwb:__subpackages__", ] @@ -43,10 +39,6 @@ java_library { srcs: [ "src/com/google/uwb/support/base/**/*.java", ], - static_libs: [ - "androidx.annotation_annotation", - "modules-utils-preconditions", - ], } java_library { @@ -56,9 +48,7 @@ java_library { "src/com/google/uwb/support/ccc/**/*.java", ], static_libs: [ - "androidx.annotation_annotation", "com.uwb.support.base", - "modules-utils-preconditions", ], } @@ -69,9 +59,7 @@ java_library { "src/com/google/uwb/support/fira/**/*.java", ], static_libs: [ - "androidx.annotation_annotation", "com.uwb.support.base", - "modules-utils-preconditions", ], } @@ -82,11 +70,9 @@ java_library { "src/com/google/uwb/support/generic/**/*.java", ], static_libs: [ - "androidx.annotation_annotation", "com.uwb.support.base", "com.uwb.support.ccc", "com.uwb.support.fira", - "modules-utils-preconditions", ], } @@ -96,8 +82,6 @@ java_library { srcs: [ "src/com/google/uwb/support/multichip/**/*.java", ], - static_libs: [ - ], } java_library { @@ -107,10 +91,8 @@ java_library { "src/com/google/uwb/support/profile/**/*.java", ], static_libs: [ - "androidx.annotation_annotation", "com.uwb.support.base", "com.uwb.support.fira", - "modules-utils-preconditions", ], } @@ -124,3 +106,15 @@ java_library { "com.uwb.support.base", ], } + +java_library { + name: "com.uwb.support.dltdoa", + defaults: ["support-lib-uwb-common-defaults"], + srcs: [ + "src/com/google/uwb/support/dltdoa/**/*.java", + ], + static_libs: [ + "com.uwb.support.base", + "com.uwb.support.fira", + ], +} diff --git a/service/support_lib/src/com/google/uwb/support/ccc/CccOpenRangingParams.java b/service/support_lib/src/com/google/uwb/support/ccc/CccOpenRangingParams.java index bd6fbcd7..f0323b16 100644 --- a/service/support_lib/src/com/google/uwb/support/ccc/CccOpenRangingParams.java +++ b/service/support_lib/src/com/google/uwb/support/ccc/CccOpenRangingParams.java @@ -16,14 +16,14 @@ package com.google.uwb.support.ccc; -import static com.android.internal.util.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkNotNull; -import android.annotation.NonNull; import android.os.Build.VERSION_CODES; import android.os.PersistableBundle; import android.uwb.UwbManager; import androidx.annotation.IntRange; +import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import com.google.uwb.support.base.RequiredParam; diff --git a/service/support_lib/src/com/google/uwb/support/ccc/CccSpecificationParams.java b/service/support_lib/src/com/google/uwb/support/ccc/CccSpecificationParams.java index 105aeab0..beeb7876 100644 --- a/service/support_lib/src/com/google/uwb/support/ccc/CccSpecificationParams.java +++ b/service/support_lib/src/com/google/uwb/support/ccc/CccSpecificationParams.java @@ -16,7 +16,7 @@ package com.google.uwb.support.ccc; -import static com.android.internal.util.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkNotNull; import android.os.Build.VERSION_CODES; import android.os.PersistableBundle; diff --git a/service/support_lib/src/com/google/uwb/support/dltdoa/DlTDoAMeasurement.java b/service/support_lib/src/com/google/uwb/support/dltdoa/DlTDoAMeasurement.java new file mode 100644 index 00000000..7c2c61c1 --- /dev/null +++ b/service/support_lib/src/com/google/uwb/support/dltdoa/DlTDoAMeasurement.java @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2022 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.google.uwb.support.dltdoa; + +import android.os.PersistableBundle; +import android.uwb.RangingMeasurement; + +import androidx.annotation.Nullable; + +/** + * DlTDoA measurement values + * + * <p> This is passed as a bundle with RangingMeasurement for mRangingReportMetadata + * {@link RangingMeasurement#getRangingMeasurementMetadata()} This will be passed for sessions + * with DL-TDoA measurements only. For other sessions, the metadata will contain something else + */ +public class DlTDoAMeasurement { + private final int mMessageType; + private final int mMessageControl; + private final int mBlockIndex; + private final int mRoundIndex; + private final int mNLoS; + private final long mTxTimestamp; + private final long mRxTimestamp; + private final int mAnchorCfo; + private final int mCfo; + private final long mInitiatorReplyTime; + private final long mResponderReplyTime; + private final int mInitiatorResponderTof; + private final byte[] mAnchorLocation; + private final byte[] mActiveRangingRounds; + + public static final String KEY_BUNDLE_VERSION = "bundle_version"; + public static final String MESSAGE_TYPE = "message_type"; + public static final String MESSAGE_CONTROL = "message_control"; + public static final String BLOCK_INDEX = "block_index"; + public static final String ROUND_INDEX = "round_index"; + public static final String NLOS = "nlos"; + public static final String RSSI = "rssi"; + public static final String TX_TIMESTAMP = "tx_timestamp"; + public static final String RX_TIMESTAMP = "rx_timestamp"; + public static final String ANCHOR_CFO = "anchor_cfo"; + public static final String CFO = "cfo"; + public static final String INITIATOR_REPLY_TIME = "initiator_reply_time"; + public static final String RESPONDER_REPLY_TIME = "responder_reply_time"; + public static final String INITIATOR_RESPONDER_TOF = "initiator_responder_time"; + public static final String ANCHOR_LOCATION = "anchor_location"; + public static final String ACTIVE_RANGING_ROUNDS = "active_ranging_rounds"; + + private static final int BUNDLE_VERSION_1 = 1; + private static final int BUNDLE_VERSION_CURRENT = BUNDLE_VERSION_1; + + public DlTDoAMeasurement(int messageType, int messageControl, int blockIndex, int roundIndex, + int NLoS, long txTimestamp, long rxTimestamp, int anchorCfo, int cfo, + long initiatorReplyTime, long responderReplyTime, int initiatorResponderTof, + byte[] anchorLocation, byte[] activeRangingRounds) { + mMessageType = messageType; + mMessageControl = messageControl; + mBlockIndex = blockIndex; + mRoundIndex = roundIndex; + mNLoS = NLoS; + mTxTimestamp = txTimestamp; + mRxTimestamp = rxTimestamp; + mAnchorCfo = anchorCfo; + mCfo = cfo; + mInitiatorReplyTime = initiatorReplyTime; + mResponderReplyTime = responderReplyTime; + mInitiatorResponderTof = initiatorResponderTof; + mAnchorLocation = anchorLocation; + mActiveRangingRounds = activeRangingRounds; + } + + protected int getBundleVersion() { + return BUNDLE_VERSION_CURRENT; + } + + public int getMessageType() { + return mMessageType; + } + + public int getMessageControl() { + return mMessageControl; + } + + public int getBlockIndex() { + return mBlockIndex; + } + + public int getRoundIndex() { + return mRoundIndex; + } + + public int getNLoS() { + return mNLoS; + } + + public long getTxTimestamp() { + return mTxTimestamp; + } + + public long getRxTimestamp() { + return mRxTimestamp; + } + + public int getAnchorCfo() { + return mAnchorCfo; + } + + public int getCfo() { + return mCfo; + } + + public long getInitiatorReplyTime() { + return mInitiatorReplyTime; + } + + public long getResponderReplyTime() { + return mResponderReplyTime; + } + + public int getInitiatorResponderTof() { + return mInitiatorResponderTof; + } + + public byte[] getAnchorLocation() { + return mAnchorLocation; + } + + public byte[] getActiveRangingRounds() { + return mActiveRangingRounds; + } + + @Nullable + private static int[] byteArrayToIntArray(@Nullable byte[] bytes) { + if (bytes == null) { + return null; + } + int[] values = new int[bytes.length]; + for (int i = 0; i < values.length; i++) { + values[i] = (bytes[i]); + } + return values; + } + + @Nullable + private static byte[] intArrayToByteArray(@Nullable int[] values) { + if (values == null) { + return null; + } + byte[] bytes = new byte[values.length]; + for (int i = 0; i < values.length; i++) { + bytes[i] = (byte) values[i]; + } + return bytes; + } + + public PersistableBundle toBundle() { + PersistableBundle bundle = new PersistableBundle(); + bundle.putInt(KEY_BUNDLE_VERSION, BUNDLE_VERSION_CURRENT); + bundle.putInt(MESSAGE_TYPE, mMessageType); + bundle.putInt(MESSAGE_CONTROL, mMessageControl); + bundle.putInt(BLOCK_INDEX, mBlockIndex); + bundle.putInt(ROUND_INDEX, mRoundIndex); + bundle.putInt(NLOS, mNLoS); + bundle.putLong(TX_TIMESTAMP, mTxTimestamp); + bundle.putLong(RX_TIMESTAMP, mRxTimestamp); + bundle.putInt(ANCHOR_CFO, mAnchorCfo); + bundle.putInt(CFO, mCfo); + bundle.putLong(INITIATOR_REPLY_TIME, mInitiatorReplyTime); + bundle.putLong(RESPONDER_REPLY_TIME, mResponderReplyTime); + bundle.putInt(INITIATOR_RESPONDER_TOF, mInitiatorResponderTof); + bundle.putIntArray(ANCHOR_LOCATION, byteArrayToIntArray(mAnchorLocation)); + bundle.putIntArray(ACTIVE_RANGING_ROUNDS, byteArrayToIntArray(mActiveRangingRounds)); + return bundle; + } + + public static DlTDoAMeasurement fromBundle(PersistableBundle bundle) { + switch (bundle.getInt(KEY_BUNDLE_VERSION)) { + case BUNDLE_VERSION_1: + return parseVersion1(bundle); + default: + throw new IllegalArgumentException("Invalid bundle version"); + } + } + + private static DlTDoAMeasurement parseVersion1(PersistableBundle bundle) { + return new DlTDoAMeasurement.Builder() + .setMessageType(bundle.getInt(MESSAGE_TYPE)) + .setMessageControl(bundle.getInt(MESSAGE_CONTROL)) + .setBlockIndex(bundle.getInt(BLOCK_INDEX)) + .setRoundIndex(bundle.getInt(ROUND_INDEX)) + .setNLoS(bundle.getInt(NLOS)) + .setTxTimestamp(bundle.getLong(TX_TIMESTAMP)) + .setRxTimestamp(bundle.getLong(RX_TIMESTAMP)) + .setAnchorCfo(bundle.getInt(ANCHOR_CFO)) + .setCfo(bundle.getInt(CFO)) + .setInitiatorReplyTime(bundle.getLong(INITIATOR_REPLY_TIME)) + .setResponderReplyTime(bundle.getLong(RESPONDER_REPLY_TIME)) + .setInitiatorResponderTof(bundle.getInt(INITIATOR_RESPONDER_TOF)) + .setAnchorLocation(intArrayToByteArray(bundle.getIntArray(ANCHOR_LOCATION))) + .setActiveRangingRounds( + intArrayToByteArray(bundle.getIntArray(ACTIVE_RANGING_ROUNDS))) + .build(); + } + + /** Builder */ + public static class Builder { + private int mMessageType; + private int mMessageControl; + private int mBlockIndex; + private int mRoundIndex; + private int mNLoS; + private long mTxTimestamp; + private long mRxTimestamp; + private int mAnchorCfo; + private int mCfo; + private long mInitiatorReplyTime; + private long mResponderReplyTime; + private int mInitiatorResponderTof; + private byte[] mAnchorLocation; + private byte[] mActiveRangingRounds; + + public DlTDoAMeasurement.Builder setMessageType(int messageType) { + mMessageType = messageType; + return this; + } + + public DlTDoAMeasurement.Builder setMessageControl(int messageControl) { + mMessageControl = messageControl; + return this; + } + + public DlTDoAMeasurement.Builder setBlockIndex(int blockIndex) { + mBlockIndex = blockIndex; + return this; + } + + public DlTDoAMeasurement.Builder setRoundIndex(int roundIndex) { + mRoundIndex = roundIndex; + return this; + } + + public DlTDoAMeasurement.Builder setNLoS(int nLoS) { + mNLoS = nLoS; + return this; + } + + public DlTDoAMeasurement.Builder setTxTimestamp(long txTimestamp) { + mTxTimestamp = txTimestamp; + return this; + } + + public DlTDoAMeasurement.Builder setRxTimestamp(long rxTimestamp) { + mRxTimestamp = rxTimestamp; + return this; + } + + public DlTDoAMeasurement.Builder setAnchorCfo(int anchorCfo) { + mAnchorCfo = anchorCfo; + return this; + } + + public DlTDoAMeasurement.Builder setCfo(int cfo) { + mCfo = cfo; + return this; + } + + public DlTDoAMeasurement.Builder setInitiatorReplyTime(long initiatorReplyTime) { + mInitiatorReplyTime = initiatorReplyTime; + return this; + } + + public DlTDoAMeasurement.Builder setResponderReplyTime(long responderReplyTime) { + mResponderReplyTime = responderReplyTime; + return this; + } + + public DlTDoAMeasurement.Builder setInitiatorResponderTof(int initiatorResponderTof) { + mInitiatorResponderTof = initiatorResponderTof; + return this; + } + + public DlTDoAMeasurement.Builder setAnchorLocation(byte[] anchorLocation) { + mAnchorLocation = anchorLocation; + return this; + } + + public DlTDoAMeasurement.Builder setActiveRangingRounds(byte[] activeRangingRounds) { + mActiveRangingRounds = activeRangingRounds; + return this; + } + + public DlTDoAMeasurement build() { + return new DlTDoAMeasurement( + mMessageType, + mMessageControl, + mBlockIndex, + mRoundIndex, + mNLoS, + mTxTimestamp, + mRxTimestamp, + mAnchorCfo, + mCfo, + mInitiatorReplyTime, + mResponderReplyTime, + mInitiatorResponderTof, + mAnchorLocation, + mActiveRangingRounds); + } + } +} diff --git a/service/support_lib/src/com/google/uwb/support/dltdoa/DlTDoARangingRoundsUpdate.java b/service/support_lib/src/com/google/uwb/support/dltdoa/DlTDoARangingRoundsUpdate.java new file mode 100644 index 00000000..25bda81c --- /dev/null +++ b/service/support_lib/src/com/google/uwb/support/dltdoa/DlTDoARangingRoundsUpdate.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2022 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.google.uwb.support.dltdoa; + +import android.os.PersistableBundle; + +import androidx.annotation.Nullable; + +/** + * Used to send SESSION_UPDATE_ACTIVE_ROUNDS_DT_TAG_CMD + * + * <p> This is passed as a bundle to update ranging rounds for DT Tag + */ +public class DlTDoARangingRoundsUpdate { + private final long mSessionId; + private final int mNoOfActiveRangingRounds; + private final byte[] mRangingRoundIndexes; + + private static final int BUNDLE_VERSION_1 = 1; + private static final int BUNDLE_VERSION_CURRENT = BUNDLE_VERSION_1; + + public static final String KEY_BUNDLE_VERSION = "bundle_version"; + public static final String SESSION_ID = "session_id"; + public static final String NO_OF_ACTIVE_RANGING_ROUNDS = "no_active_ranging_rounds"; + public static final String RANGING_ROUND_INDEXES = "ranging_round_indexes"; + + + private DlTDoARangingRoundsUpdate(long sessionId, int noOfActiveRangingRounds, + byte[] rangingRoundIndexes) { + mSessionId = sessionId; + mNoOfActiveRangingRounds = noOfActiveRangingRounds; + mRangingRoundIndexes = rangingRoundIndexes; + } + + public int getBundleVersion() { + return BUNDLE_VERSION_CURRENT; + } + + public long getSessionId() { + return mSessionId; + } + + public int getNoOfActiveRangingRounds() { + return mNoOfActiveRangingRounds; + } + + public byte[] getRangingRoundIndexes() { + return mRangingRoundIndexes; + } + + @Nullable + private static int[] byteArrayToIntArray(@Nullable byte[] bytes) { + if (bytes == null) { + return null; + } + int[] values = new int[bytes.length]; + for (int i = 0; i < values.length; i++) { + values[i] = (bytes[i]); + } + return values; + } + + @Nullable + private static byte[] intArrayToByteArray(@Nullable int[] values) { + if (values == null) { + return null; + } + byte[] bytes = new byte[values.length]; + for (int i = 0; i < values.length; i++) { + bytes[i] = (byte) values[i]; + } + return bytes; + } + + public PersistableBundle toBundle() { + PersistableBundle bundle = new PersistableBundle(); + bundle.putInt(KEY_BUNDLE_VERSION, BUNDLE_VERSION_CURRENT); + bundle.putLong(SESSION_ID, mSessionId); + bundle.putInt(NO_OF_ACTIVE_RANGING_ROUNDS, mNoOfActiveRangingRounds); + bundle.putIntArray(RANGING_ROUND_INDEXES, byteArrayToIntArray(mRangingRoundIndexes)); + return bundle; + } + + public static DlTDoARangingRoundsUpdate fromBundle(PersistableBundle bundle) { + switch (bundle.getInt(KEY_BUNDLE_VERSION)) { + case BUNDLE_VERSION_1: + return parseVersion1(bundle); + default: + throw new IllegalArgumentException("Invalid bundle version"); + } + } + + private static DlTDoARangingRoundsUpdate parseVersion1(PersistableBundle bundle) { + return new DlTDoARangingRoundsUpdate.Builder() + .setSessionId(bundle.getLong(SESSION_ID)) + .setNoOfActiveRangingRounds(bundle.getInt(NO_OF_ACTIVE_RANGING_ROUNDS)) + .setRangingRoundIndexes( + intArrayToByteArray(bundle.getIntArray(RANGING_ROUND_INDEXES))) + .build(); + } + + /** Builder */ + public static class Builder { + private long mSessionId = 0; + private int mNoOfActiveRangingRounds = 0; + private byte[] mRangingRoundIndexes = new byte[]{}; + + public DlTDoARangingRoundsUpdate.Builder setSessionId(long sessionId) { + mSessionId = sessionId; + return this; + } + + public DlTDoARangingRoundsUpdate.Builder setNoOfActiveRangingRounds( + int activeRangingRounds) { + mNoOfActiveRangingRounds = activeRangingRounds; + return this; + } + + public DlTDoARangingRoundsUpdate.Builder setRangingRoundIndexes( + byte[] rangingRoundIndexes) { + mRangingRoundIndexes = rangingRoundIndexes; + return this; + } + + public DlTDoARangingRoundsUpdate build() { + return new DlTDoARangingRoundsUpdate( + mSessionId, + mNoOfActiveRangingRounds, + mRangingRoundIndexes); + } + } +} diff --git a/service/support_lib/src/com/google/uwb/support/dltdoa/DlTDoARangingRoundsUpdateStatus.java b/service/support_lib/src/com/google/uwb/support/dltdoa/DlTDoARangingRoundsUpdateStatus.java new file mode 100644 index 00000000..753a4eb8 --- /dev/null +++ b/service/support_lib/src/com/google/uwb/support/dltdoa/DlTDoARangingRoundsUpdateStatus.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2022 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.google.uwb.support.dltdoa; + +import android.os.PersistableBundle; + +import androidx.annotation.Nullable; + +/** + * Used to send SESSION_UPDATE_ACTIVE_ROUNDS_DT_TAG_RSP + * + * <p> This is passed as a bundle for response received for command + * SESSION_UPDATE_ACTIVE_ROUNDS_DT_TAG_CMD + */ +public class DlTDoARangingRoundsUpdateStatus { + private final int mStatus; + private final int mNoOfActiveRangingRounds; + private final byte[] mRangingRoundIndexes; + + private static final int BUNDLE_VERSION_1 = 1; + private static final int BUNDLE_VERSION_CURRENT = BUNDLE_VERSION_1; + + public static final String KEY_BUNDLE_VERSION = "bundle_version"; + public static final String STATUS = "status"; + public static final String NO_OF_ACTIVE_RANGING_ROUNDS = "no_active_ranging_rounds"; + public static final String RANGING_ROUND_INDEXES = "ranging_round_indexes"; + + + private DlTDoARangingRoundsUpdateStatus(int status, int noOfActiveRangingRounds, + byte[] rangingRoundIndexes) { + mStatus = status; + mNoOfActiveRangingRounds = noOfActiveRangingRounds; + mRangingRoundIndexes = rangingRoundIndexes; + } + + public int getBundleVersion() { + return BUNDLE_VERSION_CURRENT; + } + + public int getStatus() { + return mStatus; + } + + public int getNoOfActiveRangingRounds() { + return mNoOfActiveRangingRounds; + } + + public byte[] getRangingRoundIndexes() { + return mRangingRoundIndexes; + } + + @Nullable + private static int[] byteArrayToIntArray(@Nullable byte[] bytes) { + if (bytes == null) { + return null; + } + int[] values = new int[bytes.length]; + for (int i = 0; i < values.length; i++) { + values[i] = (bytes[i]); + } + return values; + } + + @Nullable + private static byte[] intArrayToByteArray(@Nullable int[] values) { + if (values == null) { + return null; + } + byte[] bytes = new byte[values.length]; + for (int i = 0; i < values.length; i++) { + bytes[i] = (byte) values[i]; + } + return bytes; + } + + public PersistableBundle toBundle() { + PersistableBundle bundle = new PersistableBundle(); + bundle.putInt(KEY_BUNDLE_VERSION, BUNDLE_VERSION_CURRENT); + bundle.putInt(STATUS, mStatus); + bundle.putInt(NO_OF_ACTIVE_RANGING_ROUNDS, mNoOfActiveRangingRounds); + bundle.putIntArray(RANGING_ROUND_INDEXES, byteArrayToIntArray(mRangingRoundIndexes)); + return bundle; + } + + public static DlTDoARangingRoundsUpdateStatus fromBundle(PersistableBundle bundle) { + switch (bundle.getInt(KEY_BUNDLE_VERSION)) { + case BUNDLE_VERSION_1: + return parseVersion1(bundle); + default: + throw new IllegalArgumentException("Invalid bundle version"); + } + } + + private static DlTDoARangingRoundsUpdateStatus parseVersion1(PersistableBundle bundle) { + return new DlTDoARangingRoundsUpdateStatus.Builder() + .setStatus(bundle.getInt(STATUS)) + .setNoOfActiveRangingRounds(bundle.getInt(NO_OF_ACTIVE_RANGING_ROUNDS)) + .setRangingRoundIndexes( + intArrayToByteArray(bundle.getIntArray(RANGING_ROUND_INDEXES))) + .build(); + } + + /** Builder */ + public static class Builder { + private int mStatus = 0; + private int mNoOfActiveRangingRounds = 0; + private byte[] mRangingRoundIndexes = new byte[]{}; + + public DlTDoARangingRoundsUpdateStatus.Builder setStatus(int status) { + mStatus = status; + return this; + } + + public DlTDoARangingRoundsUpdateStatus.Builder setNoOfActiveRangingRounds( + int activeRangingRounds) { + mNoOfActiveRangingRounds = activeRangingRounds; + return this; + } + + public DlTDoARangingRoundsUpdateStatus.Builder setRangingRoundIndexes( + byte[] rangingRoundIndexes) { + mRangingRoundIndexes = rangingRoundIndexes; + return this; + } + + public DlTDoARangingRoundsUpdateStatus build() { + return new DlTDoARangingRoundsUpdateStatus( + mStatus, + mNoOfActiveRangingRounds, + mRangingRoundIndexes); + } + } +} diff --git a/service/support_lib/src/com/google/uwb/support/fira/FiraControleeParams.java b/service/support_lib/src/com/google/uwb/support/fira/FiraControleeParams.java index 89df6923..2930efd1 100644 --- a/service/support_lib/src/com/google/uwb/support/fira/FiraControleeParams.java +++ b/service/support_lib/src/com/google/uwb/support/fira/FiraControleeParams.java @@ -16,14 +16,13 @@ package com.google.uwb.support.fira; -import static com.android.internal.util.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; import android.os.PersistableBundle; import android.uwb.RangingSession; import android.uwb.UwbAddress; -import android.uwb.UwbManager; import androidx.annotation.Nullable; diff --git a/service/support_lib/src/com/google/uwb/support/fira/FiraOpenSessionParams.java b/service/support_lib/src/com/google/uwb/support/fira/FiraOpenSessionParams.java index 5b9cee07..d91b08c3 100644 --- a/service/support_lib/src/com/google/uwb/support/fira/FiraOpenSessionParams.java +++ b/service/support_lib/src/com/google/uwb/support/fira/FiraOpenSessionParams.java @@ -16,8 +16,8 @@ package com.google.uwb.support.fira; -import static com.android.internal.util.Preconditions.checkArgument; -import static com.android.internal.util.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import static java.util.Objects.requireNonNull; diff --git a/service/support_lib/src/com/google/uwb/support/fira/FiraParams.java b/service/support_lib/src/com/google/uwb/support/fira/FiraParams.java index c10688ea..c4772bdd 100644 --- a/service/support_lib/src/com/google/uwb/support/fira/FiraParams.java +++ b/service/support_lib/src/com/google/uwb/support/fira/FiraParams.java @@ -150,6 +150,8 @@ public abstract class FiraParams extends Params { RANGING_DEVICE_ROLE_INITIATOR, RANGING_DEVICE_ROLE_ADVERTISER, RANGING_DEVICE_ROLE_OBSERVER, + RANGING_DEVICE_DT_ANCHOR, + RANGING_DEVICE_DT_TAG, }) public @interface RangingDeviceRole {} @@ -161,6 +163,10 @@ public abstract class FiraParams extends Params { public static final int RANGING_DEVICE_ROLE_OBSERVER = 6; + public static final int RANGING_DEVICE_DT_ANCHOR = 7; + + public static final int RANGING_DEVICE_DT_TAG = 8; + /** Ranging Round Usage */ @IntDef( value = { @@ -169,6 +175,7 @@ public abstract class FiraParams extends Params { RANGING_ROUND_USAGE_SS_TWR_NON_DEFERRED_MODE, RANGING_ROUND_USAGE_DS_TWR_NON_DEFERRED_MODE, RANGING_ROUND_USAGE_OWR_AOA_MEASUREMENT, + RANGING_ROUND_USAGE_DL_TDOA, }) public @interface RangingRoundUsage {} @@ -184,6 +191,9 @@ public abstract class FiraParams extends Params { /** Double-sided two-way ranging, non-deferred */ public static final int RANGING_ROUND_USAGE_DS_TWR_NON_DEFERRED_MODE = 4; + /** Downlink Time Difference of Arrival */ + public static final int RANGING_ROUND_USAGE_DL_TDOA = 5; + /** OWR for AoA measurement */ public static final int RANGING_ROUND_USAGE_OWR_AOA_MEASUREMENT = 6; @@ -466,6 +476,7 @@ public abstract class FiraParams extends Params { STATUS_CODE_ERROR_MULTICAST_LIST_FULL, STATUS_CODE_ERROR_ADDRESS_NOT_FOUND, STATUS_CODE_ERROR_ADDRESS_ALREADY_PRESENT, + STATUS_CODE_OK_NEGATIVE_DISTANCE_REPORT, STATUS_CODE_RANGING_TX_FAILED, STATUS_CODE_RANGING_RX_TIMEOUT, STATUS_CODE_RANGING_RX_PHY_DEC_FAILED, @@ -474,6 +485,10 @@ public abstract class FiraParams extends Params { STATUS_CODE_RANGING_RX_MAC_DEC_FAILED, STATUS_CODE_RANGING_RX_MAC_IE_DEC_FAILED, STATUS_CODE_RANGING_RX_MAC_IE_MISSING, + STATUS_CODE_ERROR_ROUND_INDEX_NOT_ACTIVATED, + STATUS_CODE_ERROR_NUMBER_OF_ACTIVE_RANGING_ROUNDS_EXCEEDED, + STATUS_CODE_ERROR_ROUND_INDEX_NOT_SET_AS_INITIATOR, + STATUS_CODE_ERROR_DL_TDOA_DEVICE_ADDRESS_NOT_MATCHING_IN_REPLY_TIME_LIST, }) public @interface StatusCode {} @@ -497,6 +512,7 @@ public abstract class FiraParams extends Params { public static final int STATUS_CODE_ERROR_MULTICAST_LIST_FULL = 0x17; public static final int STATUS_CODE_ERROR_ADDRESS_NOT_FOUND = 0x18; public static final int STATUS_CODE_ERROR_ADDRESS_ALREADY_PRESENT = 0x19; + public static final int STATUS_CODE_OK_NEGATIVE_DISTANCE_REPORT = 0x1B; public static final int STATUS_CODE_RANGING_TX_FAILED = 0x20; public static final int STATUS_CODE_RANGING_RX_TIMEOUT = 0x21; public static final int STATUS_CODE_RANGING_RX_PHY_DEC_FAILED = 0x22; @@ -505,6 +521,11 @@ public abstract class FiraParams extends Params { public static final int STATUS_CODE_RANGING_RX_MAC_DEC_FAILED = 0x25; public static final int STATUS_CODE_RANGING_RX_MAC_IE_DEC_FAILED = 0x26; public static final int STATUS_CODE_RANGING_RX_MAC_IE_MISSING = 0x27; + public static final int STATUS_CODE_ERROR_ROUND_INDEX_NOT_ACTIVATED = 0X28; + public static final int STATUS_CODE_ERROR_NUMBER_OF_ACTIVE_RANGING_ROUNDS_EXCEEDED = 0X29; + public static final int STATUS_CODE_ERROR_ROUND_INDEX_NOT_SET_AS_INITIATOR = 0X2A; + public static final int + STATUS_CODE_ERROR_DL_TDOA_DEVICE_ADDRESS_NOT_MATCHING_IN_REPLY_TIME_LIST = 0X2B; /** State change reason codes defined in UCI table-15 */ @IntDef( diff --git a/service/support_lib/src/com/google/uwb/support/fira/FiraRangingReconfigureParams.java b/service/support_lib/src/com/google/uwb/support/fira/FiraRangingReconfigureParams.java index 3c741138..cb74f5f9 100644 --- a/service/support_lib/src/com/google/uwb/support/fira/FiraRangingReconfigureParams.java +++ b/service/support_lib/src/com/google/uwb/support/fira/FiraRangingReconfigureParams.java @@ -16,7 +16,7 @@ package com.google.uwb.support.fira; -import static com.android.internal.util.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; diff --git a/service/support_lib/src/com/google/uwb/support/fira/FiraSpecificationParams.java b/service/support_lib/src/com/google/uwb/support/fira/FiraSpecificationParams.java index bb373ae0..9beacad3 100644 --- a/service/support_lib/src/com/google/uwb/support/fira/FiraSpecificationParams.java +++ b/service/support_lib/src/com/google/uwb/support/fira/FiraSpecificationParams.java @@ -60,6 +60,8 @@ public class FiraSpecificationParams extends FiraParams { private final int mMinRangingInterval; + private final int mMinSlotDuration; + private final EnumSet<MultiNodeCapabilityFlag> mMultiNodeCapabilities; private final EnumSet<PrfCapabilityFlag> mPrfCapabilities; @@ -92,6 +94,7 @@ public class FiraSpecificationParams extends FiraParams { private static final String KEY_RSSI_REPORTING_SUPPORT = "rssi_reporting"; private static final String KEY_DIAGNOSTICS_SUPPORT = "diagnostics"; private static final String KEY_MIN_RANGING_INTERVAL = "min_ranging_interval"; + private static final String KEY_MIN_SLOT_DURATION = "min_slot_duration"; private static final String KEY_MULTI_NODE_CAPABILITIES = "multi_node_capabilities"; private static final String KEY_PRF_CAPABILITIES = "prf_capabilities"; private static final String KEY_RANGING_ROUND_CAPABILITIES = "ranging_round_capabilities"; @@ -119,6 +122,7 @@ public class FiraSpecificationParams extends FiraParams { boolean hasRssiReportingSupport, boolean hasDiagnosticsSupport, int minRangingInterval, + int minSlotDuration, EnumSet<MultiNodeCapabilityFlag> multiNodeCapabilities, EnumSet<PrfCapabilityFlag> prfCapabilities, EnumSet<RangingRoundCapabilityFlag> rangingRoundCapabilities, @@ -141,6 +145,7 @@ public class FiraSpecificationParams extends FiraParams { mHasRssiReportingSupport = hasRssiReportingSupport; mHasDiagnosticsSupport = hasDiagnosticsSupport; mMinRangingInterval = minRangingInterval; + mMinSlotDuration = minSlotDuration; mMultiNodeCapabilities = multiNodeCapabilities; mPrfCapabilities = prfCapabilities; mRangingRoundCapabilities = rangingRoundCapabilities; @@ -211,6 +216,10 @@ public class FiraSpecificationParams extends FiraParams { return mMinRangingInterval; } + public int getMinSlotDuration() { + return mMinSlotDuration; + } + public EnumSet<MultiNodeCapabilityFlag> getMultiNodeCapabilities() { return mMultiNodeCapabilities; } @@ -271,6 +280,7 @@ public class FiraSpecificationParams extends FiraParams { bundle.putBoolean(KEY_RSSI_REPORTING_SUPPORT, mHasRssiReportingSupport); bundle.putBoolean(KEY_DIAGNOSTICS_SUPPORT, mHasDiagnosticsSupport); bundle.putInt(KEY_MIN_RANGING_INTERVAL, mMinRangingInterval); + bundle.putInt(KEY_MIN_SLOT_DURATION, mMinSlotDuration); bundle.putInt(KEY_MULTI_NODE_CAPABILITIES, FlagEnum.toInt(mMultiNodeCapabilities)); bundle.putInt(KEY_PRF_CAPABILITIES, FlagEnum.toInt(mPrfCapabilities)); bundle.putInt(KEY_RANGING_ROUND_CAPABILITIES, FlagEnum.toInt(mRangingRoundCapabilities)); @@ -332,6 +342,7 @@ public class FiraSpecificationParams extends FiraParams { .hasNonDeferredModeSupport(bundle.getBoolean(KEY_NON_DEFERRED_MODE_SUPPORT)) .hasInitiationTimeSupport(bundle.getBoolean(KEY_INITIATION_TIME_SUPPORT)) .setMinRangingIntervalSupported(bundle.getInt(KEY_MIN_RANGING_INTERVAL, -1)) + .setMinSlotDurationSupported(bundle.getInt(KEY_MIN_SLOT_DURATION, -1)) .setMultiNodeCapabilities( FlagEnum.toEnumSet( bundle.getInt(KEY_MULTI_NODE_CAPABILITIES), @@ -413,6 +424,8 @@ public class FiraSpecificationParams extends FiraParams { private int mMinRangingInterval = -1; + private int mMinSlotDuration = -1; + // Unicast support is mandatory private final EnumSet<MultiNodeCapabilityFlag> mMultiNodeCapabilities = EnumSet.of(MultiNodeCapabilityFlag.HAS_UNICAST_SUPPORT); @@ -522,6 +535,16 @@ public class FiraSpecificationParams extends FiraParams { return this; } + /** + * Set minimum supported slot duration + * @param value : minimum slot duration supported + * @return FiraSpecificationParams builder + */ + public FiraSpecificationParams.Builder setMinSlotDurationSupported(int value) { + mMinSlotDuration = value; + return this; + } + public FiraSpecificationParams.Builder setMultiNodeCapabilities( Collection<MultiNodeCapabilityFlag> multiNodeCapabilities) { mMultiNodeCapabilities.addAll(multiNodeCapabilities); @@ -595,6 +618,7 @@ public class FiraSpecificationParams extends FiraParams { mHasRssiReportingSupport, mHasDiagnosticsSupport, mMinRangingInterval, + mMinSlotDuration, mMultiNodeCapabilities, mPrfCapabilities, mRangingRoundCapabilities, diff --git a/service/support_lib/src/com/google/uwb/support/generic/GenericSpecificationParams.java b/service/support_lib/src/com/google/uwb/support/generic/GenericSpecificationParams.java index 55b10292..0abd3aa9 100644 --- a/service/support_lib/src/com/google/uwb/support/generic/GenericSpecificationParams.java +++ b/service/support_lib/src/com/google/uwb/support/generic/GenericSpecificationParams.java @@ -16,10 +16,11 @@ package com.google.uwb.support.generic; -import android.annotation.NonNull; import android.os.PersistableBundle; import android.uwb.UwbManager; +import androidx.annotation.NonNull; + import com.google.uwb.support.base.RequiredParam; import com.google.uwb.support.ccc.CccParams; import com.google.uwb.support.ccc.CccSpecificationParams; diff --git a/service/support_lib/src/com/google/uwb/support/profile/UuidBundleWrapper.java b/service/support_lib/src/com/google/uwb/support/profile/UuidBundleWrapper.java index 53757bbc..5bfed828 100644 --- a/service/support_lib/src/com/google/uwb/support/profile/UuidBundleWrapper.java +++ b/service/support_lib/src/com/google/uwb/support/profile/UuidBundleWrapper.java @@ -47,7 +47,7 @@ public class UuidBundleWrapper { public PersistableBundle toBundle() { PersistableBundle bundle = new PersistableBundle(); bundle.putInt(KEY_BUNDLE_VERSION, getBundleVersion()); - if (!mServiceInstanceID.isEmpty()) { + if (mServiceInstanceID.isPresent()) { bundle.putString(SERVICE_INSTANCE_ID, mServiceInstanceID.get().toString()); } return bundle; diff --git a/service/support_lib/test/Android.bp b/service/support_lib/test/Android.bp index 26639f74..14f91a1f 100644 --- a/service/support_lib/test/Android.bp +++ b/service/support_lib/test/Android.bp @@ -13,6 +13,7 @@ android_test { "com.uwb.support.generic", "com.uwb.support.multichip", "com.uwb.support.oemextension", + "com.uwb.support.dltdoa", ], platform_apis: true, certificate: "platform", diff --git a/service/support_lib/test/DlTDoATests.java b/service/support_lib/test/DlTDoATests.java new file mode 100644 index 00000000..e51997f9 --- /dev/null +++ b/service/support_lib/test/DlTDoATests.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2022 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. + */ + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.google.uwb.support.dltdoa.DlTDoAMeasurement; +import com.google.uwb.support.dltdoa.DlTDoARangingRoundsUpdate; +import com.google.uwb.support.dltdoa.DlTDoARangingRoundsUpdateStatus; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DlTDoATests { + + @Test + public void dlTDoAMeasurementTest() { + int messageType = 0x02; + int messageControl = 0x513; + int blockIndex = 4; + int roundIndex = 6; + int nLoS = 40; + long txTimestamp = 40_000L; + long rxTimestamp = 50_000L; + int anchorCfo = 433; + int cfo = 0x56; + long initiatorReplyTime = 100; + long responderReplyTime = 200; + int initiatorResponderTof = 400; + byte[] anchorLocation = new byte[]{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + byte[] activeRangingRounds = new byte[]{0x01, 0x02}; + + DlTDoAMeasurement dlTDoAMeasurement = new DlTDoAMeasurement.Builder() + .setMessageType(messageType) + .setMessageControl(messageControl) + .setBlockIndex(blockIndex) + .setRoundIndex(roundIndex) + .setNLoS(nLoS) + .setTxTimestamp(txTimestamp) + .setRxTimestamp(rxTimestamp) + .setAnchorCfo(anchorCfo) + .setCfo(cfo) + .setInitiatorReplyTime(initiatorReplyTime) + .setResponderReplyTime(responderReplyTime) + .setInitiatorResponderTof(initiatorResponderTof) + .setAnchorLocation(anchorLocation) + .setActiveRangingRounds(activeRangingRounds) + .build(); + + DlTDoAMeasurement fromBundle = DlTDoAMeasurement.fromBundle(dlTDoAMeasurement.toBundle()); + + assertEquals(fromBundle.getMessageType(), messageType); + assertEquals(fromBundle.getMessageControl(), messageControl); + assertEquals(fromBundle.getBlockIndex(), blockIndex); + assertEquals(fromBundle.getRoundIndex(), roundIndex); + assertEquals(fromBundle.getNLoS(), nLoS); + assertEquals(fromBundle.getTxTimestamp(), txTimestamp); + assertEquals(fromBundle.getRxTimestamp(), rxTimestamp); + assertEquals(fromBundle.getAnchorCfo(), anchorCfo); + assertEquals(fromBundle.getCfo(), cfo); + assertEquals(fromBundle.getInitiatorReplyTime(), initiatorReplyTime); + assertEquals(fromBundle.getResponderReplyTime(), responderReplyTime); + assertEquals(fromBundle.getInitiatorResponderTof(), initiatorResponderTof); + assertArrayEquals(fromBundle.getAnchorLocation(), anchorLocation); + assertArrayEquals(fromBundle.getActiveRangingRounds(), activeRangingRounds); + } + + @Test + public void dlTDoARangingRoundsUpdateTest() { + int sessionId = 1234; + int noOfActiveRangingRounds = 3; + byte[] rangingRoundIndexes = new byte[]{0x01, 0x02, 0x03}; + + DlTDoARangingRoundsUpdate dlTDoARangingRoundsUpdate = new DlTDoARangingRoundsUpdate + .Builder() + .setSessionId(sessionId) + .setNoOfActiveRangingRounds(noOfActiveRangingRounds) + .setRangingRoundIndexes(rangingRoundIndexes) + .build(); + + DlTDoARangingRoundsUpdate fromBundle = DlTDoARangingRoundsUpdate.fromBundle( + dlTDoARangingRoundsUpdate.toBundle()); + + assertEquals(fromBundle.getSessionId(), sessionId); + assertEquals(fromBundle.getNoOfActiveRangingRounds(), noOfActiveRangingRounds); + assertArrayEquals(fromBundle.getRangingRoundIndexes(), rangingRoundIndexes); + } + + @Test + public void dlTDoARangingRoundsUpdateStatusTest() { + int status = 1; + int noOfActiveRangingRounds = 2; + byte[] rangingRoundIndexes = new byte[]{0x02, 0x03}; + + DlTDoARangingRoundsUpdateStatus dlTDoARangingRoundsUpdateStatus = + new DlTDoARangingRoundsUpdateStatus.Builder() + .setStatus(status) + .setNoOfActiveRangingRounds(noOfActiveRangingRounds) + .setRangingRoundIndexes(rangingRoundIndexes) + .build(); + + DlTDoARangingRoundsUpdateStatus fromBundle = DlTDoARangingRoundsUpdateStatus.fromBundle( + dlTDoARangingRoundsUpdateStatus.toBundle()); + + assertEquals(fromBundle.getStatus(), status); + assertEquals(fromBundle.getNoOfActiveRangingRounds(), noOfActiveRangingRounds); + assertArrayEquals(fromBundle.getRangingRoundIndexes(), rangingRoundIndexes); + } +} diff --git a/service/tests/src/com/android/server/uwb/DeviceConfigFacadeTest.java b/service/tests/src/com/android/server/uwb/DeviceConfigFacadeTest.java index 2ef232ef..59025a51 100644 --- a/service/tests/src/com/android/server/uwb/DeviceConfigFacadeTest.java +++ b/service/tests/src/com/android/server/uwb/DeviceConfigFacadeTest.java @@ -113,7 +113,7 @@ public class DeviceConfigFacadeTest { public void testDefaultValue() throws Exception { assertEquals(DeviceConfigFacade.DEFAULT_RANGING_RESULT_LOG_INTERVAL_MS, mDeviceConfigFacade.getRangingResultLogIntervalMs()); - assertEquals(true, mDeviceConfigFacade.isDeviceErrorBugreportEnabled()); + assertEquals(false, mDeviceConfigFacade.isDeviceErrorBugreportEnabled()); assertEquals(DeviceConfigFacade.DEFAULT_BUG_REPORT_MIN_INTERVAL_MS, mDeviceConfigFacade.getBugReportMinIntervalMs()); } diff --git a/service/tests/src/com/android/server/uwb/UwbMetricsTest.java b/service/tests/src/com/android/server/uwb/UwbMetricsTest.java index 16b127a0..ce9340c2 100644 --- a/service/tests/src/com/android/server/uwb/UwbMetricsTest.java +++ b/service/tests/src/com/android/server/uwb/UwbMetricsTest.java @@ -65,6 +65,7 @@ public class UwbMetricsTest { private static final int NLOS_DEFAULT = 1; private static final int VALID_RANGING_COUNT = 5; private static final int RSSI_DEFAULT_DBM = -75; + private static final boolean IS_STATUS_CODE_OK_DEFAULT = true; @Mock private UwbInjector mUwbInjector; @Mock @@ -93,6 +94,7 @@ public class UwbMetricsTest { when(mRangingData.getRangingMeasuresType()).thenReturn( (int) UwbUciConstants.RANGING_MEASUREMENT_TYPE_TWO_WAY); when(mTwoWayMeasurement.getRangingStatus()).thenReturn(FiraParams.STATUS_CODE_OK); + when(mTwoWayMeasurement.isStatusCodeOk()).thenReturn(IS_STATUS_CODE_OK_DEFAULT); when(mRangingData.getRangingTwoWayMeasures()).thenReturn(mTwoWayMeasurements); when(mUwbSession.getSessionId()).thenReturn(1); @@ -165,6 +167,7 @@ public class UwbMetricsTest { mRangingData); } when(mTwoWayMeasurement.getRangingStatus()).thenReturn(UwbUciConstants.STATUS_CODE_FAILED); + when(mTwoWayMeasurement.isStatusCodeOk()).thenReturn(!IS_STATUS_CODE_OK_DEFAULT); mUwbMetrics.logRangingResult(UwbStatsLog.UWB_SESSION_INITIATED__PROFILE__FIRA, mRangingData); diff --git a/service/tests/src/com/android/server/uwb/UwbServiceImplTest.java b/service/tests/src/com/android/server/uwb/UwbServiceImplTest.java index 79958242..c8863068 100644 --- a/service/tests/src/com/android/server/uwb/UwbServiceImplTest.java +++ b/service/tests/src/com/android/server/uwb/UwbServiceImplTest.java @@ -29,6 +29,7 @@ import static com.google.uwb.support.fira.FiraParams.RANGE_DATA_NTF_CONFIG_ENABL import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -47,6 +48,7 @@ import android.content.Intent; import android.os.IBinder; import android.os.PersistableBundle; import android.os.Process; +import android.os.UserManager; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.test.suitebuilder.annotation.SmallTest; @@ -59,6 +61,7 @@ import android.uwb.UwbAddress; import androidx.test.runner.AndroidJUnit4; +import com.android.modules.utils.build.SdkLevel; import com.android.server.uwb.data.UwbUciConstants; import com.android.server.uwb.jni.NativeUwbManager; import com.android.server.uwb.multchip.UwbMultichipData; @@ -72,6 +75,7 @@ import com.google.uwb.support.profile.UuidBundleWrapper; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; @@ -103,11 +107,13 @@ public class UwbServiceImplTest { @Mock private NativeUwbManager mNativeUwbManager; @Mock private UwbMultichipData mUwbMultichipData; @Mock private ProfileManager mProfileManager; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private UserManager mUserManager; @Captor private ArgumentCaptor<IUwbRangingCallbacks> mRangingCbCaptor; @Captor private ArgumentCaptor<IUwbRangingCallbacks> mRangingCbCaptor2; @Captor private ArgumentCaptor<IBinder.DeathRecipient> mClientDeathCaptor; @Captor private ArgumentCaptor<IBinder.DeathRecipient> mUwbServiceCoreDeathCaptor; @Captor private ArgumentCaptor<BroadcastReceiver> mApmModeBroadcastReceiver; + @Captor private ArgumentCaptor<BroadcastReceiver> mUserRestrictionReceiver; private UwbServiceImpl mUwbServiceImpl; @@ -122,12 +128,17 @@ public class UwbServiceImplTest { when(mUwbInjector.getMultichipData()).thenReturn(mUwbMultichipData); when(mUwbInjector.getSettingsInt(Settings.Global.AIRPLANE_MODE_ON, 0)).thenReturn(0); when(mUwbInjector.getNativeUwbManager()).thenReturn(mNativeUwbManager); + when(mUwbInjector.getUserManager()).thenReturn(mUserManager); + when(mUserManager.getUserRestrictions().getBoolean(anyString())).thenReturn(false); mUwbServiceImpl = new UwbServiceImpl(mContext, mUwbInjector); verify(mContext).registerReceiver( mApmModeBroadcastReceiver.capture(), argThat(i -> i.getAction(0).equals(Intent.ACTION_AIRPLANE_MODE_CHANGED))); + verify(mContext).registerReceiver( + mUserRestrictionReceiver.capture(), + argThat(i -> i.getAction(0).equals(UserManager.ACTION_USER_RESTRICTIONS_CHANGED))); } @Test @@ -356,6 +367,25 @@ public class UwbServiceImplTest { } @Test + public void testUserRestrictionChanged() throws Exception { + assumeTrue(SdkLevel.isAtLeastU()); // Test should only run on U+ devices. + + mUwbServiceImpl.setEnabled(true); + + // User restriction changes to disallow UWB + when(mUserManager.getUserRestrictions().getBoolean(anyString())).thenReturn(true); + mUserRestrictionReceiver.getValue().onReceive( + mContext, new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)); + verify(mUwbServiceCore).setEnabled(false); + + // User restriction changes to allow UWB + when(mUserManager.getUserRestrictions().getBoolean(anyString())).thenReturn(true); + mUserRestrictionReceiver.getValue().onReceive( + mContext, new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)); + verify(mUwbServiceCore, times(1)).setEnabled(true); + } + + @Test public void testToggleFromRootedShellWhenApmModeOn() throws Exception { BinderUtil.setUid(Process.ROOT_UID); when(mUwbInjector.getSettingsInt(Settings.Global.AIRPLANE_MODE_ON, 0)).thenReturn(1); diff --git a/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java b/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java index ec77fede..5795acd4 100644 --- a/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java +++ b/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java @@ -25,14 +25,24 @@ import static com.android.server.uwb.UwbTestUtils.PEER_BAD_MAC_ADDRESS; import static com.android.server.uwb.UwbTestUtils.PEER_EXTENDED_MAC_ADDRESS; import static com.android.server.uwb.UwbTestUtils.PEER_EXTENDED_MAC_ADDRESS_2; import static com.android.server.uwb.UwbTestUtils.PEER_EXTENDED_SHORT_MAC_ADDRESS; -import static com.android.server.uwb.UwbTestUtils.PEER_UWB_ADDRESS; +import static com.android.server.uwb.UwbTestUtils.PEER_EXTENDED_UWB_ADDRESS; +import static com.android.server.uwb.UwbTestUtils.PEER_SHORT_MAC_ADDRESS; +import static com.android.server.uwb.UwbTestUtils.PEER_SHORT_UWB_ADDRESS; import static com.android.server.uwb.UwbTestUtils.PERSISTABLE_BUNDLE; +import static com.android.server.uwb.UwbTestUtils.RANGING_MEASUREMENT_TYPE_UNDEFINED; import static com.android.server.uwb.UwbTestUtils.TEST_SESSION_ID; import static com.android.server.uwb.UwbTestUtils.TEST_SESSION_ID_2; +import static com.android.server.uwb.data.UwbUciConstants.MAC_ADDRESSING_MODE_EXTENDED; +import static com.android.server.uwb.data.UwbUciConstants.MAC_ADDRESSING_MODE_SHORT; +import static com.android.server.uwb.data.UwbUciConstants.RANGING_DEVICE_ROLE_ADVERTISER; +import static com.android.server.uwb.data.UwbUciConstants.RANGING_DEVICE_ROLE_OBSERVER; import static com.android.server.uwb.data.UwbUciConstants.RANGING_MEASUREMENT_TYPE_OWR_AOA; import static com.android.server.uwb.data.UwbUciConstants.RANGING_MEASUREMENT_TYPE_TWO_WAY; +import static com.android.server.uwb.data.UwbUciConstants.ROUND_USAGE_DS_TWR_DEFERRED_MODE; +import static com.android.server.uwb.data.UwbUciConstants.ROUND_USAGE_OWR_AOA_MEASUREMENT; import static com.google.common.truth.Truth.assertThat; +import static com.google.uwb.support.fira.FiraParams.PROTOCOL_NAME; import static com.google.uwb.support.fira.FiraParams.RangeDataNtfConfigCapabilityFlag.HAS_RANGE_DATA_NTF_CONFIG_DISABLE; import static com.google.uwb.support.fira.FiraParams.RangeDataNtfConfigCapabilityFlag.HAS_RANGE_DATA_NTF_CONFIG_ENABLE; @@ -107,6 +117,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -235,7 +246,8 @@ public class UwbSessionManagerTest { @Test public void onRangeDataNotificationReceivedWithValidUwbSession_twoWay() { UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( - RANGING_MEASUREMENT_TYPE_TWO_WAY, UwbUciConstants.STATUS_CODE_OK); + RANGING_MEASUREMENT_TYPE_TWO_WAY, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_OK); UwbSession mockUwbSession = mock(UwbSession.class); when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class)); doReturn(mockUwbSession) @@ -251,7 +263,8 @@ public class UwbSessionManagerTest { @Test public void onRangeDataNotificationReceivedWithInvalidSession_twoWay() { UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( - RANGING_MEASUREMENT_TYPE_TWO_WAY, UwbUciConstants.STATUS_CODE_OK); + RANGING_MEASUREMENT_TYPE_TWO_WAY, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_OK); doReturn(null) .when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID)); @@ -262,19 +275,27 @@ public class UwbSessionManagerTest { verify(mUwbMetrics, never()).logRangingResult(anyInt(), eq(uwbRangingData)); } + // Test scenario for receiving Application payload data followed by a RANGE_DATA_NTF with an + // OWR Aoa Measurement (such that the ExtendedMacAddress format is used for the remote device). @Test - public void onRangeDataNotificationReceived_owrAoa_success() { - UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( - RANGING_MEASUREMENT_TYPE_OWR_AOA, UwbUciConstants.STATUS_CODE_OK); + public void onRangeDataNotificationReceived_owrAoa_success_extendedMacAddress() { UwbSession mockUwbSession = mock(UwbSession.class); when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class)); doReturn(mockUwbSession) .when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID)); + // First call onDataReceived() to get the application payload data. mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK, DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT, DATA_PAYLOAD); + // Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF. + UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( + RANGING_MEASUREMENT_TYPE_OWR_AOA, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_OK); + Params firaParams = setupFiraParams( + RANGING_DEVICE_ROLE_OBSERVER, Optional.of(ROUND_USAGE_OWR_AOA_MEASUREMENT)); + when(mockUwbSession.getParams()).thenReturn(firaParams); when(mUwbAdvertiseManager.isPointedTarget(PEER_EXTENDED_MAC_ADDRESS)).thenReturn(true); mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData); @@ -282,36 +303,162 @@ public class UwbSessionManagerTest { .onRangingResult(eq(mockUwbSession), eq(uwbRangingData)); verify(mUwbAdvertiseManager).updateAdvertiseTarget(uwbRangingData.mRangingOwrAoaMeasure); verify(mUwbSessionNotificationManager) - .onDataReceived(eq(mockUwbSession), eq(PEER_UWB_ADDRESS), + .onDataReceived(eq(mockUwbSession), eq(PEER_EXTENDED_UWB_ADDRESS), + isA(PersistableBundle.class), eq(DATA_PAYLOAD)); + verify(mUwbAdvertiseManager).removeAdvertiseTarget(PEER_EXTENDED_MAC_ADDRESS); + verify(mUwbMetrics).logRangingResult(anyInt(), eq(uwbRangingData)); + } + + // Test scenario for receiving Application payload data followed by a RANGE_DATA_NTF with an + // OWR Aoa Measurement (such that the ShortMacAddress format is used for the remote device). + @Test + public void onRangeDataNotificationReceived_owrAoa_success_shortMacAddress() { + UwbSession mockUwbSession = mock(UwbSession.class); + when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class)); + doReturn(mockUwbSession) + .when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID)); + + // First call onDataReceived() to get the application payload data. This should always have + // the MacAddress (in 8 Bytes), even for a Short MacAddress (MSB are zeroed out). + mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK, + DATA_SEQUENCE_NUM, PEER_EXTENDED_SHORT_MAC_ADDRESS, + SOURCE_END_POINT, DEST_END_POINT, DATA_PAYLOAD); + + // Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF. + UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( + RANGING_MEASUREMENT_TYPE_OWR_AOA, MAC_ADDRESSING_MODE_SHORT, + UwbUciConstants.STATUS_CODE_OK); + Params firaParams = setupFiraParams( + RANGING_DEVICE_ROLE_OBSERVER, Optional.of(ROUND_USAGE_OWR_AOA_MEASUREMENT)); + when(mockUwbSession.getParams()).thenReturn(firaParams); + when(mUwbAdvertiseManager.isPointedTarget(PEER_SHORT_MAC_ADDRESS)).thenReturn(true); + mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData); + + verify(mUwbSessionNotificationManager) + .onRangingResult(eq(mockUwbSession), eq(uwbRangingData)); + verify(mUwbAdvertiseManager).updateAdvertiseTarget(uwbRangingData.mRangingOwrAoaMeasure); + verify(mUwbSessionNotificationManager) + .onDataReceived(eq(mockUwbSession), eq(PEER_SHORT_UWB_ADDRESS), isA(PersistableBundle.class), eq(DATA_PAYLOAD)); + verify(mUwbAdvertiseManager).removeAdvertiseTarget(PEER_SHORT_MAC_ADDRESS); verify(mUwbMetrics).logRangingResult(anyInt(), eq(uwbRangingData)); } @Test public void onRangeDataNotificationReceived_owrAoa_missingUwbSession() { UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( - RANGING_MEASUREMENT_TYPE_OWR_AOA, UwbUciConstants.STATUS_CODE_OK); + RANGING_MEASUREMENT_TYPE_OWR_AOA, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_OK); + + // Setup the test scenario such that the UwbSession (from the RANGE_DATA_NTF) doesn't exist. doReturn(null) .when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID)); + // First call onDataReceived() to get the application payload data. mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK, DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT, DATA_PAYLOAD); + + // Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF. mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData); verifyZeroInteractions(mUwbAdvertiseManager, mUwbSessionNotificationManager, mUwbMetrics); } @Test + public void onRangeDataNotificationReceived_incorrectRangingMeasureType() { + UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( + RANGING_MEASUREMENT_TYPE_UNDEFINED, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_OK); + UwbSession mockUwbSession = mock(UwbSession.class); + when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class)); + doReturn(mockUwbSession) + .when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID)); + + // First call onDataReceived() to get the application payload data. + mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK, + DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT, + DATA_PAYLOAD); + + // Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF. + mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData); + + verify(mUwbSessionNotificationManager) + .onRangingResult(eq(mockUwbSession), eq(uwbRangingData)); + verify(mUwbMetrics).logRangingResult(anyInt(), eq(uwbRangingData)); + verifyZeroInteractions(mUwbAdvertiseManager); + } + + @Test + public void onRangeDataNotificationReceived_owrAoa_incorrectRangingRoundUsage() { + UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( + RANGING_MEASUREMENT_TYPE_OWR_AOA, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_OK); + UwbSession mockUwbSession = mock(UwbSession.class); + when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class)); + doReturn(mockUwbSession) + .when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID)); + + // First call onDataReceived() to get the application payload data. + mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK, + DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT, + DATA_PAYLOAD); + + // Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF (with an + // incorrect RangingRoundUsage value). + Params firaParams = setupFiraParams( + RANGING_DEVICE_ROLE_OBSERVER, Optional.of(ROUND_USAGE_DS_TWR_DEFERRED_MODE)); + when(mockUwbSession.getParams()).thenReturn(firaParams); + mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData); + + verify(mUwbSessionNotificationManager) + .onRangingResult(eq(mockUwbSession), eq(uwbRangingData)); + verify(mUwbMetrics).logRangingResult(anyInt(), eq(uwbRangingData)); + verifyZeroInteractions(mUwbAdvertiseManager); + } + + @Test + public void onRangeDataNotificationReceived_owrAoa_incorrectDeviceRole() { + UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( + RANGING_MEASUREMENT_TYPE_OWR_AOA, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_OK); + UwbSession mockUwbSession = mock(UwbSession.class); + when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class)); + doReturn(mockUwbSession) + .when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID)); + + // First call onDataReceived() to get the application payload data. + mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK, + DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT, + DATA_PAYLOAD); + + // Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF. + Params firaParams = setupFiraParams( + RANGING_DEVICE_ROLE_ADVERTISER, Optional.of(ROUND_USAGE_OWR_AOA_MEASUREMENT)); + when(mockUwbSession.getParams()).thenReturn(firaParams); + mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData); + + verify(mUwbSessionNotificationManager) + .onRangingResult(eq(mockUwbSession), eq(uwbRangingData)); + verify(mUwbMetrics).logRangingResult(anyInt(), eq(uwbRangingData)); + verifyZeroInteractions(mUwbAdvertiseManager); + } + + @Test public void onRangeDataNotificationReceived_owrAoa_receivedDataNotCalled() { UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( - RANGING_MEASUREMENT_TYPE_OWR_AOA, UwbUciConstants.STATUS_CODE_OK); + RANGING_MEASUREMENT_TYPE_OWR_AOA, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_OK); UwbSession mockUwbSession = mock(UwbSession.class); when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class)); doReturn(mockUwbSession) .when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID)); - // Skip call to mUwbSessionManager.onDataReceived() + // Skip call to mUwbSessionManager.onDataReceived(). This means there is no application + // payload data, and so mUwbSessionNotificationManager.onDataReceived() shouldn't be called. + Params firaParams = setupFiraParams( + RANGING_DEVICE_ROLE_OBSERVER, Optional.of(ROUND_USAGE_OWR_AOA_MEASUREMENT)); + when(mockUwbSession.getParams()).thenReturn(firaParams); mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData); verify(mUwbSessionNotificationManager) @@ -326,7 +473,8 @@ public class UwbSessionManagerTest { @Test public void onRangeDataNotificationReceived_owrAoa_receivedDataDifferentMacAddress() { UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( - RANGING_MEASUREMENT_TYPE_OWR_AOA, UwbUciConstants.STATUS_CODE_OK); + RANGING_MEASUREMENT_TYPE_OWR_AOA, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_OK); UwbSession mockUwbSession = mock(UwbSession.class); when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class)); doReturn(mockUwbSession) @@ -337,6 +485,11 @@ public class UwbSessionManagerTest { mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK, DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS_2, SOURCE_END_POINT, DEST_END_POINT, DATA_PAYLOAD); + + // Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF. + Params firaParams = setupFiraParams( + RANGING_DEVICE_ROLE_OBSERVER, Optional.of(ROUND_USAGE_OWR_AOA_MEASUREMENT)); + when(mockUwbSession.getParams()).thenReturn(firaParams); mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData); verify(mUwbSessionNotificationManager) @@ -351,7 +504,8 @@ public class UwbSessionManagerTest { @Test public void onRangeDataNotificationReceived_owrAoa_receivedDataDifferentUwbSession() { UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( - RANGING_MEASUREMENT_TYPE_OWR_AOA, UwbUciConstants.STATUS_CODE_OK); + RANGING_MEASUREMENT_TYPE_OWR_AOA, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_OK); UwbSession mockUwbSession = mock(UwbSession.class); when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class)); doReturn(mockUwbSession) @@ -363,11 +517,15 @@ public class UwbSessionManagerTest { DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT, DATA_PAYLOAD); + // Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF. UwbSession mockUwbSession2 = mock(UwbSession.class); when(mockUwbSession2.getWaitObj()).thenReturn(mock(WaitObj.class)); doReturn(mockUwbSession2) .when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID_2)); + Params firaParams = setupFiraParams( + RANGING_DEVICE_ROLE_OBSERVER, Optional.of(ROUND_USAGE_OWR_AOA_MEASUREMENT)); + when(mockUwbSession.getParams()).thenReturn(firaParams); mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData); verify(mUwbSessionNotificationManager) @@ -382,7 +540,8 @@ public class UwbSessionManagerTest { @Test public void onRangeDataNotificationReceived_owrAoa_notPointedTarget() { UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( - RANGING_MEASUREMENT_TYPE_OWR_AOA, UwbUciConstants.STATUS_CODE_OK); + RANGING_MEASUREMENT_TYPE_OWR_AOA, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_OK); UwbSession mockUwbSession = mock(UwbSession.class); when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class)); doReturn(mockUwbSession) @@ -399,6 +558,7 @@ public class UwbSessionManagerTest { verify(mUwbSessionNotificationManager) .onRangingResult(eq(mockUwbSession), eq(uwbRangingData)); verify(mUwbMetrics).logRangingResult(anyInt(), eq(uwbRangingData)); + verify(mUwbAdvertiseManager, never()).removeAdvertiseTarget(isA(byte[].class)); verifyZeroInteractions(mUwbSessionNotificationManager); } @@ -573,27 +733,6 @@ public class UwbSessionManagerTest { } @Test - public void deInitSession_notExistedSession() { - doReturn(false).when(mUwbSessionManager).isExistedSession(any()); - - mUwbSessionManager.deInitSession(mock(SessionHandle.class)); - - verify(mUwbSessionManager, never()).getSessionId(any()); - assertThat(mTestLooper.nextMessage()).isNull(); - } - - @Test - public void deInitSession_success() { - doReturn(true).when(mUwbSessionManager).isExistedSession(any()); - doReturn(TEST_SESSION_ID).when(mUwbSessionManager).getSessionId(any()); - - mUwbSessionManager.deInitSession(mock(SessionHandle.class)); - - verify(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID)); - assertThat(mTestLooper.nextMessage().what).isEqualTo(5); // SESSION_CLOSE - } - - @Test public void startRanging_notExistedSession() { doReturn(false).when(mUwbSessionManager).isExistedSession(any()); doReturn(TEST_SESSION_ID).when(mUwbSessionManager).getSessionId(any()); @@ -678,6 +817,31 @@ public class UwbSessionManagerTest { } @Test + public void stopRanging_currentSessionStateActive_owrAoa() { + UwbSession mockUwbSession = mock(UwbSession.class); + + doReturn(true).when(mUwbSessionManager).isExistedSession(any()); + doReturn(TEST_SESSION_ID).when(mUwbSessionManager).getSessionId(any()); + doReturn(mockUwbSession).when(mUwbSessionManager).getUwbSession(anyInt()); + doReturn(UwbUciConstants.UWB_SESSION_STATE_ACTIVE) + .when(mUwbSessionManager).getCurrentSessionState(anyInt()); + when(mNativeUwbManager.stopRanging(eq(TEST_SESSION_ID), anyString())) + .thenReturn((byte) UwbUciConstants.STATUS_CODE_OK); + + doReturn(PROTOCOL_NAME).when(mockUwbSession).getProtocolName(); + doReturn(0).when(mockUwbSession).getCurrentFiraRangingIntervalMs(); + + // Setup the UwbSession to have the peer device's MacAddress stored (which happens when + // a valid RANGE_DATA_NTF with an OWR AoA Measurement is received). + doReturn(PEER_EXTENDED_MAC_ADDRESS).when(mockUwbSession).getRemoteMacAddress(); + + mUwbSessionManager.stopRanging(mock(SessionHandle.class)); + mTestLooper.dispatchNext(); + + verify(mUwbAdvertiseManager).removeAdvertiseTarget(PEER_EXTENDED_MAC_ADDRESS); + } + + @Test public void stopRanging_currentSessionStateIdle() { doReturn(true).when(mUwbSessionManager).isExistedSession(any()); doReturn(TEST_SESSION_ID).when(mUwbSessionManager).getSessionId(any()); @@ -805,29 +969,6 @@ public class UwbSessionManagerTest { } @Test - public void deinitAllSession() { - UwbSession mockUwbSession1 = mock(UwbSession.class); - mUwbSessionManager.mSessionTable.put(TEST_SESSION_ID, mockUwbSession1); - when(mockUwbSession1.getBinder()).thenReturn(mock(IBinder.class)); - when(mockUwbSession1.getSessionId()).thenReturn(TEST_SESSION_ID); - when(mockUwbSession1.getProtocolName()).thenReturn(FiraParams.PROTOCOL_NAME); - UwbSession mockUwbSession2 = mock(UwbSession.class); - mUwbSessionManager.mSessionTable.put(TEST_SESSION_ID + 100, mockUwbSession2); - when(mockUwbSession2.getBinder()).thenReturn(mock(IBinder.class)); - when(mockUwbSession2.getSessionId()).thenReturn(TEST_SESSION_ID + 100); - when(mockUwbSession2.getProtocolName()).thenReturn(FiraParams.PROTOCOL_NAME); - - mUwbSessionManager.deinitAllSession(); - - verify(mUwbSessionNotificationManager, times(2)) - .onRangingClosedWithApiReasonCode(any(), eq(RangingChangeReason.SYSTEM_POLICY)); - verify(mUwbSessionManager, times(2)).removeSession(any()); - // TODO: enable it when the resetDevice is enabled. - // verify(mNativeUwbManager).resetDevice(eq(UwbUciConstants.UWBS_RESET)); - assertThat(mUwbSessionManager.getSessionCount()).isEqualTo(0); - } - - @Test public void setCurrentSessionState() { UwbSession mockUwbSession = mock(UwbSession.class); mUwbSessionManager.mSessionTable.put(TEST_SESSION_ID, mockUwbSession); @@ -883,21 +1024,8 @@ public class UwbSessionManagerTest { doReturn(false).when(mUwbSessionManager).isExistedSession(anyInt()); IUwbRangingCallbacks mockRangingCallbacks = mock(IUwbRangingCallbacks.class); SessionHandle mockSessionHandle = mock(SessionHandle.class); - Params params = new FiraOpenSessionParams.Builder() - .setDeviceAddress(UwbAddress.fromBytes(new byte[] {(byte) 0x01, (byte) 0x02 })) - .setVendorId(new byte[] { (byte) 0x00, (byte) 0x01 }) - .setStaticStsIV(new byte[] { (byte) 0x01, (byte) 0x02, (byte) 0x03, - (byte) 0x04, (byte) 0x05, (byte) 0x06 }) - .setDestAddressList(Arrays.asList( - UWB_DEST_ADDRESS)) - .setProtocolVersion(new FiraProtocolVersion(1, 0)) - .setSessionId(10) - .setDeviceType(FiraParams.RANGING_DEVICE_TYPE_CONTROLLER) - .setDeviceRole(FiraParams.RANGING_DEVICE_ROLE_INITIATOR) - .setMultiNodeMode(FiraParams.MULTI_NODE_MODE_UNICAST) - .setRangingIntervalMs(TEST_RANGING_INTERVAL_MS) - .build(); IBinder mockBinder = mock(IBinder.class); + Params params = setupFiraParams(); UwbSession uwbSession = spy( mUwbSessionManager.new UwbSession(attributionSource, mockSessionHandle, TEST_SESSION_ID, FiraParams.PROTOCOL_NAME, params, mockRangingCallbacks, @@ -910,6 +1038,32 @@ public class UwbSessionManagerTest { return uwbSession; } + private Params setupFiraParams() { + return setupFiraParams(FiraParams.RANGING_DEVICE_ROLE_INITIATOR, Optional.empty()); + } + + private Params setupFiraParams(int deviceRole, Optional<Integer> rangingRoundUsageOptional) { + FiraOpenSessionParams.Builder paramsBuilder = new FiraOpenSessionParams.Builder() + .setDeviceAddress(UwbAddress.fromBytes(new byte[] {(byte) 0x01, (byte) 0x02 })) + .setVendorId(new byte[] { (byte) 0x00, (byte) 0x01 }) + .setStaticStsIV(new byte[] { (byte) 0x01, (byte) 0x02, (byte) 0x03, + (byte) 0x04, (byte) 0x05, (byte) 0x06 }) + .setDestAddressList(Arrays.asList( + UWB_DEST_ADDRESS)) + .setProtocolVersion(new FiraProtocolVersion(1, 0)) + .setSessionId(10) + .setDeviceType(FiraParams.RANGING_DEVICE_TYPE_CONTROLLER) + .setDeviceRole(deviceRole) + .setMultiNodeMode(FiraParams.MULTI_NODE_MODE_UNICAST) + .setRangingIntervalMs(TEST_RANGING_INTERVAL_MS); + + if (rangingRoundUsageOptional.isPresent()) { + paramsBuilder.setRangingRoundUsage(rangingRoundUsageOptional.get()); + } + + return paramsBuilder.build(); + } + private UwbSession setUpCccUwbSessionForExecution() throws RemoteException { // setup message doReturn(0).when(mUwbSessionManager).getSessionCount(); @@ -1393,7 +1547,8 @@ public class UwbSessionManagerTest { // Now send a range data notification. UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( - RANGING_MEASUREMENT_TYPE_TWO_WAY, UwbUciConstants.STATUS_CODE_OK); + RANGING_MEASUREMENT_TYPE_TWO_WAY, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_OK); mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData); verify(mUwbSessionNotificationManager).onRangingResult(uwbSession, uwbRangingData); } @@ -1427,7 +1582,8 @@ public class UwbSessionManagerTest { // Now send a range data notification with an error. UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( - rangingMeasurementType, UwbUciConstants.STATUS_CODE_RANGING_RX_TIMEOUT); + rangingMeasurementType, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_RANGING_RX_TIMEOUT); mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData); verify(mUwbSessionNotificationManager).onRangingResult(uwbSession, uwbRangingData); ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor = @@ -1438,7 +1594,8 @@ public class UwbSessionManagerTest { // Send one more error and ensure that the timer is not cancelled. uwbRangingData = UwbTestUtils.generateRangingData( - rangingMeasurementType, UwbUciConstants.STATUS_CODE_RANGING_RX_TIMEOUT); + rangingMeasurementType, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_RANGING_RX_TIMEOUT); mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData); verify(mUwbSessionNotificationManager).onRangingResult(uwbSession, uwbRangingData); @@ -1480,7 +1637,8 @@ public class UwbSessionManagerTest { // Now send a range data notification with an error. UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( - RANGING_MEASUREMENT_TYPE_TWO_WAY, UwbUciConstants.STATUS_CODE_RANGING_RX_TIMEOUT); + RANGING_MEASUREMENT_TYPE_TWO_WAY, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_RANGING_RX_TIMEOUT); mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData); verify(mUwbSessionNotificationManager).onRangingResult(uwbSession, uwbRangingData); ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor = @@ -1491,7 +1649,8 @@ public class UwbSessionManagerTest { // Send success and ensure that the timer is cancelled. uwbRangingData = UwbTestUtils.generateRangingData( - RANGING_MEASUREMENT_TYPE_TWO_WAY, UwbUciConstants.STATUS_CODE_OK); + RANGING_MEASUREMENT_TYPE_TWO_WAY, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_OK); mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData); verify(mUwbSessionNotificationManager).onRangingResult(uwbSession, uwbRangingData); @@ -1594,33 +1753,47 @@ public class UwbSessionManagerTest { public void sendData_success_validUwbSession() throws Exception { UwbSession uwbSession = prepareExistingUwbSession(); - when(mNativeUwbManager.sendData(eq(TEST_SESSION_ID), eq(PEER_UWB_ADDRESS.toBytes()), + // Setup the UwbSession to start ranging (and move it to active state). + doReturn(UwbUciConstants.UWB_SESSION_STATE_IDLE).when(uwbSession).getSessionState(); + when(mNativeUwbManager.startRanging(eq(TEST_SESSION_ID), anyString())) + .thenReturn((byte) UwbUciConstants.STATUS_CODE_OK); + + mUwbSessionManager.startRanging( + uwbSession.getSessionHandle(), uwbSession.getParams()); + mTestLooper.dispatchAll(); + doReturn(UwbUciConstants.UWB_SESSION_STATE_ACTIVE).when(uwbSession).getSessionState(); + + // Send data on the UWB session. + when(mNativeUwbManager.sendData(eq(TEST_SESSION_ID), + eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()), eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM), eq(DATA_PAYLOAD))).thenReturn((byte) UwbUciConstants.STATUS_CODE_OK); - mUwbSessionManager.sendData(uwbSession.getSessionHandle(), PEER_UWB_ADDRESS, + mUwbSessionManager.sendData(uwbSession.getSessionHandle(), PEER_EXTENDED_UWB_ADDRESS, PERSISTABLE_BUNDLE, DATA_PAYLOAD); mTestLooper.dispatchNext(); - verify(mNativeUwbManager).sendData(eq(TEST_SESSION_ID), eq(PEER_UWB_ADDRESS.toBytes()), + verify(mNativeUwbManager).sendData(eq(TEST_SESSION_ID), + eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()), eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM), eq(DATA_PAYLOAD)); verify(mUwbSessionNotificationManager).onDataSent( - eq(uwbSession), eq(PEER_UWB_ADDRESS), eq(PERSISTABLE_BUNDLE)); + eq(uwbSession), eq(PEER_EXTENDED_UWB_ADDRESS), eq(PERSISTABLE_BUNDLE)); } @Test public void sendData_missingSessionHandle() throws Exception { mUwbSessionManager.sendData( - null /* sessionHandle */, PEER_UWB_ADDRESS, PERSISTABLE_BUNDLE, DATA_PAYLOAD); + null /* sessionHandle */, PEER_EXTENDED_UWB_ADDRESS, PERSISTABLE_BUNDLE, + DATA_PAYLOAD); mTestLooper.dispatchNext(); verify(mNativeUwbManager, never()).sendData( - eq(TEST_SESSION_ID), eq(PEER_UWB_ADDRESS.toBytes()), + eq(TEST_SESSION_ID), eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()), eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM), eq(DATA_PAYLOAD)); verify(mUwbSessionNotificationManager).onDataSendFailed( - eq(null), eq(PEER_UWB_ADDRESS), + eq(null), eq(PEER_EXTENDED_UWB_ADDRESS), eq(UwbUciConstants.STATUS_CODE_ERROR_SESSION_NOT_EXIST), eq(PERSISTABLE_BUNDLE)); } @@ -1631,32 +1804,63 @@ public class UwbSessionManagerTest { SessionHandle sessionHandle = new SessionHandle(HANDLE_ID, ATTRIBUTION_SOURCE, PID); mUwbSessionManager.sendData( - sessionHandle, PEER_UWB_ADDRESS, PERSISTABLE_BUNDLE, DATA_PAYLOAD); + sessionHandle, PEER_EXTENDED_UWB_ADDRESS, PERSISTABLE_BUNDLE, DATA_PAYLOAD); mTestLooper.dispatchNext(); verify(mNativeUwbManager, never()).sendData( - eq(TEST_SESSION_ID), eq(PEER_UWB_ADDRESS.toBytes()), + eq(TEST_SESSION_ID), eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()), eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM), eq(DATA_PAYLOAD)); verify(mUwbSessionNotificationManager).onDataSendFailed( - eq(null), eq(PEER_UWB_ADDRESS), + eq(null), eq(PEER_EXTENDED_UWB_ADDRESS), eq(UwbUciConstants.STATUS_CODE_ERROR_SESSION_NOT_EXIST), eq(PERSISTABLE_BUNDLE)); } @Test + public void sendData_invalidUwbSessionState() throws Exception { + // Setup a uwbSession and don't start ranging, so it remains in IDLE state. + UwbSession uwbSession = prepareExistingUwbSession(); + doReturn(UwbUciConstants.UWB_SESSION_STATE_IDLE).when(uwbSession).getSessionState(); + + // Attempt to send data on the UWB session. + mUwbSessionManager.sendData( + uwbSession.getSessionHandle(), PEER_EXTENDED_UWB_ADDRESS, PERSISTABLE_BUNDLE, null); + mTestLooper.dispatchNext(); + + verify(mNativeUwbManager, never()).sendData( + eq(TEST_SESSION_ID), eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()), + eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM), + eq(DATA_PAYLOAD)); + verify(mUwbSessionNotificationManager).onDataSendFailed( + eq(uwbSession), eq(PEER_EXTENDED_UWB_ADDRESS), + eq(UwbUciConstants.STATUS_CODE_FAILED), eq(PERSISTABLE_BUNDLE)); + } + + @Test public void sendData_missingDataPayload() throws Exception { UwbSession uwbSession = prepareExistingUwbSession(); + // Setup the UwbSession to start ranging (and move it to active state). + doReturn(UwbUciConstants.UWB_SESSION_STATE_IDLE).when(uwbSession).getSessionState(); + when(mNativeUwbManager.startRanging(eq(TEST_SESSION_ID), anyString())) + .thenReturn((byte) UwbUciConstants.STATUS_CODE_OK); + + mUwbSessionManager.startRanging( + uwbSession.getSessionHandle(), uwbSession.getParams()); + mTestLooper.dispatchAll(); + doReturn(UwbUciConstants.UWB_SESSION_STATE_ACTIVE).when(uwbSession).getSessionState(); + + // Attempt to send data on the UWB session. mUwbSessionManager.sendData( - uwbSession.getSessionHandle(), PEER_UWB_ADDRESS, PERSISTABLE_BUNDLE, null); + uwbSession.getSessionHandle(), PEER_EXTENDED_UWB_ADDRESS, PERSISTABLE_BUNDLE, null); mTestLooper.dispatchNext(); verify(mNativeUwbManager, never()).sendData( - eq(TEST_SESSION_ID), eq(PEER_UWB_ADDRESS.toBytes()), + eq(TEST_SESSION_ID), eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()), eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM), eq(DATA_PAYLOAD)); verify(mUwbSessionNotificationManager).onDataSendFailed( - eq(uwbSession), eq(PEER_UWB_ADDRESS), + eq(uwbSession), eq(PEER_EXTENDED_UWB_ADDRESS), eq(UwbUciConstants.STATUS_CODE_INVALID_PARAM), eq(PERSISTABLE_BUNDLE)); } @@ -1664,12 +1868,23 @@ public class UwbSessionManagerTest { public void sendData_missingRemoteDevice() throws Exception { UwbSession uwbSession = prepareExistingUwbSession(); + // Setup the UwbSession to start ranging (and move it to active state). + doReturn(UwbUciConstants.UWB_SESSION_STATE_IDLE).when(uwbSession).getSessionState(); + when(mNativeUwbManager.startRanging(eq(TEST_SESSION_ID), anyString())) + .thenReturn((byte) UwbUciConstants.STATUS_CODE_OK); + + mUwbSessionManager.startRanging( + uwbSession.getSessionHandle(), uwbSession.getParams()); + mTestLooper.dispatchAll(); + doReturn(UwbUciConstants.UWB_SESSION_STATE_ACTIVE).when(uwbSession).getSessionState(); + + // Attempt to send data on the UWB session. mUwbSessionManager.sendData( uwbSession.getSessionHandle(), null, PERSISTABLE_BUNDLE, DATA_PAYLOAD); mTestLooper.dispatchNext(); verify(mNativeUwbManager, never()).sendData( - eq(TEST_SESSION_ID), eq(PEER_UWB_ADDRESS.toBytes()), + eq(TEST_SESSION_ID), eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()), eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM), eq(DATA_PAYLOAD)); verify(mUwbSessionNotificationManager).onDataSendFailed( @@ -1681,19 +1896,32 @@ public class UwbSessionManagerTest { public void sendData_dataSendFailure() throws Exception { UwbSession uwbSession = prepareExistingUwbSession(); - when(mNativeUwbManager.sendData(eq(TEST_SESSION_ID), eq(PEER_UWB_ADDRESS.toBytes()), + // Setup the UwbSession to start ranging (and move it to active state). + doReturn(UwbUciConstants.UWB_SESSION_STATE_IDLE).when(uwbSession).getSessionState(); + when(mNativeUwbManager.startRanging(eq(TEST_SESSION_ID), anyString())) + .thenReturn((byte) UwbUciConstants.STATUS_CODE_OK); + + mUwbSessionManager.startRanging( + uwbSession.getSessionHandle(), uwbSession.getParams()); + mTestLooper.dispatchAll(); + doReturn(UwbUciConstants.UWB_SESSION_STATE_ACTIVE).when(uwbSession).getSessionState(); + + // Attempt to send data on the UWB session. + when(mNativeUwbManager.sendData(eq(TEST_SESSION_ID), + eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()), eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM), eq(DATA_PAYLOAD))).thenReturn((byte) UwbUciConstants.STATUS_CODE_FAILED); - mUwbSessionManager.sendData(uwbSession.getSessionHandle(), PEER_UWB_ADDRESS, + mUwbSessionManager.sendData(uwbSession.getSessionHandle(), PEER_EXTENDED_UWB_ADDRESS, PERSISTABLE_BUNDLE, DATA_PAYLOAD); mTestLooper.dispatchNext(); - verify(mNativeUwbManager).sendData(eq(TEST_SESSION_ID), eq(PEER_UWB_ADDRESS.toBytes()), + verify(mNativeUwbManager).sendData(eq(TEST_SESSION_ID), + eq(PEER_EXTENDED_UWB_ADDRESS.toBytes()), eq(UwbUciConstants.UWB_DESTINATION_END_POINT_HOST), eq(DATA_SEQUENCE_NUM), eq(DATA_PAYLOAD)); verify(mUwbSessionNotificationManager).onDataSendFailed( - eq(uwbSession), eq(PEER_UWB_ADDRESS), + eq(uwbSession), eq(PEER_EXTENDED_UWB_ADDRESS), eq(UwbUciConstants.STATUS_CODE_FAILED), eq(PERSISTABLE_BUNDLE)); } @@ -2148,16 +2376,63 @@ public class UwbSessionManagerTest { } @Test - public void deInitSession() throws Exception { + public void deInitSession_notExistedSession() { + doReturn(false).when(mUwbSessionManager).isExistedSession(any()); + + mUwbSessionManager.deInitSession(mock(SessionHandle.class)); + + verify(mUwbSessionManager, never()).getSessionId(any()); + assertThat(mTestLooper.nextMessage()).isNull(); + } + + @Test + public void deInitSession_success() { + doReturn(true).when(mUwbSessionManager).isExistedSession(any()); + doReturn(TEST_SESSION_ID).when(mUwbSessionManager).getSessionId(any()); + + mUwbSessionManager.deInitSession(mock(SessionHandle.class)); + + verify(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID)); + assertThat(mTestLooper.nextMessage().what).isEqualTo(5); // SESSION_DEINIT + + verifyZeroInteractions(mUwbAdvertiseManager); + } + + @Test + public void deInitSession_success_afterOwrAoaMeasurement() { + UwbSession mockUwbSession = mock(UwbSession.class); + when(mockUwbSession.getWaitObj()).thenReturn(mock(WaitObj.class)); + doReturn(mockUwbSession).when(mUwbSessionManager).getUwbSession(eq(TEST_SESSION_ID)); + + // Setup the UwbSession to have the peer device's MacAddress stored (which happens when + // a valid RANGE_DATA_NTF with an OWR AoA Measurement is received). + doReturn(PEER_EXTENDED_MAC_ADDRESS).when(mockUwbSession).getRemoteMacAddress(); + + // Call deInitSession(). + IBinder mockBinder = mock(IBinder.class); + doReturn(mockBinder).when(mockUwbSession).getBinder(); + doReturn(FiraParams.PROTOCOL_NAME).when(mockUwbSession).getProtocolName(); + doReturn(null).when(mockUwbSession).getAnyNonPrivilegedAppInAttributionSource(); + doReturn(true).when(mUwbSessionManager).isExistedSession(any()); + doReturn(TEST_SESSION_ID).when(mUwbSessionManager).getSessionId(any()); + mUwbSessionManager.deInitSession(mock(SessionHandle.class)); + + mTestLooper.dispatchNext(); + + verify(mUwbAdvertiseManager).removeAdvertiseTarget(PEER_EXTENDED_MAC_ADDRESS); + } + + @Test + public void execDeInitSession() throws Exception { UwbSession uwbSession = prepareExistingUwbSession(); mUwbSessionManager.deInitSession(uwbSession.getSessionHandle()); - assertThat(mTestLooper.nextMessage().what).isEqualTo(5); // SESSION_CLOSE + assertThat(mTestLooper.nextMessage().what).isEqualTo(5); // SESSION_DEINIT } @Test - public void execCloseSession_success() throws Exception { + public void execDeInitSession_success() throws Exception { UwbSession uwbSession = prepareExistingUwbSession(); when(mNativeUwbManager.deInitSession(eq(TEST_SESSION_ID), anyString())) .thenReturn((byte) UwbUciConstants.STATUS_CODE_OK); @@ -2170,10 +2445,11 @@ public class UwbSessionManagerTest { verify(mUwbMetrics).logRangingCloseEvent( eq(uwbSession), eq(UwbUciConstants.STATUS_CODE_OK)); assertThat(mUwbSessionManager.getSessionCount()).isEqualTo(0); + verifyZeroInteractions(mUwbAdvertiseManager); } @Test - public void execCloseSession_failed() throws Exception { + public void execDeInitSession_failed() throws Exception { UwbSession uwbSession = prepareExistingUwbSession(); when(mNativeUwbManager.deInitSession(eq(TEST_SESSION_ID), anyString())) .thenReturn((byte) UwbUciConstants.STATUS_CODE_FAILED); @@ -2183,9 +2459,34 @@ public class UwbSessionManagerTest { verify(mUwbSessionNotificationManager).onRangingClosed( eq(uwbSession), eq(UwbUciConstants.STATUS_CODE_FAILED)); + verify(mUwbAdvertiseManager, never()).removeAdvertiseTarget(isA(byte[].class)); verify(mUwbMetrics).logRangingCloseEvent( eq(uwbSession), eq(UwbUciConstants.STATUS_CODE_FAILED)); assertThat(mUwbSessionManager.getSessionCount()).isEqualTo(0); + verifyZeroInteractions(mUwbAdvertiseManager); + } + + @Test + public void deinitAllSession() { + UwbSession mockUwbSession1 = mock(UwbSession.class); + mUwbSessionManager.mSessionTable.put(TEST_SESSION_ID, mockUwbSession1); + when(mockUwbSession1.getBinder()).thenReturn(mock(IBinder.class)); + when(mockUwbSession1.getSessionId()).thenReturn(TEST_SESSION_ID); + when(mockUwbSession1.getProtocolName()).thenReturn(FiraParams.PROTOCOL_NAME); + UwbSession mockUwbSession2 = mock(UwbSession.class); + mUwbSessionManager.mSessionTable.put(TEST_SESSION_ID + 100, mockUwbSession2); + when(mockUwbSession2.getBinder()).thenReturn(mock(IBinder.class)); + when(mockUwbSession2.getSessionId()).thenReturn(TEST_SESSION_ID + 100); + when(mockUwbSession2.getProtocolName()).thenReturn(FiraParams.PROTOCOL_NAME); + + mUwbSessionManager.deinitAllSession(); + + verify(mUwbSessionNotificationManager, times(2)) + .onRangingClosedWithApiReasonCode(any(), eq(RangingChangeReason.SYSTEM_POLICY)); + verify(mUwbSessionManager, times(2)).removeSession(any()); + // TODO: enable it when the resetDevice is enabled. + // verify(mNativeUwbManager).resetDevice(eq(UwbUciConstants.UWBS_RESET)); + assertThat(mUwbSessionManager.getSessionCount()).isEqualTo(0); } @Test @@ -2207,6 +2508,49 @@ public class UwbSessionManagerTest { } @Test + public void onSessionStatusNotification_session_deinit_owrAoa() throws Exception { + UwbSession uwbSession = prepareExistingUwbSession(); + UwbRangingData uwbRangingData = UwbTestUtils.generateRangingData( + RANGING_MEASUREMENT_TYPE_OWR_AOA, MAC_ADDRESSING_MODE_EXTENDED, + UwbUciConstants.STATUS_CODE_OK); + + // First call onDataReceived() to get the application payload data. + mUwbSessionManager.onDataReceived(TEST_SESSION_ID, UwbUciConstants.STATUS_CODE_OK, + DATA_SEQUENCE_NUM, PEER_EXTENDED_MAC_ADDRESS, SOURCE_END_POINT, DEST_END_POINT, + DATA_PAYLOAD); + + // Next call onRangeDataNotificationReceived() to process the RANGE_DATA_NTF. Setup + // isPointedTarget() to return "false", as in that scenario the stored AdvertiseTarget + // is not removed. + Params firaParams = setupFiraParams( + RANGING_DEVICE_ROLE_OBSERVER, Optional.of(ROUND_USAGE_OWR_AOA_MEASUREMENT)); + when(uwbSession.getParams()).thenReturn(firaParams); + when(mUwbAdvertiseManager.isPointedTarget(PEER_EXTENDED_MAC_ADDRESS)).thenReturn(false); + mUwbSessionManager.onRangeDataNotificationReceived(uwbRangingData); + + verify(mUwbAdvertiseManager).updateAdvertiseTarget(uwbRangingData.mRangingOwrAoaMeasure); + verify(mUwbAdvertiseManager).isPointedTarget(PEER_EXTENDED_MAC_ADDRESS); + + // Now call onSessionStatusNotificationReceived() on the same UwbSession, and verify that + // removeAdvertiseTarget() is called to remove any stored OwR AoA Measurement(s). + when(mNativeUwbManager.deInitSession(eq(TEST_SESSION_ID), anyString())) + .thenReturn((byte) UwbUciConstants.STATUS_CODE_OK); + + mUwbSessionManager.onSessionStatusNotificationReceived( + uwbSession.getSessionId(), UwbUciConstants.UWB_SESSION_STATE_DEINIT, + UwbUciConstants.REASON_STATE_CHANGE_WITH_SESSION_MANAGEMENT_COMMANDS); + mTestLooper.dispatchNext(); + + verify(mUwbSessionNotificationManager).onRangingClosedWithApiReasonCode( + eq(uwbSession), eq(RangingChangeReason.SYSTEM_POLICY)); + verify(mUwbMetrics).logRangingCloseEvent( + eq(uwbSession), eq(UwbUciConstants.STATUS_CODE_OK)); + assertThat(mUwbSessionManager.getSessionCount()).isEqualTo(0); + + verify(mUwbAdvertiseManager).removeAdvertiseTarget(isA(byte[].class)); + } + + @Test public void testHandleClientDeath() throws Exception { UwbSession uwbSession = prepareExistingUwbSession(); when(mNativeUwbManager.deInitSession(eq(TEST_SESSION_ID), anyString())) diff --git a/service/tests/src/com/android/server/uwb/UwbSessionNotificationManagerTest.java b/service/tests/src/com/android/server/uwb/UwbSessionNotificationManagerTest.java index 7cfce4db..5b3810da 100644 --- a/service/tests/src/com/android/server/uwb/UwbSessionNotificationManagerTest.java +++ b/service/tests/src/com/android/server/uwb/UwbSessionNotificationManagerTest.java @@ -17,10 +17,11 @@ package com.android.server.uwb; import static com.android.server.uwb.UwbTestUtils.DATA_PAYLOAD; -import static com.android.server.uwb.UwbTestUtils.PEER_EXTENDED_MAC_ADDRESS; +import static com.android.server.uwb.UwbTestUtils.PEER_EXTENDED_UWB_ADDRESS; import static com.android.server.uwb.UwbTestUtils.PEER_SHORT_MAC_ADDRESS; -import static com.android.server.uwb.UwbTestUtils.PEER_UWB_ADDRESS; import static com.android.server.uwb.UwbTestUtils.PERSISTABLE_BUNDLE; +import static com.android.server.uwb.data.UwbUciConstants.MAC_ADDRESSING_MODE_SHORT; +import static com.android.server.uwb.data.UwbUciConstants.RANGING_MEASUREMENT_TYPE_DL_TDOA; import static com.android.server.uwb.data.UwbUciConstants.RANGING_MEASUREMENT_TYPE_OWR_AOA; import static com.android.server.uwb.data.UwbUciConstants.RANGING_MEASUREMENT_TYPE_TWO_WAY; import static com.android.server.uwb.data.UwbUciConstants.STATUS_CODE_FAILED; @@ -110,7 +111,8 @@ public class UwbSessionNotificationManagerTest { public void testOnRangingResultWithoutUwbRangingPermission() throws Exception { Pair<UwbRangingData, RangingReport> testRangingDataAndRangingReport = UwbTestUtils.generateRangingDataAndRangingReport( - PEER_SHORT_MAC_ADDRESS, RANGING_MEASUREMENT_TYPE_TWO_WAY, + PEER_SHORT_MAC_ADDRESS, MAC_ADDRESSING_MODE_SHORT, + RANGING_MEASUREMENT_TYPE_TWO_WAY, true, true, false, false, TEST_ELAPSED_NANOS); when(mUwbInjector.checkUwbRangingPermissionForDataDelivery(eq(ATTRIBUTION_SOURCE), any())) .thenReturn(false); @@ -124,7 +126,8 @@ public class UwbSessionNotificationManagerTest { public void testOnRangingResult_forTwoWay_WithAoa() throws Exception { Pair<UwbRangingData, RangingReport> testRangingDataAndRangingReport = UwbTestUtils.generateRangingDataAndRangingReport( - PEER_SHORT_MAC_ADDRESS, RANGING_MEASUREMENT_TYPE_TWO_WAY, + PEER_SHORT_MAC_ADDRESS, MAC_ADDRESSING_MODE_SHORT, + RANGING_MEASUREMENT_TYPE_TWO_WAY, true, true, false, false, TEST_ELAPSED_NANOS); mUwbSessionNotificationManager.onRangingResult( mUwbSession, testRangingDataAndRangingReport.first); @@ -138,7 +141,8 @@ public class UwbSessionNotificationManagerTest { FiraParams.AOA_RESULT_REQUEST_MODE_NO_AOA_REPORT); Pair<UwbRangingData, RangingReport> testRangingDataAndRangingReport = UwbTestUtils.generateRangingDataAndRangingReport( - PEER_SHORT_MAC_ADDRESS, RANGING_MEASUREMENT_TYPE_TWO_WAY, + PEER_SHORT_MAC_ADDRESS, MAC_ADDRESSING_MODE_SHORT, + RANGING_MEASUREMENT_TYPE_TWO_WAY, false, false, false, false, TEST_ELAPSED_NANOS); mUwbSessionNotificationManager.onRangingResult( mUwbSession, testRangingDataAndRangingReport.first); @@ -152,7 +156,8 @@ public class UwbSessionNotificationManagerTest { FiraParams.AOA_RESULT_REQUEST_MODE_REQ_AOA_RESULTS_AZIMUTH_ONLY); Pair<UwbRangingData, RangingReport> testRangingDataAndRangingReport = UwbTestUtils.generateRangingDataAndRangingReport( - PEER_SHORT_MAC_ADDRESS, RANGING_MEASUREMENT_TYPE_TWO_WAY, + PEER_SHORT_MAC_ADDRESS, MAC_ADDRESSING_MODE_SHORT, + RANGING_MEASUREMENT_TYPE_TWO_WAY, true, false, false, false, TEST_ELAPSED_NANOS); mUwbSessionNotificationManager.onRangingResult( mUwbSession, testRangingDataAndRangingReport.first); @@ -166,7 +171,8 @@ public class UwbSessionNotificationManagerTest { FiraParams.AOA_RESULT_REQUEST_MODE_REQ_AOA_RESULTS_ELEVATION_ONLY); Pair<UwbRangingData, RangingReport> testRangingDataAndRangingReport = UwbTestUtils.generateRangingDataAndRangingReport( - PEER_SHORT_MAC_ADDRESS, RANGING_MEASUREMENT_TYPE_TWO_WAY, + PEER_SHORT_MAC_ADDRESS, MAC_ADDRESSING_MODE_SHORT, + RANGING_MEASUREMENT_TYPE_TWO_WAY, false, true, false, false, TEST_ELAPSED_NANOS); mUwbSessionNotificationManager.onRangingResult( mUwbSession, testRangingDataAndRangingReport.first); @@ -183,7 +189,8 @@ public class UwbSessionNotificationManagerTest { when(mFiraParams.hasAngleOfArrivalElevationReport()).thenReturn(true); Pair<UwbRangingData, RangingReport> testRangingDataAndRangingReport = UwbTestUtils.generateRangingDataAndRangingReport( - PEER_SHORT_MAC_ADDRESS, RANGING_MEASUREMENT_TYPE_TWO_WAY, + PEER_SHORT_MAC_ADDRESS, MAC_ADDRESSING_MODE_SHORT, + RANGING_MEASUREMENT_TYPE_TWO_WAY, true, true, true, true, TEST_ELAPSED_NANOS); mUwbSessionNotificationManager.onRangingResult( mUwbSession, testRangingDataAndRangingReport.first); @@ -200,7 +207,8 @@ public class UwbSessionNotificationManagerTest { when(mFiraParams.hasAngleOfArrivalElevationReport()).thenReturn(true); Pair<UwbRangingData, RangingReport> testRangingDataAndRangingReport = UwbTestUtils.generateRangingDataAndRangingReport( - PEER_SHORT_MAC_ADDRESS, RANGING_MEASUREMENT_TYPE_TWO_WAY, + PEER_SHORT_MAC_ADDRESS, MAC_ADDRESSING_MODE_SHORT, + RANGING_MEASUREMENT_TYPE_TWO_WAY, true, true, false, true, TEST_ELAPSED_NANOS); mUwbSessionNotificationManager.onRangingResult( mUwbSession, testRangingDataAndRangingReport.first); @@ -217,7 +225,8 @@ public class UwbSessionNotificationManagerTest { when(mFiraParams.hasAngleOfArrivalElevationReport()).thenReturn(false); Pair<UwbRangingData, RangingReport> testRangingDataAndRangingReport = UwbTestUtils.generateRangingDataAndRangingReport( - PEER_SHORT_MAC_ADDRESS, RANGING_MEASUREMENT_TYPE_TWO_WAY, + PEER_SHORT_MAC_ADDRESS, MAC_ADDRESSING_MODE_SHORT, + RANGING_MEASUREMENT_TYPE_TWO_WAY, true, true, true, false, TEST_ELAPSED_NANOS); mUwbSessionNotificationManager.onRangingResult( mUwbSession, testRangingDataAndRangingReport.first); @@ -234,7 +243,8 @@ public class UwbSessionNotificationManagerTest { when(mFiraParams.hasAngleOfArrivalElevationReport()).thenReturn(true); Pair<UwbRangingData, RangingReport> testRangingDataAndRangingReport = UwbTestUtils.generateRangingDataAndRangingReport( - PEER_SHORT_MAC_ADDRESS, RANGING_MEASUREMENT_TYPE_TWO_WAY, + PEER_SHORT_MAC_ADDRESS, MAC_ADDRESSING_MODE_SHORT, + RANGING_MEASUREMENT_TYPE_TWO_WAY, false, false, true, true, TEST_ELAPSED_NANOS); mUwbSessionNotificationManager.onRangingResult( mUwbSession, testRangingDataAndRangingReport.first); @@ -251,7 +261,8 @@ public class UwbSessionNotificationManagerTest { when(mFiraParams.hasAngleOfArrivalElevationReport()).thenReturn(true); Pair<UwbRangingData, RangingReport> testRangingDataAndRangingReport = UwbTestUtils.generateRangingDataAndRangingReport( - PEER_SHORT_MAC_ADDRESS, RANGING_MEASUREMENT_TYPE_TWO_WAY, + PEER_SHORT_MAC_ADDRESS, MAC_ADDRESSING_MODE_SHORT, + RANGING_MEASUREMENT_TYPE_TWO_WAY, false, true, true, true, TEST_ELAPSED_NANOS); mUwbSessionNotificationManager.onRangingResult( mUwbSession, testRangingDataAndRangingReport.first); @@ -268,7 +279,8 @@ public class UwbSessionNotificationManagerTest { when(mFiraParams.hasAngleOfArrivalElevationReport()).thenReturn(true); Pair<UwbRangingData, RangingReport> testRangingDataAndRangingReport = UwbTestUtils.generateRangingDataAndRangingReport( - PEER_SHORT_MAC_ADDRESS, RANGING_MEASUREMENT_TYPE_TWO_WAY, + PEER_SHORT_MAC_ADDRESS, MAC_ADDRESSING_MODE_SHORT, + RANGING_MEASUREMENT_TYPE_TWO_WAY, true, false, true, true, TEST_ELAPSED_NANOS); mUwbSessionNotificationManager.onRangingResult( mUwbSession, testRangingDataAndRangingReport.first); @@ -280,7 +292,21 @@ public class UwbSessionNotificationManagerTest { public void testOnRangingResult_forOwrAoa() throws Exception { Pair<UwbRangingData, RangingReport> testRangingDataAndRangingReport = UwbTestUtils.generateRangingDataAndRangingReport( - PEER_EXTENDED_MAC_ADDRESS, RANGING_MEASUREMENT_TYPE_OWR_AOA, + PEER_SHORT_MAC_ADDRESS, MAC_ADDRESSING_MODE_SHORT, + RANGING_MEASUREMENT_TYPE_OWR_AOA, + true, true, false, false, TEST_ELAPSED_NANOS); + mUwbSessionNotificationManager.onRangingResult( + mUwbSession, testRangingDataAndRangingReport.first); + verify(mIUwbRangingCallbacks).onRangingResult( + mSessionHandle, testRangingDataAndRangingReport.second); + } + + @Test + public void testOnRangingResult_forDlTDoA() throws Exception { + Pair<UwbRangingData, RangingReport> testRangingDataAndRangingReport = + UwbTestUtils.generateRangingDataAndRangingReport( + PEER_SHORT_MAC_ADDRESS, MAC_ADDRESSING_MODE_SHORT, + RANGING_MEASUREMENT_TYPE_DL_TDOA, true, true, false, false, TEST_ELAPSED_NANOS); mUwbSessionNotificationManager.onRangingResult( mUwbSession, testRangingDataAndRangingReport.first); @@ -436,37 +462,40 @@ public class UwbSessionNotificationManagerTest { @Test public void testOnDataReceived() throws Exception { - mUwbSessionNotificationManager.onDataReceived(mUwbSession, PEER_UWB_ADDRESS, + mUwbSessionNotificationManager.onDataReceived(mUwbSession, PEER_EXTENDED_UWB_ADDRESS, PERSISTABLE_BUNDLE, DATA_PAYLOAD); - verify(mIUwbRangingCallbacks).onDataReceived(eq(mSessionHandle), eq(PEER_UWB_ADDRESS), + verify(mIUwbRangingCallbacks).onDataReceived(eq(mSessionHandle), eq( + PEER_EXTENDED_UWB_ADDRESS), eq(PERSISTABLE_BUNDLE), eq(DATA_PAYLOAD)); } @Test public void testOnDataReceiveFailed() throws Exception { - mUwbSessionNotificationManager.onDataReceiveFailed(mUwbSession, PEER_UWB_ADDRESS, + mUwbSessionNotificationManager.onDataReceiveFailed(mUwbSession, PEER_EXTENDED_UWB_ADDRESS, STATUS_CODE_FAILED, PERSISTABLE_BUNDLE); - verify(mIUwbRangingCallbacks).onDataReceiveFailed(eq(mSessionHandle), eq(PEER_UWB_ADDRESS), + verify(mIUwbRangingCallbacks).onDataReceiveFailed(eq(mSessionHandle), eq( + PEER_EXTENDED_UWB_ADDRESS), eq(STATUS_CODE_FAILED), eq(PERSISTABLE_BUNDLE)); } @Test public void testOnDataSent() throws Exception { - mUwbSessionNotificationManager.onDataSent(mUwbSession, PEER_UWB_ADDRESS, + mUwbSessionNotificationManager.onDataSent(mUwbSession, PEER_EXTENDED_UWB_ADDRESS, PERSISTABLE_BUNDLE); - verify(mIUwbRangingCallbacks).onDataSent(eq(mSessionHandle), eq(PEER_UWB_ADDRESS), + verify(mIUwbRangingCallbacks).onDataSent(eq(mSessionHandle), eq(PEER_EXTENDED_UWB_ADDRESS), eq(PERSISTABLE_BUNDLE)); } @Test public void testOnDataSendFailed() throws Exception { - mUwbSessionNotificationManager.onDataSendFailed(mUwbSession, PEER_UWB_ADDRESS, + mUwbSessionNotificationManager.onDataSendFailed(mUwbSession, PEER_EXTENDED_UWB_ADDRESS, STATUS_CODE_FAILED, PERSISTABLE_BUNDLE); - verify(mIUwbRangingCallbacks).onDataSendFailed(eq(mSessionHandle), eq(PEER_UWB_ADDRESS), + verify(mIUwbRangingCallbacks).onDataSendFailed(eq(mSessionHandle), eq( + PEER_EXTENDED_UWB_ADDRESS), eq(STATUS_CODE_FAILED), eq(PERSISTABLE_BUNDLE)); } } diff --git a/service/tests/src/com/android/server/uwb/advertisement/UwbAdvertiseManagerTest.java b/service/tests/src/com/android/server/uwb/advertisement/UwbAdvertiseManagerTest.java index e7bbad60..a4bd7b03 100644 --- a/service/tests/src/com/android/server/uwb/advertisement/UwbAdvertiseManagerTest.java +++ b/service/tests/src/com/android/server/uwb/advertisement/UwbAdvertiseManagerTest.java @@ -19,6 +19,7 @@ package com.android.server.uwb.advertisement; import static com.android.server.uwb.advertisement.UwbAdvertiseManager.CRITERIA_ANGLE; import static com.android.server.uwb.advertisement.UwbAdvertiseManager.SIZE_OF_ARRAY_TO_CHECK; import static com.android.server.uwb.advertisement.UwbAdvertiseManager.TRUSTED_VALUE_OF_VARIANCE; +import static com.android.server.uwb.util.DataTypeConversionUtil.macAddressByteArrayToLong; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -53,9 +54,11 @@ import java.nio.ByteBuffer; @Presubmit public class UwbAdvertiseManagerTest { private static final byte[] TEST_MAC_ADDRESS_A = {0x11, 0x13}; - private static final int TEST_MAC_ADDRESS_A_INT = - ByteBuffer.wrap(TEST_MAC_ADDRESS_A).getShort(); + private static final long TEST_MAC_ADDRESS_A_LONG = + macAddressByteArrayToLong(TEST_MAC_ADDRESS_A); private static final byte[] TEST_MAC_ADDRESS_B = {0x15, 0x17}; + private static final int TEST_MAC_ADDRESS_B_INT = + ByteBuffer.wrap(TEST_MAC_ADDRESS_B).getShort(); private static final byte[] TEST_MAC_ADDRESS_C = {0x12, 0x14}; private static final int TEST_STATUS = FiraParams.STATUS_CODE_OK; private static final int TEST_LOS = 3; @@ -153,7 +156,7 @@ public class UwbAdvertiseManagerTest { @Test public void testIsTarget_beforeUpdate() throws Exception { assertFalse(mUwbAdvertiseManager.isPointedTarget(TEST_MAC_ADDRESS_A)); - assertNull(mUwbAdvertiseManager.getAdvertiseTarget(TEST_MAC_ADDRESS_A_INT)); + assertNull(mUwbAdvertiseManager.getAdvertiseTarget(TEST_MAC_ADDRESS_A_LONG)); } // Call isPointedTarget() with a different MacAddress (device B), after a call to @@ -162,7 +165,7 @@ public class UwbAdvertiseManagerTest { public void testIsTarget_differentMacAddress() throws Exception { mUwbAdvertiseManager.updateAdvertiseTarget(UWB_OWR_AOA_MEASUREMENT_DEVICE_A); assertFalse(mUwbAdvertiseManager.isPointedTarget(TEST_MAC_ADDRESS_B)); - assertNotNull(mUwbAdvertiseManager.getAdvertiseTarget(TEST_MAC_ADDRESS_A_INT)); + assertNotNull(mUwbAdvertiseManager.getAdvertiseTarget(TEST_MAC_ADDRESS_A_LONG)); } // Confirm the device is not considered to be a target when there aren't sufficient OWR AoA @@ -279,6 +282,54 @@ public class UwbAdvertiseManagerTest { assertFalse(mUwbAdvertiseManager.isPointedTarget(TEST_MAC_ADDRESS_A)); } + @Test + public void testUpdateAdvertiseTarget_outsideTimeThreshold() throws Exception { + // Setup OwR AoA Measurements such that the device is a pointed target. + UwbOwrAoaMeasurement uwbOwrAoaMeasurement = setupOwrAoaMeasurements(TEST_MAC_ADDRESS_A, + NUM_REQUIRED_OWR_AOA_MEASUREMENTS, + TEST_AOA_AZIMUTH_Q97_FORMAT, TEST_DELTA_AOA_INSIDE_VARIANCE, + TEST_AOA_ELEVATION_Q97_FORMAT, TEST_DELTA_AOA_INSIDE_VARIANCE); + assertTrue(mUwbAdvertiseManager.isPointedTarget(TEST_MAC_ADDRESS_A)); + UwbAdvertiseManager.UwbAdvertiseTarget uwbAdvertiseTarget = + mUwbAdvertiseManager.getAdvertiseTarget(TEST_MAC_ADDRESS_A_LONG); + assertTrue(uwbAdvertiseTarget.isVarianceCalculated()); + + // Fake the current time such that the stored OwR AoA Measurements now seem to be stale, and + // record one more OwR AoA Measurement. + uwbOwrAoaMeasurement.mFrameSequenceNumber++; + when(mUwbInjector.getElapsedSinceBootMillis()).thenReturn( + OWR_AOA_MEASUREMENT_TIME_OUTSIDE_THRESHOLD_MILLIS); + mUwbAdvertiseManager.updateAdvertiseTarget(uwbOwrAoaMeasurement); + + // Check that the variance is not calculated (as a proxy for the number of stored OwR AoA + // measurements for the target, which should now be just 1). + uwbAdvertiseTarget = mUwbAdvertiseManager.getAdvertiseTarget(TEST_MAC_ADDRESS_A_LONG); + assertFalse(uwbAdvertiseTarget.isVarianceCalculated()); + assertFalse(mUwbAdvertiseManager.isPointedTarget(TEST_MAC_ADDRESS_A)); + } + + @Test + public void testRemoveAdvertiseTarget() throws Exception { + // Call updateAdvertiseTarget() with a OwR AoA Measurement and verify that a + // UwbAdvertiseTarget gets created for it (but not for another random device). + UwbOwrAoaMeasurement uwbOwrAoaMeasurement = setupOwrAoaMeasurements(TEST_MAC_ADDRESS_A, + NUM_REQUIRED_OWR_AOA_MEASUREMENTS, + TEST_AOA_AZIMUTH_Q97_FORMAT, TEST_DELTA_AOA_INSIDE_VARIANCE, + TEST_AOA_ELEVATION_Q97_FORMAT, TEST_DELTA_AOA_INSIDE_VARIANCE); + mUwbAdvertiseManager.updateAdvertiseTarget(uwbOwrAoaMeasurement); + + assertNotNull(mUwbAdvertiseManager.getAdvertiseTarget(TEST_MAC_ADDRESS_A_LONG)); + assertNull(mUwbAdvertiseManager.getAdvertiseTarget(TEST_MAC_ADDRESS_B_INT)); + + // Call removeAdvertiseTarget() for the device and verify that it has been removed. + mUwbAdvertiseManager.removeAdvertiseTarget(TEST_MAC_ADDRESS_A); + assertNull(mUwbAdvertiseManager.getAdvertiseTarget(TEST_MAC_ADDRESS_A_LONG)); + + // Call removeAdvertiseTarget() for a device that doesn't exist and verify no exceptions. + mUwbAdvertiseManager.removeAdvertiseTarget(TEST_MAC_ADDRESS_B); + assertNull(mUwbAdvertiseManager.getAdvertiseTarget(TEST_MAC_ADDRESS_B_INT)); + } + private UwbOwrAoaMeasurement setupOwrAoaMeasurements(byte[] macAddress, int numMeasurements, int aoaAzimuth, int aoaAzimuthVariance, int aoaElevation, int aoaElevationVariance) { diff --git a/service/tests/src/com/android/server/uwb/data/UwbRangingDataTest.java b/service/tests/src/com/android/server/uwb/data/UwbRangingDataTest.java index addcf25c..11d71550 100644 --- a/service/tests/src/com/android/server/uwb/data/UwbRangingDataTest.java +++ b/service/tests/src/com/android/server/uwb/data/UwbRangingDataTest.java @@ -16,6 +16,7 @@ package com.android.server.uwb.data; +import static com.android.server.uwb.data.UwbUciConstants.RANGING_MEASUREMENT_TYPE_DL_TDOA; import static com.android.server.uwb.data.UwbUciConstants.RANGING_MEASUREMENT_TYPE_OWR_AOA; import static com.android.server.uwb.data.UwbUciConstants.RANGING_MEASUREMENT_TYPE_TWO_WAY; import static com.android.server.uwb.util.UwbUtil.convertFloatToQFormat; @@ -50,8 +51,11 @@ public class UwbRangingDataTest { private static final int TEST_MAC_ADDRESS_MODE = 1; private static final byte[] TEST_MAC_ADDRESS = {0x1, 0x3}; private static final int TEST_STATUS = FiraParams.STATUS_CODE_OK; + private static final int TEST_MESSAGE_TYPE = 1; + private static final int TEST_MESSAGE_CONTROL = 1331; private static final int TEST_LOS = 0; private static final int TEST_BLOCK_INDEX = 5; + private static final int TEST_ROUND_INDEX = 1; private static final int TEST_FRAME_SEQ_NUMBER = 1; private static final int TEST_DISTANCE = 101; private static final int TEST_AOA_AZIMUTH = 67; @@ -72,6 +76,15 @@ public class UwbRangingDataTest { private static final int TEST_AOA_DEST_ELEVATION_FOM = 90; private static final int TEST_SLOT_IDX = 10; private static final int TEST_RSSI = -1; + private static final long TEST_TIMESTAMP = 500_000L; + private static final int TEST_ANCHOR_CFO = 100; + private static final int TEST_CFO = 200; + private static final long TEST_INTIATOR_REPLY_TIME = 500_000L; + private static final long TEST_RESPONDER_REPLY_TIME = 300_000L; + private static final int TEST_INITIATOR_RESPONDER_TOF = 500; + private static final byte[] TEST_ANCHOR_LOCATION = {0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}; + private static final byte[] TEST_ACTIVE_RANGING_ROUNDS = {0x02, 0x08}; private static final byte[] TEST_RAW_NTF_DATA = {0x10, 0x01}; private UwbRangingData mUwbRangingData; @@ -153,4 +166,48 @@ public class UwbRangingDataTest { assertThat(mUwbRangingData.toString()).isEqualTo(testString); } + + + @Test + public void testInitializeUwbRangingData_withUwbDlTDoAMeasurement() throws Exception { + final int noOfRangingMeasures = 1; + final UwbDlTDoAMeasurement[] uwbDlTDoAMeasurements = + new UwbDlTDoAMeasurement[noOfRangingMeasures]; + uwbDlTDoAMeasurements[0] = new UwbDlTDoAMeasurement(TEST_MAC_ADDRESS, TEST_STATUS, + TEST_MESSAGE_TYPE, TEST_MESSAGE_CONTROL, TEST_BLOCK_INDEX, TEST_ROUND_INDEX, + TEST_LOS, TEST_AOA_AZIMUTH_Q97_FORMAT, TEST_AOA_AZIMUTH_FOM, + TEST_AOA_ELEVATION_Q97_FORMAT, TEST_AOA_ELEVATION_FOM, TEST_RSSI, TEST_TIMESTAMP, + TEST_TIMESTAMP, TEST_ANCHOR_CFO, TEST_CFO, TEST_INTIATOR_REPLY_TIME, + TEST_RESPONDER_REPLY_TIME, TEST_INITIATOR_RESPONDER_TOF, TEST_ANCHOR_LOCATION, + TEST_ACTIVE_RANGING_ROUNDS); + + final int rangingMeasuresType = RANGING_MEASUREMENT_TYPE_DL_TDOA; + mUwbRangingData = new UwbRangingData(TEST_SEQ_COUNTER, TEST_SESSION_ID, + TEST_RCR_INDICATION, TEST_CURR_RANGING_INTERVAL, rangingMeasuresType, + TEST_MAC_ADDRESS_MODE, noOfRangingMeasures, uwbDlTDoAMeasurements, + TEST_RAW_NTF_DATA); + + assertThat(mUwbRangingData.getSequenceCounter()).isEqualTo(TEST_SEQ_COUNTER); + assertThat(mUwbRangingData.getSessionId()).isEqualTo(TEST_SESSION_ID); + assertThat(mUwbRangingData.getRcrIndication()).isEqualTo(TEST_RCR_INDICATION); + assertThat(mUwbRangingData.getCurrRangingInterval()).isEqualTo(TEST_CURR_RANGING_INTERVAL); + assertThat(mUwbRangingData.getRangingMeasuresType()).isEqualTo(rangingMeasuresType); + assertThat(mUwbRangingData.getMacAddressMode()).isEqualTo(TEST_MAC_ADDRESS_MODE); + assertThat(mUwbRangingData.getNoOfRangingMeasures()).isEqualTo(1); + assertThat(mUwbRangingData.getRawNtfData()).isEqualTo(TEST_RAW_NTF_DATA); + + final String testString = "UwbRangingData { " + + " SeqCounter = " + TEST_SEQ_COUNTER + + ", SessionId = " + TEST_SESSION_ID + + ", RcrIndication = " + TEST_RCR_INDICATION + + ", CurrRangingInterval = " + TEST_CURR_RANGING_INTERVAL + + ", RangingMeasuresType = " + rangingMeasuresType + + ", MacAddressMode = " + TEST_MAC_ADDRESS_MODE + + ", NoOfRangingMeasures = " + noOfRangingMeasures + + ", RangingDlTDoAMeasure = " + Arrays.toString(uwbDlTDoAMeasurements) + + ", RawNotificationData = " + Arrays.toString(TEST_RAW_NTF_DATA) + + '}'; + + assertThat(mUwbRangingData.toString()).isEqualTo(testString); + } } diff --git a/service/tests/src/com/android/server/uwb/params/CccEncoderTest.java b/service/tests/src/com/android/server/uwb/params/CccEncoderTest.java index fcd2aa90..766920ed 100644 --- a/service/tests/src/com/android/server/uwb/params/CccEncoderTest.java +++ b/service/tests/src/com/android/server/uwb/params/CccEncoderTest.java @@ -65,8 +65,8 @@ public class CccEncoderTest { .setHoppingSequence(HOPPING_SEQUENCE_DEFAULT); private static final byte[] TEST_CCC_OPEN_RANGING_TLV_DATA = - UwbUtil.getByteArray("0001000201010401090501010904800100000E010011010103010" - + "11B01062301012C0100A3020100A4020000A50100A602D0020802B004140101"); + UwbUtil.getByteArray("0001010201010401090501010904800100000E010011010103010" + + "11B01062301012C0100A3020001A4020000A50100A602D0020802B004140101"); private final CccEncoder mCccEncoder = new CccEncoder(); diff --git a/service/tests/src/com/android/server/uwb/params/FiraDecoderTest.java b/service/tests/src/com/android/server/uwb/params/FiraDecoderTest.java index d63172f5..67c1bf89 100644 --- a/service/tests/src/com/android/server/uwb/params/FiraDecoderTest.java +++ b/service/tests/src/com/android/server/uwb/params/FiraDecoderTest.java @@ -92,10 +92,11 @@ public class FiraDecoderTest { + "E40401010101" + "E50403000000" + "E601FF" - + "E70101"; + + "E70101" + + "E80401010101"; private static final byte[] TEST_FIRA_SPECIFICATION_TLV_DATA = UwbUtil.getByteArray(TEST_FIRA_SPECIFICATION_TLV_STRING); - public static final int TEST_FIRA_SPECIFICATION_TLV_NUM_PARAMS = 23; + public static final int TEST_FIRA_SPECIFICATION_TLV_NUM_PARAMS = 24; private final FiraDecoder mFiraDecoder = new FiraDecoder(); public static void verifyFiraSpecification(FiraSpecificationParams firaSpecificationParams) { diff --git a/service/tests/src/com/android/server/uwb/util/DataTypeConversionUtilTest.java b/service/tests/src/com/android/server/uwb/util/DataTypeConversionUtilTest.java index e14cc968..fba1cc73 100644 --- a/service/tests/src/com/android/server/uwb/util/DataTypeConversionUtilTest.java +++ b/service/tests/src/com/android/server/uwb/util/DataTypeConversionUtilTest.java @@ -168,4 +168,54 @@ public class DataTypeConversionUtilTest { new byte[0])); } + + @Test + public void macAddressByteArrayToLong_success_shortMacAddress_LittleEndian() { + assertThat( + DataTypeConversionUtil.macAddressByteArrayToLong(new byte[] {0x35, 0x37})) + .isEqualTo(0x3735); + } + + @Test + public void macAddressByteArrayToLong_success_int_LittleEndian() { + assertThat( + DataTypeConversionUtil.macAddressByteArrayToLong( + new byte[] {0x35, 0x37, 0x38, 0x48})) + .isEqualTo(0x48383735); + } + + @Test + public void macAddressByteArrayToLong_success_extendedMacAddress_LittleEndian() { + assertThat( + DataTypeConversionUtil.macAddressByteArrayToLong( + new byte[] {0x35, 0x37, 0x38, 0x48, 0x22, 0x24, 0x26, 0x28})) + .isEqualTo(0x2826242248383735L); + } + + @Test + public void macAddressByteArrayToLong_success_shortInExtendedMacAddressFormat_LittleEndian() { + assertThat( + DataTypeConversionUtil.macAddressByteArrayToLong( + new byte[] {0x35, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})) + .isEqualTo(0x3735); + } + + @Test + public void macAddressByteArrayToLong_badLength() { + // Unsupported lengths. + assertThrows( + NumberFormatException.class, + () -> DataTypeConversionUtil.macAddressByteArrayToLong(new byte[]{0x01})); + assertThrows( + NumberFormatException.class, + () -> DataTypeConversionUtil.macAddressByteArrayToLong( + new byte[]{0x01, 0x02, 0x03})); + + // Too long + assertThrows( + NumberFormatException.class, + () -> DataTypeConversionUtil.macAddressByteArrayToLong( + new byte[]{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09})); + + } } diff --git a/service/uci/jni/Android.bp b/service/uci/jni/Android.bp index 7d3c65ba..c5402e27 100755..100644 --- a/service/uci/jni/Android.bp +++ b/service/uci/jni/Android.bp @@ -8,15 +8,18 @@ rust_defaults { lints: "android", clippy_lints: "android", min_sdk_version: "Tiramisu", - srcs: ["rust/lib.rs"], + srcs: ["src/lib.rs"], rustlibs: [ - "libjni", "libbinder_rs", + "libjni", "liblog_rust", "liblogger", "libnum_traits", + "libthiserror", + "libtokio", + "libuci_hal_android", + "libuwb_core", "libuwb_uci_packets", - "libuwb_uci_rust", ], prefer_rlib: true, apex_available: [ diff --git a/service/uci/jni/rust/lib.rs b/service/uci/jni/rust/lib.rs deleted file mode 100644 index 0287e35b..00000000 --- a/service/uci/jni/rust/lib.rs +++ /dev/null @@ -1,1563 +0,0 @@ -//! jni for uwb native stack -use jni::objects::{JObject, JString, JValue}; -use jni::sys::{ - jarray, jboolean, jbyte, jbyteArray, jint, jintArray, jlong, jobject, jobjectArray, jshort, - jshortArray, jsize, -}; -use jni::JNIEnv; -use log::{error, info}; -use num_traits::ToPrimitive; -use uwb_uci_packets::{ - GetCapsInfoRspPacket, Packet, SessionGetAppConfigRspPacket, SessionSetAppConfigRspPacket, - StatusCode, UciResponseChild, UciResponsePacket, UciVendor_9_ResponseChild, - UciVendor_A_ResponseChild, UciVendor_B_ResponseChild, UciVendor_E_ResponseChild, - UciVendor_F_ResponseChild, -}; -use uwb_uci_rust::error::UwbErr; -use uwb_uci_rust::event_manager::EventManagerImpl as EventManager; -use uwb_uci_rust::uci::{uci_hrcv::UciResponse, Dispatcher, DispatcherImpl, JNICommand}; - -trait Context<'a> { - fn convert_byte_array(&self, array: jbyteArray) -> Result<Vec<u8>, jni::errors::Error>; - fn get_array_length(&self, array: jarray) -> Result<jsize, jni::errors::Error>; - fn get_short_array_region( - &self, - array: jshortArray, - start: jsize, - buf: &mut [jshort], - ) -> Result<(), jni::errors::Error>; - fn get_int_array_region( - &self, - array: jintArray, - start: jsize, - buf: &mut [jint], - ) -> Result<(), jni::errors::Error>; - fn is_same_object(&self, obj1: JObject, obj2: JObject) -> Result<bool, jni::errors::Error>; - fn get_dispatcher(&self) -> Result<&'a mut dyn Dispatcher, UwbErr>; -} - -struct JniContext<'a> { - env: JNIEnv<'a>, - obj: JObject<'a>, -} - -impl<'a> JniContext<'a> { - fn new(env: JNIEnv<'a>, obj: JObject<'a>) -> Self { - Self { env, obj } - } -} - -struct ControleeData { - addresses: jshortArray, - sub_session_ids: jintArray, - message_control: jint, - sub_session_keys: jbyteArray, -} - -impl<'a> Context<'a> for JniContext<'a> { - fn convert_byte_array(&self, array: jbyteArray) -> Result<Vec<u8>, jni::errors::Error> { - self.env.convert_byte_array(array) - } - fn get_array_length(&self, array: jarray) -> Result<jsize, jni::errors::Error> { - self.env.get_array_length(array) - } - fn get_short_array_region( - &self, - array: jshortArray, - start: jsize, - buf: &mut [jshort], - ) -> Result<(), jni::errors::Error> { - self.env.get_short_array_region(array, start, buf) - } - fn get_int_array_region( - &self, - array: jintArray, - start: jsize, - buf: &mut [jint], - ) -> Result<(), jni::errors::Error> { - self.env.get_int_array_region(array, start, buf) - } - fn is_same_object(&self, obj1: JObject, obj2: JObject) -> Result<bool, jni::errors::Error> { - self.env.is_same_object(obj1, obj2) - } - fn get_dispatcher(&self) -> Result<&'a mut dyn Dispatcher, UwbErr> { - let dispatcher_ptr_value = self.env.get_field(self.obj, "mDispatcherPointer", "J")?; - let dispatcher_ptr = dispatcher_ptr_value.j()?; - if dispatcher_ptr == 0i64 { - error!("The dispatcher is not initialized."); - return Err(UwbErr::NoneDispatcher); - } - // Safety: dispatcher pointer must not be a null pointer and it must point to a valid - // dispatcher object. This can be ensured because the dispatcher is created in an earlier - // stage and won't be deleted before calling doDeinitialize. - unsafe { Ok(&mut *(dispatcher_ptr as *mut DispatcherImpl)) } - } -} - -/// Initialize UWB -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeInit( - _env: JNIEnv, - _obj: JObject, -) -> jboolean { - logger::init( - logger::Config::default() - .with_tag_on_device("uwb") - .with_min_level(log::Level::Trace) - .with_filter("trace,jni=info"), - ); - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeInit: enter"); - true as jboolean -} - -/// Get max session number -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeGetMaxSessionNumber( - _env: JNIEnv, - _obj: JObject, -) -> jint { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeGetMaxSessionNumber: enter"); - 5 -} - -/// Turn on UWB. initialize the GKI module and HAL module for UWB device. -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeDoInitialize( - env: JNIEnv, - obj: JObject, - chip_id: JString, -) -> jboolean { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeDoInitialize: enter"); - boolean_result_helper( - do_initialize(&JniContext::new(env, obj), env.get_string(chip_id).unwrap().into()), - "DoInitialize", - ) -} - -/// Turn off UWB. Deinitilize the GKI and HAL module, power of the UWB device. -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeDoDeinitialize( - env: JNIEnv, - obj: JObject, - chip_id: JString, -) -> jboolean { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeDoDeinitialize: enter"); - boolean_result_helper( - do_deinitialize(&JniContext::new(env, obj), env.get_string(chip_id).unwrap().into()), - "DoDeinitialize", - ) -} - -/// get nanos -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeGetTimestampResolutionNanos( - _env: JNIEnv, - _obj: JObject, -) -> jlong { - info!( - "Java_com_android_server_uwb_jni_NativeUwbManager_nativeGetTimestampResolutionNanos: enter" - ); - 0 -} - -/// reset the device -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeDeviceReset( - env: JNIEnv, - obj: JObject, - reset_config: jbyte, - chip_id: JString, -) -> jbyte { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeDeviceReset: enter"); - byte_result_helper( - reset_device( - &JniContext::new(env, obj), - reset_config as u8, - env.get_string(chip_id).unwrap().into(), - ), - "ResetDevice", - ) -} - -/// init the session -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeSessionInit( - env: JNIEnv, - obj: JObject, - session_id: jint, - session_type: jbyte, - chip_id: JString, -) -> jbyte { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeSessionInit: enter"); - byte_result_helper( - session_init( - &JniContext::new(env, obj), - session_id as u32, - session_type as u8, - env.get_string(chip_id).unwrap().into(), - ), - "SessionInit", - ) -} - -/// deinit the session -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeSessionDeInit( - env: JNIEnv, - obj: JObject, - session_id: jint, - chip_id: JString, -) -> jbyte { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeSessionDeInit: enter"); - byte_result_helper( - session_deinit( - &JniContext::new(env, obj), - session_id as u32, - env.get_string(chip_id).unwrap().into(), - ), - "SessionDeInit", - ) -} - -/// get session count -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeGetSessionCount( - env: JNIEnv, - obj: JObject, - chip_id: JString, -) -> jbyte { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeGetSessionCount: enter"); - match get_session_count(&JniContext::new(env, obj), env.get_string(chip_id).unwrap().into()) { - Ok(count) => count, - Err(e) => { - error!("GetSessionCount failed with {:?}", e); - -1 - } - } -} - -/// start the ranging -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeRangingStart( - env: JNIEnv, - obj: JObject, - session_id: jint, - chip_id: JString, -) -> jbyte { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeRangingStart: enter"); - byte_result_helper( - ranging_start( - &JniContext::new(env, obj), - session_id as u32, - env.get_string(chip_id).unwrap().into(), - ), - "RangingStart", - ) -} - -/// stop the ranging -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeRangingStop( - env: JNIEnv, - obj: JObject, - session_id: jint, - chip_id: JString, -) -> jbyte { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeRangingStop: enter"); - byte_result_helper( - ranging_stop( - &JniContext::new(env, obj), - session_id as u32, - env.get_string(chip_id).unwrap().into(), - ), - "RangingStop", - ) -} - -/// get the session state -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeGetSessionState( - env: JNIEnv, - obj: JObject, - session_id: jint, - chip_id: JString, -) -> jbyte { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeGetSessionState: enter"); - match get_session_state( - &JniContext::new(env, obj), - session_id as u32, - env.get_string(chip_id).unwrap().into(), - ) { - Ok(state) => state, - Err(e) => { - error!("GetSessionState failed with {:?}", e); - -1 - } - } -} - -/// set app configurations -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeSetAppConfigurations( - env: JNIEnv, - obj: JObject, - session_id: jint, - no_of_params: jint, - app_config_param_len: jint, - app_config_params: jbyteArray, - chip_id: JString, -) -> jbyteArray { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeSetAppConfigurations: enter"); - match set_app_configurations( - &JniContext::new(env, obj), - session_id as u32, - no_of_params as u32, - app_config_param_len as u32, - app_config_params, - env.get_string(chip_id).unwrap().into(), - ) { - Ok(data) => { - let uwb_config_status_class = - env.find_class("com/android/server/uwb/data/UwbConfigStatusData").unwrap(); - let mut buf: Vec<u8> = Vec::new(); - for iter in data.get_cfg_status() { - buf.push(iter.cfg_id as u8); - buf.push(iter.status as u8); - } - let cfg_jbytearray = env.byte_array_from_slice(&buf).unwrap(); - let uwb_config_status_object = env.new_object( - uwb_config_status_class, - "(II[B)V", - &[ - JValue::Int(data.get_status().to_i32().unwrap()), - JValue::Int(data.get_cfg_status().len().to_i32().unwrap()), - JValue::Object(JObject::from(cfg_jbytearray)), - ], - ); - *uwb_config_status_object.unwrap() - } - Err(e) => { - error!("SetAppConfig failed with: {:?}", e); - *JObject::null() - } - } -} - -/// get app configurations -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeGetAppConfigurations( - env: JNIEnv, - obj: JObject, - session_id: jint, - no_of_params: jint, - app_config_param_len: jint, - app_config_params: jbyteArray, - chip_id: JString, -) -> jbyteArray { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeGetAppConfigurations: enter"); - match get_app_configurations( - &JniContext::new(env, obj), - session_id as u32, - no_of_params as u32, - app_config_param_len as u32, - app_config_params, - env.get_string(chip_id).unwrap().into(), - ) { - Ok(data) => { - let uwb_tlv_info_class = - env.find_class("com/android/server/uwb/data/UwbTlvData").unwrap(); - let mut buf: Vec<u8> = Vec::new(); - for tlv in data.get_tlvs() { - buf.push(tlv.cfg_id as u8); - buf.push(tlv.v.len() as u8); - buf.extend(&tlv.v); - } - let tlv_jbytearray = env.byte_array_from_slice(&buf).unwrap(); - let uwb_tlv_info_object = env.new_object( - uwb_tlv_info_class, - "(II[B)V", - &[ - JValue::Int(data.get_status().to_i32().unwrap()), - JValue::Int(data.get_tlvs().len().to_i32().unwrap()), - JValue::Object(JObject::from(tlv_jbytearray)), - ], - ); - *uwb_tlv_info_object.unwrap() - } - Err(e) => { - error!("GetAppConfig failed with: {:?}", e); - *JObject::null() - } - } -} - -/// get capability info -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeGetCapsInfo( - env: JNIEnv, - obj: JObject, - chip_id: JString, -) -> jbyteArray { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeGetCapsInfo: enter"); - match get_caps_info(&JniContext::new(env, obj), env.get_string(chip_id).unwrap().into()) { - Ok(data) => { - let uwb_tlv_info_class = - env.find_class("com/android/server/uwb/data/UwbTlvData").unwrap(); - let mut buf: Vec<u8> = Vec::new(); - for tlv in data.get_tlvs() { - buf.push(tlv.t as u8); - buf.push(tlv.v.len() as u8); - buf.extend(&tlv.v); - } - let tlv_jbytearray = env.byte_array_from_slice(&buf).unwrap(); - let uwb_tlv_info_object = env.new_object( - uwb_tlv_info_class, - "(II[B)V", - &[ - JValue::Int(data.get_status().to_i32().unwrap()), - JValue::Int(data.get_tlvs().len().to_i32().unwrap()), - JValue::Object(JObject::from(tlv_jbytearray)), - ], - ); - *uwb_tlv_info_object.unwrap() - } - Err(e) => { - error!("GetCapsInfo failed with: {:?}", e); - *JObject::null() - } - } -} - -/// update multicast list by SESSION_UPDATE_CONTROLLER_MULTICAST_LIST_CMD -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeControllerMulticastListUpdateV1( - env: JNIEnv, - obj: JObject, - session_id: jint, - action: jbyte, - no_of_controlee: jbyte, - addresses: jshortArray, - sub_session_ids: jintArray, - chip_id: JString, -) -> jbyte { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeControllerMulticastListUpdateV1: enter"); - let controlee_data = ControleeData { - addresses, - sub_session_ids, - message_control: -1, - sub_session_keys: *JObject::null(), - }; - byte_result_helper( - multicast_list_update( - &JniContext::new(env, obj), - session_id as u32, - action as u8, - no_of_controlee as u8, - controlee_data, - env.get_string(chip_id).unwrap().into(), - ), - "ControllerMulticastListUpdate", - ) -} - -/// update multicast list by SESSION_UPDATE_CONTROLLER_MULTICAST_LIST_V2_CMD -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeControllerMulticastListUpdateV2( - env: JNIEnv, - obj: JObject, - session_id: jint, - action: jbyte, - no_of_controlee: jbyte, - addresses: jshortArray, - sub_session_ids: jintArray, - message_control: jint, - sub_session_keys: jbyteArray, - chip_id: JString, -) -> jbyte { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeControllerMulticastListUpdateV2: enter"); - let controlee_data = - ControleeData { addresses, sub_session_ids, message_control, sub_session_keys }; - byte_result_helper( - multicast_list_update( - &JniContext::new(env, obj), - session_id as u32, - action as u8, - no_of_controlee as u8, - controlee_data, - env.get_string(chip_id).unwrap().into(), - ), - "ControllerMulticastListUpdate", - ) -} - -/// set country code -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeSetCountryCode( - env: JNIEnv, - obj: JObject, - country_code: jbyteArray, - chip_id: JString, -) -> jbyte { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeSetCountryCode: enter"); - byte_result_helper( - set_country_code( - &JniContext::new(env, obj), - country_code, - env.get_string(chip_id).unwrap().into(), - ), - "SetCountryCode", - ) -} - -/// Set log mode for new stack. -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeSetLogMode( - _env: JNIEnv, - _obj: JObject, - _log_mode_jstring: JString, // Ignored as existing stack sets log mode differently. -) -> jboolean { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeSetLogMode: enter"); - false as jboolean -} - -/// set country code -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeSendRawVendorCmd( - env: JNIEnv, - obj: JObject, - gid: jint, - oid: jint, - payload: jbyteArray, - chip_id: JString, -) -> jobject { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeRawVendor: enter"); - let uwb_vendor_uci_response_class = - env.find_class("com/android/server/uwb/data/UwbVendorUciResponse").unwrap(); - match send_raw_vendor_cmd( - &JniContext::new(env, obj), - gid.try_into().expect("invalid gid"), - oid.try_into().expect("invalid oid"), - payload, - env.get_string(chip_id).unwrap().into(), - ) { - Ok((gid, oid, payload)) => *env - .new_object( - uwb_vendor_uci_response_class, - "(BII[B)V", - &[ - JValue::Byte(StatusCode::UciStatusOk.to_i8().unwrap()), - JValue::Int(gid.to_i32().unwrap()), - JValue::Int(oid.to_i32().unwrap()), - JValue::Object(JObject::from( - env.byte_array_from_slice(payload.as_ref()).unwrap(), - )), - ], - ) - .unwrap(), - Err(e) => { - error!("send raw uci cmd failed with: {:?}", e); - *env.new_object( - uwb_vendor_uci_response_class, - "(BII[B)V", - &[ - JValue::Byte(StatusCode::UciStatusFailed.to_i8().unwrap()), - JValue::Int(-1), - JValue::Int(-1), - JValue::Object(JObject::null()), - ], - ) - .unwrap() - } - } -} - -/// retrieve the UWB power stats -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeGetPowerStats( - env: JNIEnv, - obj: JObject, - chip_id: JString, -) -> jobject { - info!("Java_com_android_server_uwb_jni_NativeUwbManager_nativeGetPowerStats: enter"); - let uwb_power_stats_class = - env.find_class("com/android/server/uwb/info/UwbPowerStats").unwrap(); - match get_power_stats(&JniContext::new(env, obj), env.get_string(chip_id).unwrap().into()) { - Ok(para) => { - let power_stats = env.new_object(uwb_power_stats_class, "(IIII)V", ¶).unwrap(); - *power_stats - } - Err(e) => { - error!("Get power stats failed with: {:?}", e); - *JObject::null() - } - } -} - -fn boolean_result_helper(result: Result<(), UwbErr>, function_name: &str) -> jboolean { - match result { - Ok(()) => true as jboolean, - Err(err) => { - error!("{} failed with: {:?}", function_name, err); - false as jboolean - } - } -} - -fn byte_result_helper(result: Result<(), UwbErr>, function_name: &str) -> jbyte { - match result { - Ok(()) => StatusCode::UciStatusOk.to_i8().unwrap(), - Err(err) => { - error!("{} failed with: {:?}", function_name, err); - match err { - UwbErr::StatusCode(status_code) => status_code - .to_i8() - .unwrap_or_else(|| StatusCode::UciStatusFailed.to_i8().unwrap()), - _ => StatusCode::UciStatusFailed.to_i8().unwrap(), - } - } - } -} - -fn do_initialize<'a, T: Context<'a>>(context: &T, chip_id: String) -> Result<(), UwbErr> { - let dispatcher = context.get_dispatcher()?; - match dispatcher.block_on_jni_command(JNICommand::Enable, chip_id.clone())? { - UciResponse::EnableRsp(enable) => { - if !enable { - error!("Enable UWB failed."); - return Err(UwbErr::failed()); - } - } - _ => { - error!("Received wrong response!"); - return Err(UwbErr::failed()); - } - } - match uwa_get_device_info(dispatcher, chip_id) { - Ok(res) => { - if let UciResponse::GetDeviceInfoRsp(device_info) = res { - dispatcher.set_device_info(Some(device_info)); - } - } - Err(e) => { - error!("GetDeviceInfo failed with: {:?}", e); - return Err(UwbErr::failed()); - } - } - Ok(()) -} - -fn do_deinitialize<'a, T: Context<'a>>(context: &T, chip_id: String) -> Result<(), UwbErr> { - let dispatcher = context.get_dispatcher()?; - dispatcher.send_jni_command(JNICommand::Disable(true), chip_id)?; - dispatcher.wait_for_exit()?; - Ok(()) -} - -// unused, but leaving this behind if we want to use it later. -#[allow(dead_code)] -fn get_specification_info<'a, T: Context<'a>>( - context: &T, - _chip_id: String, -) -> Result<[JValue<'a>; 16], UwbErr> { - let dispatcher = context.get_dispatcher()?; - match dispatcher.get_device_info() { - Some(data) => { - Ok([ - JValue::Int((data.get_uci_version() & 0xFF).into()), - JValue::Int(((data.get_uci_version() >> 8) & 0xF).into()), - JValue::Int(((data.get_uci_version() >> 12) & 0xF).into()), - JValue::Int((data.get_mac_version() & 0xFF).into()), - JValue::Int(((data.get_mac_version() >> 8) & 0xF).into()), - JValue::Int(((data.get_mac_version() >> 12) & 0xF).into()), - JValue::Int((data.get_phy_version() & 0xFF).into()), - JValue::Int(((data.get_phy_version() >> 8) & 0xF).into()), - JValue::Int(((data.get_phy_version() >> 12) & 0xF).into()), - JValue::Int((data.get_uci_test_version() & 0xFF).into()), - JValue::Int(((data.get_uci_test_version() >> 8) & 0xF).into()), - JValue::Int(((data.get_uci_test_version() >> 12) & 0xF).into()), - JValue::Int(1), // fira_major_version - JValue::Int(0), // fira_minor_version - JValue::Int(1), // ccc_major_version - JValue::Int(0), // ccc_minor_version - ]) - } - None => { - error!("Fail to get specification info."); - Err(UwbErr::failed()) - } - } -} - -fn session_init<'a, T: Context<'a>>( - context: &T, - session_id: u32, - session_type: u8, - chip_id: String, -) -> Result<(), UwbErr> { - let dispatcher = context.get_dispatcher()?; - let res = match dispatcher - .block_on_jni_command(JNICommand::UciSessionInit(session_id, session_type), chip_id)? - { - UciResponse::SessionInitRsp(data) => data, - _ => return Err(UwbErr::failed()), - }; - status_code_to_res(res.get_status()) -} - -fn session_deinit<'a, T: Context<'a>>( - context: &T, - session_id: u32, - chip_id: String, -) -> Result<(), UwbErr> { - let dispatcher = context.get_dispatcher()?; - let res = - match dispatcher.block_on_jni_command(JNICommand::UciSessionDeinit(session_id), chip_id)? { - UciResponse::SessionDeinitRsp(data) => data, - _ => return Err(UwbErr::failed()), - }; - status_code_to_res(res.get_status()) -} - -fn get_session_count<'a, T: Context<'a>>(context: &T, chip_id: String) -> Result<jbyte, UwbErr> { - let dispatcher = context.get_dispatcher()?; - match dispatcher.block_on_jni_command(JNICommand::UciSessionGetCount, chip_id)? { - UciResponse::SessionGetCountRsp(rsp) => match status_code_to_res(rsp.get_status()) { - Ok(()) => Ok(rsp.get_session_count() as jbyte), - Err(err) => Err(err), - }, - _ => Err(UwbErr::failed()), - } -} - -fn ranging_start<'a, T: Context<'a>>( - context: &T, - session_id: u32, - chip_id: String, -) -> Result<(), UwbErr> { - let dispatcher = context.get_dispatcher()?; - let res = - match dispatcher.block_on_jni_command(JNICommand::UciStartRange(session_id), chip_id)? { - UciResponse::RangeStartRsp(data) => data, - _ => return Err(UwbErr::failed()), - }; - status_code_to_res(res.get_status()) -} - -fn ranging_stop<'a, T: Context<'a>>( - context: &T, - session_id: u32, - chip_id: String, -) -> Result<(), UwbErr> { - let dispatcher = context.get_dispatcher()?; - let res = - match dispatcher.block_on_jni_command(JNICommand::UciStopRange(session_id), chip_id)? { - UciResponse::RangeStopRsp(data) => data, - _ => return Err(UwbErr::failed()), - }; - status_code_to_res(res.get_status()) -} - -fn get_session_state<'a, T: Context<'a>>( - context: &T, - session_id: u32, - chip_id: String, -) -> Result<jbyte, UwbErr> { - let dispatcher = context.get_dispatcher()?; - match dispatcher.block_on_jni_command(JNICommand::UciGetSessionState(session_id), chip_id)? { - UciResponse::SessionGetStateRsp(data) => Ok(data.get_session_state() as jbyte), - _ => Err(UwbErr::failed()), - } -} - -fn set_app_configurations<'a, T: Context<'a>>( - context: &T, - session_id: u32, - no_of_params: u32, - app_config_param_len: u32, - app_config_params: jintArray, - chip_id: String, -) -> Result<SessionSetAppConfigRspPacket, UwbErr> { - let app_configs = context.convert_byte_array(app_config_params)?; - let dispatcher = context.get_dispatcher()?; - match dispatcher.block_on_jni_command( - JNICommand::UciSetAppConfig { session_id, no_of_params, app_config_param_len, app_configs }, - chip_id, - )? { - UciResponse::SessionSetAppConfigRsp(data) => Ok(data), - _ => Err(UwbErr::failed()), - } -} - -fn get_app_configurations<'a, T: Context<'a>>( - context: &T, - session_id: u32, - no_of_params: u32, - app_config_param_len: u32, - app_config_params: jintArray, - chip_id: String, -) -> Result<SessionGetAppConfigRspPacket, UwbErr> { - let app_configs = context.convert_byte_array(app_config_params)?; - let dispatcher = context.get_dispatcher()?; - match dispatcher.block_on_jni_command( - JNICommand::UciGetAppConfig { session_id, no_of_params, app_config_param_len, app_configs }, - chip_id, - )? { - UciResponse::SessionGetAppConfigRsp(data) => Ok(data), - _ => Err(UwbErr::failed()), - } -} - -fn get_caps_info<'a, T: Context<'a>>( - context: &T, - chip_id: String, -) -> Result<GetCapsInfoRspPacket, UwbErr> { - let dispatcher = context.get_dispatcher()?; - match dispatcher.block_on_jni_command(JNICommand::UciGetCapsInfo, chip_id)? { - UciResponse::GetCapsInfoRsp(data) => Ok(data), - _ => Err(UwbErr::failed()), - } -} - -fn multicast_list_update<'a, T: Context<'a>>( - context: &T, - session_id: u32, - action: u8, - no_of_controlee: u8, - controlee_data: ControleeData, - chip_id: String, -) -> Result<(), UwbErr> { - let mut address_list = vec![0i16; no_of_controlee as usize]; - context.get_short_array_region(controlee_data.addresses, 0, &mut address_list)?; - let mut sub_session_id_list = vec![0i32; no_of_controlee as usize]; - context.get_int_array_region(controlee_data.sub_session_ids, 0, &mut sub_session_id_list)?; - - let sub_session_key_list = match context - .is_same_object(controlee_data.sub_session_keys.into(), JObject::null())? - { - true => vec![0i32; no_of_controlee as usize], - false => { - let mut keys = - vec![ - 0i32; - context.get_array_length(controlee_data.sub_session_keys)?.try_into().unwrap() - ]; - context.get_int_array_region(controlee_data.sub_session_keys, 0, &mut keys)?; - keys - } - }; - - let dispatcher = context.get_dispatcher()?; - let res = match dispatcher.block_on_jni_command( - JNICommand::UciSessionUpdateMulticastList { - session_id, - action, - no_of_controlee, - address_list: address_list.to_vec(), - sub_session_id_list: sub_session_id_list.to_vec(), - message_control: controlee_data.message_control, - sub_session_key_list: split_sub_session_keys( - sub_session_key_list, - controlee_data.message_control, - )?, - }, - chip_id, - )? { - UciResponse::SessionUpdateControllerMulticastListRsp(data) => data, - _ => return Err(UwbErr::failed()), - }; - status_code_to_res(res.get_status()) -} - -fn split_sub_session_keys( - sub_session_key_list: Vec<i32>, - message_control: i32, -) -> Result<Vec<Vec<u8>>, UwbErr> { - if (message_control >> 3) & 1 == 0 { - Ok(Vec::new()) - } else { - match message_control & 1 { - 0 => sub_session_key_builder(sub_session_key_list, 4), - 1 => sub_session_key_builder(sub_session_key_list, 8), - _ => Err(UwbErr::InvalidArgs), - } - } -} - -fn sub_session_key_builder( - sub_session_key_list: Vec<i32>, - size: usize, -) -> Result<Vec<Vec<u8>>, UwbErr> { - let mut res = Vec::new(); - for chunk in sub_session_key_list.chunks(size) { - let mut key_in_byte = Vec::new(); - for key in chunk.iter() { - key_in_byte.extend_from_slice(&key.to_be_bytes()); - } - res.push(key_in_byte); - } - Ok(res) -} - -fn set_country_code<'a, T: Context<'a>>( - context: &T, - country_code: jbyteArray, - chip_id: String, -) -> Result<(), UwbErr> { - let code = context.convert_byte_array(country_code)?; - if code.len() != 2 { - return Err(UwbErr::failed()); - } - let dispatcher = context.get_dispatcher()?; - let res = - match dispatcher.block_on_jni_command(JNICommand::UciSetCountryCode { code }, chip_id)? { - UciResponse::AndroidSetCountryCodeRsp(data) => data, - _ => return Err(UwbErr::failed()), - }; - status_code_to_res(res.get_status()) -} - -fn get_vendor_uci_payload(data: UciResponsePacket) -> Result<Vec<u8>, UwbErr> { - match data.specialize() { - UciResponseChild::UciVendor_9_Response(evt) => match evt.specialize() { - UciVendor_9_ResponseChild::Payload(payload) => Ok(payload.to_vec()), - UciVendor_9_ResponseChild::None => Ok(Vec::new()), - }, - UciResponseChild::UciVendor_A_Response(evt) => match evt.specialize() { - UciVendor_A_ResponseChild::Payload(payload) => Ok(payload.to_vec()), - UciVendor_A_ResponseChild::None => Ok(Vec::new()), - }, - UciResponseChild::UciVendor_B_Response(evt) => match evt.specialize() { - UciVendor_B_ResponseChild::Payload(payload) => Ok(payload.to_vec()), - UciVendor_B_ResponseChild::None => Ok(Vec::new()), - }, - UciResponseChild::UciVendor_E_Response(evt) => match evt.specialize() { - UciVendor_E_ResponseChild::Payload(payload) => Ok(payload.to_vec()), - UciVendor_E_ResponseChild::None => Ok(Vec::new()), - }, - UciResponseChild::UciVendor_F_Response(evt) => match evt.specialize() { - UciVendor_F_ResponseChild::Payload(payload) => Ok(payload.to_vec()), - UciVendor_F_ResponseChild::None => Ok(Vec::new()), - }, - _ => { - error!("Invalid vendor response with gid {:?}", data.get_group_id()); - Err(UwbErr::Specialize(data.to_vec())) - } - } -} - -fn send_raw_vendor_cmd<'a, T: Context<'a>>( - context: &T, - gid: u32, - oid: u32, - payload: jbyteArray, - chip_id: String, -) -> Result<(i32, i32, Vec<u8>), UwbErr> { - let payload = context.convert_byte_array(payload)?; - let dispatcher = context.get_dispatcher()?; - match dispatcher - .block_on_jni_command(JNICommand::UciRawVendorCmd { gid, oid, payload }, chip_id)? - { - UciResponse::RawVendorRsp(response) => Ok(( - response.get_group_id().to_i32().unwrap(), - response.get_opcode().to_i32().unwrap(), - get_vendor_uci_payload(response)?, - )), - _ => Err(UwbErr::failed()), - } -} - -fn status_code_to_res(status_code: StatusCode) -> Result<(), UwbErr> { - match status_code { - StatusCode::UciStatusOk => Ok(()), - _ => Err(UwbErr::StatusCode(status_code)), - } -} - -/// create a dispatcher instance -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeDispatcherNew( - env: JNIEnv, - obj: JObject, - chip_ids: jobjectArray, -) -> jlong { - let eventmanager = match EventManager::new(env, obj) { - Ok(evtmgr) => evtmgr, - Err(err) => { - error!("Fail to create event manager{:?}", err); - return *JObject::null() as jlong; - } - }; - - let mut chip_ids_vec = Vec::new(); - for n in 0..env.get_array_length(chip_ids).unwrap() { - let chip_id = env - .get_string(env.get_object_array_element(chip_ids, n).unwrap().into()) - .unwrap() - .into(); - chip_ids_vec.push(chip_id); - } - match DispatcherImpl::new(eventmanager, chip_ids_vec) { - Ok(dispatcher) => Box::into_raw(Box::new(dispatcher)) as jlong, - Err(err) => { - error!("Fail to create dispatcher {:?}", err); - *JObject::null() as jlong - } - } -} - -/// destroy the dispatcher instance -#[no_mangle] -pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeDispatcherDestroy( - env: JNIEnv, - obj: JObject, -) { - let dispatcher_ptr_value = match env.get_field(obj, "mDispatcherPointer", "J") { - Ok(value) => value, - Err(err) => { - error!("Failed to get the pointer with: {:?}", err); - return; - } - }; - let dispatcher_ptr = match dispatcher_ptr_value.j() { - Ok(value) => value, - Err(err) => { - error!("Failed to get the pointer with: {:?}", err); - return; - } - }; - // Safety: dispatcher pointer must not be a null pointer and must point to a valid dispatcher object. - // This can be ensured because the dispatcher is created in an earlier stage and - // won't be deleted before calling this destroy function. - // This function will early return if the instance is already destroyed. - let _boxed_dispatcher = unsafe { Box::from_raw(dispatcher_ptr as *mut DispatcherImpl) }; - info!("The dispatcher successfully destroyed."); -} - -fn get_power_stats<'a, T: Context<'a>>( - context: &T, - chip_id: String, -) -> Result<[JValue<'a>; 4], UwbErr> { - let dispatcher = context.get_dispatcher()?; - match dispatcher.block_on_jni_command(JNICommand::UciGetPowerStats, chip_id)? { - UciResponse::AndroidGetPowerStatsRsp(data) => Ok([ - JValue::Int(data.get_stats().idle_time_ms as i32), - JValue::Int(data.get_stats().tx_time_ms as i32), - JValue::Int(data.get_stats().rx_time_ms as i32), - JValue::Int(data.get_stats().total_wake_count as i32), - ]), - _ => Err(UwbErr::failed()), - } -} - -fn uwa_get_device_info( - dispatcher: &dyn Dispatcher, - chip_id: String, -) -> Result<UciResponse, UwbErr> { - let res = dispatcher.block_on_jni_command(JNICommand::UciGetDeviceInfo, chip_id)?; - Ok(res) -} - -fn reset_device<'a, T: Context<'a>>( - context: &T, - reset_config: u8, - chip_id: String, -) -> Result<(), UwbErr> { - let dispatcher = context.get_dispatcher()?; - let res = match dispatcher - .block_on_jni_command(JNICommand::UciDeviceReset { reset_config }, chip_id)? - { - UciResponse::DeviceResetRsp(data) => data, - _ => return Err(UwbErr::failed()), - }; - status_code_to_res(res.get_status()) -} - -#[cfg(test)] -mod mock_context; -#[cfg(test)] -mod mock_dispatcher; - -#[cfg(test)] -mod tests { - use super::*; - - use crate::mock_context::MockContext; - use crate::mock_dispatcher::MockDispatcher; - - #[test] - fn test_boolean_result_helper() { - assert_eq!(true as jboolean, boolean_result_helper(Ok(()), "Foo")); - assert_eq!(false as jboolean, boolean_result_helper(Err(UwbErr::Undefined), "Foo")); - } - - #[test] - fn test_byte_result_helper() { - assert_eq!(StatusCode::UciStatusOk.to_i8().unwrap(), byte_result_helper(Ok(()), "Foo")); - assert_eq!( - StatusCode::UciStatusFailed.to_i8().unwrap(), - byte_result_helper(Err(UwbErr::Undefined), "Foo") - ); - assert_eq!( - StatusCode::UciStatusRejected.to_i8().unwrap(), - byte_result_helper(Err(UwbErr::StatusCode(StatusCode::UciStatusRejected)), "Foo") - ); - } - - #[test] - fn test_do_initialize() { - let chip_id = String::from("chip_id"); - let packet = uwb_uci_packets::GetDeviceInfoRspBuilder { - status: StatusCode::UciStatusOk, - uci_version: 0, - mac_version: 0, - phy_version: 0, - uci_test_version: 0, - vendor_spec_info: vec![], - } - .build(); - - let mut dispatcher = MockDispatcher::new(); - dispatcher - .expect_block_on_jni_command(JNICommand::Enable, Ok(UciResponse::EnableRsp(true))); - dispatcher.expect_block_on_jni_command( - JNICommand::UciGetDeviceInfo, - Ok(UciResponse::GetDeviceInfoRsp(packet.clone())), - ); - let mut context = MockContext::new(dispatcher); - - let result = do_initialize(&context, chip_id); - let device_info = context.get_mock_dispatcher().get_device_info().clone(); - assert!(result.is_ok()); - assert_eq!(device_info.unwrap().to_vec(), packet.to_vec()); - } - - #[test] - fn test_do_deinitialize() { - let mut dispatcher = MockDispatcher::new(); - dispatcher.expect_send_jni_command(JNICommand::Disable(true), Ok(())); - dispatcher.expect_wait_for_exit(Ok(())); - let context = MockContext::new(dispatcher); - let chip_id = String::from("chip_id"); - - let result = do_deinitialize(&context, chip_id); - assert!(result.is_ok()); - } - - #[test] - fn test_get_specification_info() { - let packet = uwb_uci_packets::GetDeviceInfoRspBuilder { - status: StatusCode::UciStatusOk, - uci_version: 0x1234, - mac_version: 0x5678, - phy_version: 0x9ABC, - uci_test_version: 0x1357, - vendor_spec_info: vec![], - } - .build(); - let expected_array = [ - 0x34, 0x2, 0x1, // uci_version - 0x78, 0x6, 0x5, // mac_version. - 0xBC, 0xA, 0x9, // phy_version. - 0x57, 0x3, 0x1, // uci_test_version. - 1, // fira_major_version - 0, // fira_minor_version - 1, // ccc_major_version - 0, // ccc_minor_version - ]; - - let mut dispatcher = MockDispatcher::new(); - dispatcher.set_device_info(Some(packet)); - let context = MockContext::new(dispatcher); - let chip_id = String::from("chip_id"); - - let results = get_specification_info(&context, chip_id).unwrap(); - for (idx, result) in results.iter().enumerate() { - assert_eq!(TryInto::<jint>::try_into(*result).unwrap(), expected_array[idx]); - } - } - - #[test] - fn test_session_init() { - let session_id = 1234; - let session_type = 5; - let packet = - uwb_uci_packets::SessionInitRspBuilder { status: StatusCode::UciStatusOk }.build(); - - let mut dispatcher = MockDispatcher::new(); - dispatcher.expect_block_on_jni_command( - JNICommand::UciSessionInit(session_id, session_type), - Ok(UciResponse::SessionInitRsp(packet)), - ); - let context = MockContext::new(dispatcher); - let chip_id = String::from("chip_id"); - - let result = session_init(&context, session_id, session_type, chip_id); - assert!(result.is_ok()); - } - - #[test] - fn test_session_deinit() { - let session_id = 1234; - let packet = - uwb_uci_packets::SessionDeinitRspBuilder { status: StatusCode::UciStatusOk }.build(); - - let mut dispatcher = MockDispatcher::new(); - dispatcher.expect_block_on_jni_command( - JNICommand::UciSessionDeinit(session_id), - Ok(UciResponse::SessionDeinitRsp(packet)), - ); - let context = MockContext::new(dispatcher); - let chip_id = String::from("chip_id"); - - let result = session_deinit(&context, session_id, chip_id); - assert!(result.is_ok()); - } - - #[test] - fn test_get_session_count() { - let session_count = 7; - let packet = uwb_uci_packets::SessionGetCountRspBuilder { - status: StatusCode::UciStatusOk, - session_count, - } - .build(); - - let mut dispatcher = MockDispatcher::new(); - dispatcher.expect_block_on_jni_command( - JNICommand::UciSessionGetCount, - Ok(UciResponse::SessionGetCountRsp(packet)), - ); - let context = MockContext::new(dispatcher); - let chip_id = String::from("chip_id"); - - let result = get_session_count(&context, chip_id).unwrap(); - assert_eq!(result, session_count as jbyte); - } - - #[test] - fn test_ranging_start() { - let session_id = 1234; - let packet = - uwb_uci_packets::RangeStartRspBuilder { status: StatusCode::UciStatusOk }.build(); - - let mut dispatcher = MockDispatcher::new(); - dispatcher.expect_block_on_jni_command( - JNICommand::UciStartRange(session_id), - Ok(UciResponse::RangeStartRsp(packet)), - ); - let context = MockContext::new(dispatcher); - let chip_id = String::from("chip_id"); - - let result = ranging_start(&context, session_id, chip_id); - assert!(result.is_ok()); - } - - #[test] - fn test_ranging_stop() { - let session_id = 1234; - let packet = - uwb_uci_packets::RangeStopRspBuilder { status: StatusCode::UciStatusOk }.build(); - - let mut dispatcher = MockDispatcher::new(); - dispatcher.expect_block_on_jni_command( - JNICommand::UciStopRange(session_id), - Ok(UciResponse::RangeStopRsp(packet)), - ); - let context = MockContext::new(dispatcher); - let chip_id = String::from("chip_id"); - - let result = ranging_stop(&context, session_id, chip_id); - assert!(result.is_ok()); - } - - #[test] - fn test_get_session_state() { - let session_id = 1234; - let session_state = uwb_uci_packets::SessionState::SessionStateActive; - let packet = uwb_uci_packets::SessionGetStateRspBuilder { - status: StatusCode::UciStatusOk, - session_state, - } - .build(); - - let mut dispatcher = MockDispatcher::new(); - dispatcher.expect_block_on_jni_command( - JNICommand::UciGetSessionState(session_id), - Ok(UciResponse::SessionGetStateRsp(packet)), - ); - let context = MockContext::new(dispatcher); - let chip_id = String::from("chip_id"); - - let result = get_session_state(&context, session_id, chip_id).unwrap(); - assert_eq!(result, session_state as jbyte); - } - - #[test] - fn test_set_app_configurations() { - let session_id = 1234; - let no_of_params = 3; - let app_config_param_len = 5; - let app_configs = vec![1, 2, 3, 4, 5]; - let fake_app_config_params = std::ptr::null_mut(); - let packet = uwb_uci_packets::SessionSetAppConfigRspBuilder { - status: StatusCode::UciStatusOk, - cfg_status: vec![], - } - .build(); - - let mut dispatcher = MockDispatcher::new(); - dispatcher.expect_block_on_jni_command( - JNICommand::UciSetAppConfig { - session_id, - no_of_params, - app_config_param_len, - app_configs: app_configs.clone(), - }, - Ok(UciResponse::SessionSetAppConfigRsp(packet.clone())), - ); - let mut context = MockContext::new(dispatcher); - context.expect_convert_byte_array(fake_app_config_params, Ok(app_configs)); - let chip_id = String::from("chip_id"); - - let result = set_app_configurations( - &context, - session_id, - no_of_params, - app_config_param_len, - fake_app_config_params, - chip_id, - ) - .unwrap(); - assert_eq!(result.to_vec(), packet.to_vec()); - } - - #[test] - fn test_get_app_configurations() { - let session_id = 1234; - let no_of_params = 3; - let app_config_param_len = 5; - let app_configs = vec![1, 2, 3, 4, 5]; - let fake_app_config_params = std::ptr::null_mut(); - let packet = uwb_uci_packets::SessionGetAppConfigRspBuilder { - status: StatusCode::UciStatusOk, - tlvs: vec![], - } - .build(); - - let mut dispatcher = MockDispatcher::new(); - dispatcher.expect_block_on_jni_command( - JNICommand::UciGetAppConfig { - session_id, - no_of_params, - app_config_param_len, - app_configs: app_configs.clone(), - }, - Ok(UciResponse::SessionGetAppConfigRsp(packet.clone())), - ); - let mut context = MockContext::new(dispatcher); - context.expect_convert_byte_array(fake_app_config_params, Ok(app_configs)); - let chip_id = String::from("chip_id"); - - let result = get_app_configurations( - &context, - session_id, - no_of_params, - app_config_param_len, - fake_app_config_params, - chip_id, - ) - .unwrap(); - assert_eq!(result.to_vec(), packet.to_vec()); - } - - #[test] - fn test_get_caps_info() { - let packet = uwb_uci_packets::GetCapsInfoRspBuilder { - status: StatusCode::UciStatusOk, - tlvs: vec![], - } - .build(); - - let mut dispatcher = MockDispatcher::new(); - dispatcher.expect_block_on_jni_command( - JNICommand::UciGetCapsInfo, - Ok(UciResponse::GetCapsInfoRsp(packet.clone())), - ); - let context = MockContext::new(dispatcher); - let chip_id = String::from("chip_id"); - - let result = get_caps_info(&context, chip_id).unwrap(); - assert_eq!(result.to_vec(), packet.to_vec()); - } - - #[test] - fn test_multicast_list_update() { - let session_id = 1234; - let action = 3; - let no_of_controlee = 5; - let fake_addresses = std::ptr::null_mut(); - let address_list = Box::new([1, 3, 5, 7, 9]); - let fake_sub_session_ids = std::ptr::null_mut(); - let sub_session_id_list = Box::new([2, 4, 6, 8, 10]); - let message_control = 8; - let fake_sub_session_key_list = std::ptr::null_mut(); - let sub_session_key_list = Box::new([1, 2, 3, 4]); - let split_sub_session_keys = - split_sub_session_keys(sub_session_key_list.to_vec(), message_control).unwrap(); - let packet = uwb_uci_packets::SessionUpdateControllerMulticastListRspBuilder { - status: StatusCode::UciStatusOk, - } - .build(); - - let mut dispatcher = MockDispatcher::new(); - dispatcher.expect_block_on_jni_command( - JNICommand::UciSessionUpdateMulticastList { - session_id, - action, - no_of_controlee, - address_list: address_list.to_vec(), - sub_session_id_list: sub_session_id_list.to_vec(), - message_control, - sub_session_key_list: split_sub_session_keys, - }, - Ok(UciResponse::SessionUpdateControllerMulticastListRsp(packet)), - ); - let mut context = MockContext::new(dispatcher); - context.expect_get_short_array_region(fake_addresses, 0, Ok(address_list)); - context.expect_get_int_array_region(fake_sub_session_ids, 0, Ok(sub_session_id_list)); - context.expect_get_array_length( - fake_sub_session_key_list, - Ok(sub_session_key_list.len() as jsize), - ); - context.expect_get_int_array_region(fake_sub_session_key_list, 0, Ok(sub_session_key_list)); - let chip_id = String::from("chip_id"); - - let result = multicast_list_update( - &context, - session_id, - action, - no_of_controlee, - ControleeData { - addresses: fake_addresses, - sub_session_ids: fake_sub_session_ids, - message_control, - sub_session_keys: fake_sub_session_key_list, - }, - chip_id, - ); - assert!(result.is_ok()); - } - - #[test] - fn test_set_country_code() { - let fake_country_code = std::ptr::null_mut(); - let country_code = "US".as_bytes().to_vec(); - let packet = - uwb_uci_packets::AndroidSetCountryCodeRspBuilder { status: StatusCode::UciStatusOk } - .build(); - - let mut dispatcher = MockDispatcher::new(); - dispatcher.expect_block_on_jni_command( - JNICommand::UciSetCountryCode { code: country_code.clone() }, - Ok(UciResponse::AndroidSetCountryCodeRsp(packet)), - ); - let mut context = MockContext::new(dispatcher); - context.expect_convert_byte_array(fake_country_code, Ok(country_code)); - let chip_id = String::from("chip_id"); - - let result = set_country_code(&context, fake_country_code, chip_id); - assert!(result.is_ok()); - } - - #[test] - fn test_send_raw_vendor_cmd() { - let gid = 2; - let oid = 4; - let opcode = 6; - let fake_payload = std::ptr::null_mut(); - let payload = vec![1, 2, 4, 8]; - let response = vec![3, 6, 9]; - let packet = uwb_uci_packets::UciVendor_9_ResponseBuilder { - opcode, - payload: Some(response.clone().into()), - } - .build() - .into(); - - let mut dispatcher = MockDispatcher::new(); - dispatcher.expect_block_on_jni_command( - JNICommand::UciRawVendorCmd { gid, oid, payload: payload.clone() }, - Ok(UciResponse::RawVendorRsp(packet)), - ); - let mut context = MockContext::new(dispatcher); - context.expect_convert_byte_array(fake_payload, Ok(payload)); - let chip_id = String::from("chip_id"); - - let result = send_raw_vendor_cmd(&context, gid, oid, fake_payload, chip_id).unwrap(); - assert_eq!(result.0, uwb_uci_packets::GroupId::VendorReserved9 as i32); - assert_eq!(result.1, opcode as i32); - assert_eq!(result.2, response); - } - - #[test] - fn test_get_power_stats() { - let idle_time_ms = 5; - let tx_time_ms = 4; - let rx_time_ms = 3; - let total_wake_count = 2; - let packet = uwb_uci_packets::AndroidGetPowerStatsRspBuilder { - stats: uwb_uci_packets::PowerStats { - status: StatusCode::UciStatusOk, - idle_time_ms, - tx_time_ms, - rx_time_ms, - total_wake_count, - }, - } - .build(); - - let mut dispatcher = MockDispatcher::new(); - dispatcher.expect_block_on_jni_command( - JNICommand::UciGetPowerStats, - Ok(UciResponse::AndroidGetPowerStatsRsp(packet)), - ); - let context = MockContext::new(dispatcher); - let chip_id = String::from("chip_id"); - - let result = get_power_stats(&context, chip_id).unwrap(); - assert_eq!(TryInto::<jint>::try_into(result[0]).unwrap(), idle_time_ms as jint); - assert_eq!(TryInto::<jint>::try_into(result[1]).unwrap(), tx_time_ms as jint); - assert_eq!(TryInto::<jint>::try_into(result[2]).unwrap(), rx_time_ms as jint); - assert_eq!(TryInto::<jint>::try_into(result[3]).unwrap(), total_wake_count as jint); - } - - #[test] - fn test_reset_device() { - let reset_config = uwb_uci_packets::ResetConfig::UwbsReset as u8; - let packet = - uwb_uci_packets::DeviceResetRspBuilder { status: StatusCode::UciStatusOk }.build(); - - let mut dispatcher = MockDispatcher::new(); - dispatcher.expect_block_on_jni_command( - JNICommand::UciDeviceReset { reset_config }, - Ok(UciResponse::DeviceResetRsp(packet)), - ); - let context = MockContext::new(dispatcher); - let chip_id = String::from("chip_id"); - - let result = reset_device(&context, reset_config, chip_id); - assert!(result.is_ok()); - } - - #[test] - fn test_split_sub_session_keys() { - let sub_session_key_list = vec![1, 2, 3, 4, 5, 6, 7, 8]; - let message_control = 8; - let expected_res = vec![ - vec![0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x4], - vec![0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x8], - ]; - let byte_array = split_sub_session_keys(sub_session_key_list, message_control).unwrap(); - assert_eq!(byte_array, expected_res); - } -} diff --git a/service/uci/jni/rust/mock_context.rs b/service/uci/jni/rust/mock_context.rs deleted file mode 100644 index 403070d7..00000000 --- a/service/uci/jni/rust/mock_context.rs +++ /dev/null @@ -1,192 +0,0 @@ -use std::cell::{Cell, RefCell}; -use std::collections::VecDeque; - -use jni::objects::JObject; -use jni::sys::{jarray, jbyteArray, jint, jintArray, jshort, jshortArray, jsize}; -use uwb_uci_rust::error::UwbErr; -use uwb_uci_rust::uci::Dispatcher; - -use crate::mock_dispatcher::MockDispatcher; -use crate::Context; - -#[cfg(test)] -pub struct MockContext { - dispatcher: Cell<MockDispatcher>, - expected_calls: RefCell<VecDeque<ExpectedCall>>, -} - -#[cfg(test)] -impl MockContext { - pub fn new(dispatcher: MockDispatcher) -> Self { - Self { dispatcher: Cell::new(dispatcher), expected_calls: Default::default() } - } - - pub fn get_mock_dispatcher(&mut self) -> &mut MockDispatcher { - self.dispatcher.get_mut() - } - - pub fn expect_convert_byte_array( - &mut self, - expected_array: jbyteArray, - out: Result<Vec<u8>, jni::errors::Error>, - ) { - self.expected_calls - .borrow_mut() - .push_back(ExpectedCall::ConvertByteArray { expected_array, out }); - } - - pub fn expect_get_array_length( - &mut self, - expected_array: jarray, - out: Result<jsize, jni::errors::Error>, - ) { - self.expected_calls - .borrow_mut() - .push_back(ExpectedCall::GetArrayLength { expected_array, out }); - } - - pub fn expect_get_short_array_region( - &mut self, - expected_array: jshortArray, - expected_start: jsize, - out: Result<Box<[jshort]>, jni::errors::Error>, - ) { - self.expected_calls.borrow_mut().push_back(ExpectedCall::GetShortArrayRegion { - expected_array, - expected_start, - out, - }); - } - - pub fn expect_get_int_array_region( - &mut self, - expected_array: jintArray, - expected_start: jsize, - out: Result<Box<[jint]>, jni::errors::Error>, - ) { - self.expected_calls.borrow_mut().push_back(ExpectedCall::GetIntArrayRegion { - expected_array, - expected_start, - out, - }); - } -} - -#[cfg(test)] -impl<'a> Context<'a> for MockContext { - fn convert_byte_array(&self, array: jbyteArray) -> Result<Vec<u8>, jni::errors::Error> { - let mut expected_calls = self.expected_calls.borrow_mut(); - match expected_calls.pop_front() { - Some(ExpectedCall::ConvertByteArray { expected_array, out }) - if array == expected_array => - { - out - } - Some(call) => { - expected_calls.push_front(call); - Err(jni::errors::Error::JniCall(jni::errors::JniError::Unknown)) - } - None => Err(jni::errors::Error::JniCall(jni::errors::JniError::Unknown)), - } - } - - fn get_array_length(&self, array: jarray) -> Result<jsize, jni::errors::Error> { - let mut expected_calls = self.expected_calls.borrow_mut(); - match expected_calls.pop_front() { - Some(ExpectedCall::GetArrayLength { expected_array, out }) - if array == expected_array => - { - out - } - Some(call) => { - expected_calls.push_front(call); - Err(jni::errors::Error::JniCall(jni::errors::JniError::Unknown)) - } - None => Err(jni::errors::Error::JniCall(jni::errors::JniError::Unknown)), - } - } - - fn get_short_array_region( - &self, - array: jshortArray, - start: jsize, - buf: &mut [jshort], - ) -> Result<(), jni::errors::Error> { - let mut expected_calls = self.expected_calls.borrow_mut(); - match expected_calls.pop_front() { - Some(ExpectedCall::GetShortArrayRegion { expected_array, expected_start, out }) - if array == expected_array && start == expected_start => - { - match out { - Ok(expected_buf) => { - buf.clone_from_slice(&expected_buf); - Ok(()) - } - Err(err) => Err(err), - } - } - Some(call) => { - expected_calls.push_front(call); - Err(jni::errors::Error::JniCall(jni::errors::JniError::Unknown)) - } - None => Err(jni::errors::Error::JniCall(jni::errors::JniError::Unknown)), - } - } - - fn get_int_array_region( - &self, - array: jintArray, - start: jsize, - buf: &mut [jint], - ) -> Result<(), jni::errors::Error> { - let mut expected_calls = self.expected_calls.borrow_mut(); - match expected_calls.pop_front() { - Some(ExpectedCall::GetIntArrayRegion { expected_array, expected_start, out }) - if array == expected_array && start == expected_start => - { - match out { - Ok(expected_buf) => { - buf.clone_from_slice(&expected_buf); - Ok(()) - } - Err(err) => Err(err), - } - } - Some(call) => { - expected_calls.push_front(call); - Err(jni::errors::Error::JniCall(jni::errors::JniError::Unknown)) - } - None => Err(jni::errors::Error::JniCall(jni::errors::JniError::Unknown)), - } - } - - fn is_same_object(&self, _obj1: JObject, _obj2: JObject) -> Result<bool, jni::errors::Error> { - Ok(false) - } - - fn get_dispatcher(&self) -> Result<&'a mut dyn Dispatcher, UwbErr> { - unsafe { Ok(&mut *(self.dispatcher.as_ptr())) } - } -} - -#[cfg(test)] -enum ExpectedCall { - ConvertByteArray { - expected_array: jbyteArray, - out: Result<Vec<u8>, jni::errors::Error>, - }, - GetArrayLength { - expected_array: jarray, - out: Result<jsize, jni::errors::Error>, - }, - GetShortArrayRegion { - expected_array: jshortArray, - expected_start: jsize, - out: Result<Box<[jshort]>, jni::errors::Error>, - }, - GetIntArrayRegion { - expected_array: jintArray, - expected_start: jsize, - out: Result<Box<[jint]>, jni::errors::Error>, - }, -} diff --git a/service/uci/jni/rust/mock_dispatcher.rs b/service/uci/jni/rust/mock_dispatcher.rs deleted file mode 100644 index 3827be91..00000000 --- a/service/uci/jni/rust/mock_dispatcher.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::cell::RefCell; -use std::collections::VecDeque; - -use uwb_uci_packets::GetDeviceInfoRspPacket; -use uwb_uci_rust::error::UwbErr; -use uwb_uci_rust::uci::{uci_hrcv::UciResponse, Dispatcher, JNICommand, Result}; - -#[cfg(test)] -#[derive(Default)] -pub struct MockDispatcher { - expected_calls: RefCell<VecDeque<ExpectedCall>>, - device_info: Option<GetDeviceInfoRspPacket>, -} - -#[cfg(test)] -impl MockDispatcher { - pub fn new() -> Self { - Default::default() - } - - pub fn expect_send_jni_command(&mut self, expected_cmd: JNICommand, out: Result<()>) { - self.expected_calls - .borrow_mut() - .push_back(ExpectedCall::SendJniCommand { expected_cmd, out }) - } - - pub fn expect_block_on_jni_command( - &mut self, - expected_cmd: JNICommand, - out: Result<UciResponse>, - ) { - self.expected_calls - .borrow_mut() - .push_back(ExpectedCall::BlockOnJniCommand { expected_cmd, out }) - } - - pub fn expect_wait_for_exit(&mut self, out: Result<()>) { - self.expected_calls.borrow_mut().push_back(ExpectedCall::WaitForExit { out }) - } -} - -#[cfg(test)] -impl Drop for MockDispatcher { - fn drop(&mut self) { - assert!(self.expected_calls.borrow().is_empty()); - } -} - -#[cfg(test)] -impl Dispatcher for MockDispatcher { - fn send_jni_command(&self, cmd: JNICommand, _chip_id: String) -> Result<()> { - let mut expected_calls = self.expected_calls.borrow_mut(); - match expected_calls.pop_front() { - Some(ExpectedCall::SendJniCommand { expected_cmd, out }) if cmd == expected_cmd => out, - Some(call) => { - expected_calls.push_front(call); - Err(UwbErr::Undefined) - } - None => Err(UwbErr::Undefined), - } - } - fn block_on_jni_command(&self, cmd: JNICommand, _chip_id: String) -> Result<UciResponse> { - let mut expected_calls = self.expected_calls.borrow_mut(); - match expected_calls.pop_front() { - Some(ExpectedCall::BlockOnJniCommand { expected_cmd, out }) if cmd == expected_cmd => { - out - } - Some(call) => { - expected_calls.push_front(call); - Err(UwbErr::Undefined) - } - None => Err(UwbErr::Undefined), - } - } - fn wait_for_exit(&mut self) -> Result<()> { - let mut expected_calls = self.expected_calls.borrow_mut(); - match expected_calls.pop_front() { - Some(ExpectedCall::WaitForExit { out }) => out, - Some(call) => { - expected_calls.push_front(call); - Err(UwbErr::Undefined) - } - None => Err(UwbErr::Undefined), - } - } - - fn set_device_info(&mut self, device_info: Option<GetDeviceInfoRspPacket>) { - self.device_info = device_info; - } - - fn get_device_info(&self) -> &Option<GetDeviceInfoRspPacket> { - &self.device_info - } -} - -#[cfg(test)] -enum ExpectedCall { - SendJniCommand { expected_cmd: JNICommand, out: Result<()> }, - BlockOnJniCommand { expected_cmd: JNICommand, out: Result<UciResponse> }, - WaitForExit { out: Result<()> }, -} diff --git a/service/uci/jni_new/src/dispatcher.rs b/service/uci/jni/src/dispatcher.rs index 2bbea59a..bde146e0 100644 --- a/service/uci/jni_new/src/dispatcher.rs +++ b/service/uci/jni/src/dispatcher.rs @@ -12,15 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Implementation of Dispatcher. +//! Implementation of Dispatcher and related methods. +use crate::error::{Error, Result}; use crate::notification_manager_android::NotificationManagerAndroidBuilder; use std::collections::HashMap; +use std::ops::Deref; use std::sync::Arc; -use jni::objects::GlobalRef; -use jni::JavaVM; +use jni::objects::{GlobalRef, JObject, JString}; +use jni::{JNIEnv, JavaVM, MonitorGuard}; use tokio::runtime::{Builder as RuntimeBuilder, Runtime}; use uci_hal_android::uci_hal_android::UciHalAndroid; use uwb_core::error::{Error as UwbCoreError, Result as UwbCoreResult}; @@ -76,8 +78,8 @@ impl Dispatcher { } /// Sets log mode for all chips. - pub fn set_logger_mode(&mut self, logger_mode: UciLoggerMode) -> UwbCoreResult<()> { - for (_, manager) in self.manager_map.iter_mut() { + pub fn set_logger_mode(&self, logger_mode: UciLoggerMode) -> UwbCoreResult<()> { + for (_, manager) in self.manager_map.iter() { manager.set_logger_mode(logger_mode.clone())?; } Ok(()) @@ -101,4 +103,73 @@ impl Dispatcher { pub unsafe fn destroy_ptr(dispatcher_ptr: *mut Dispatcher) { let _ = Box::from_raw(dispatcher_ptr); } + + /// Gets reference to Dispatcher. + /// + /// # Safety + /// Must be called from a Java object holding a valid or null mDispatcherPointer. + pub unsafe fn get_dispatcher<'a>( + env: JNIEnv<'a>, + obj: JObject<'a>, + ) -> Result<GuardedDispatcher<'a>> { + let guard = env.lock_obj(obj)?; + let dispatcher_ptr_value = env.get_field(obj, "mDispatcherPointer", "J")?.j()?; + if dispatcher_ptr_value == 0 { + return Err(Error::UwbCoreError(UwbCoreError::BadParameters)); + } + let dispatcher_ptr = dispatcher_ptr_value as *const Dispatcher; + Ok(GuardedDispatcher { _guard: guard, dispatcher: &*dispatcher_ptr }) + } + + /// Gets reference to UciManagerSync with chip_id. + /// + /// # Safety + /// Must be called from a Java object holding a valid or null mDispatcherPointer. + pub unsafe fn get_uci_manager<'a>( + env: JNIEnv<'a>, + obj: JObject<'a>, + chip_id: JString, + ) -> Result<GuardedUciManager<'a>> { + // Safety: get_dispatcher and get_uci_manager has the same assumption. + let guarded_dispatcher = Self::get_dispatcher(env, obj)?; + let chip_id_str = String::from(env.get_string(chip_id)?); + guarded_dispatcher.into_guarded_uci_manager(&chip_id_str) + } +} + +/// Lifetimed reference to UciManagerSync that locks Java object while reference is alive. +pub(crate) struct GuardedUciManager<'a> { + _guard: MonitorGuard<'a>, + uci_manager: &'a UciManagerSync, +} + +impl<'a> Deref for GuardedUciManager<'a> { + type Target = UciManagerSync; + fn deref(&self) -> &Self::Target { + self.uci_manager + } +} + +/// Lifetimed reference to Dispatcher that locks Java object while reference is alive. +pub(crate) struct GuardedDispatcher<'a> { + _guard: MonitorGuard<'a>, + dispatcher: &'a Dispatcher, +} + +impl<'a> GuardedDispatcher<'a> { + pub fn into_guarded_uci_manager(self, chip_id: &str) -> Result<GuardedUciManager<'a>> { + let uci_manager = self + .dispatcher + .manager_map + .get(chip_id) + .ok_or(Error::UwbCoreError(UwbCoreError::BadParameters))?; + Ok(GuardedUciManager { _guard: self._guard, uci_manager }) + } +} + +impl<'a> Deref for GuardedDispatcher<'a> { + type Target = Dispatcher; + fn deref(&self) -> &Self::Target { + self.dispatcher + } } diff --git a/service/uci/jni_new/src/error.rs b/service/uci/jni/src/error.rs index cca2b56f..cca2b56f 100644 --- a/service/uci/jni_new/src/error.rs +++ b/service/uci/jni/src/error.rs diff --git a/service/uci/jni_new/src/helper.rs b/service/uci/jni/src/helper.rs index 986eff92..986eff92 100644 --- a/service/uci/jni_new/src/helper.rs +++ b/service/uci/jni/src/helper.rs diff --git a/service/uci/jni_new/src/jclass_name.rs b/service/uci/jni/src/jclass_name.rs index c3bbd5fd..ef75d53d 100644 --- a/service/uci/jni_new/src/jclass_name.rs +++ b/service/uci/jni/src/jclass_name.rs @@ -22,3 +22,5 @@ pub(crate) const UWB_RANGING_DATA_CLASS: &str = "com/android/server/uwb/data/Uwb pub(crate) const UWB_TWO_WAY_MEASUREMENT_CLASS: &str = "com/android/server/uwb/data/UwbTwoWayMeasurement"; pub(crate) const VENDOR_RESPONSE_CLASS: &str = "com/android/server/uwb/data/UwbVendorUciResponse"; +pub(crate) const DT_RANGING_ROUNDS_STATUS_CLASS: &str = + "com/android/server/uwb/data/DtTagUpdateRangingRoundsStatus"; diff --git a/service/uci/jni_new/src/lib.rs b/service/uci/jni/src/lib.rs index 3a9cab2f..3a9cab2f 100644 --- a/service/uci/jni_new/src/lib.rs +++ b/service/uci/jni/src/lib.rs diff --git a/service/uci/jni_new/src/notification_manager_android.rs b/service/uci/jni/src/notification_manager_android.rs index 8af4a51f..945419e8 100644 --- a/service/uci/jni_new/src/notification_manager_android.rs +++ b/service/uci/jni/src/notification_manager_android.rs @@ -494,7 +494,7 @@ impl NotificationManager for NotificationManagerAndroid { fn on_vendor_notification( &mut self, - vendor_notification: uwb_core::params::RawVendorMessage, + vendor_notification: uwb_core::params::RawUciMessage, ) -> UwbCoreResult<()> { debug!("UCI JNI: vendor notification callback."); let payload_jbytearray = self diff --git a/service/uci/jni_new/src/uci_jni_android_new.rs b/service/uci/jni/src/uci_jni_android_new.rs index 251a25fb..4d5c5ac8 100644 --- a/service/uci/jni_new/src/uci_jni_android_new.rs +++ b/service/uci/jni/src/uci_jni_android_new.rs @@ -18,8 +18,8 @@ use crate::dispatcher::Dispatcher; use crate::error::{Error, Result}; use crate::helper::{boolean_result_helper, byte_result_helper, option_result_helper}; use crate::jclass_name::{ - CONFIG_STATUS_DATA_CLASS, POWER_STATS_CLASS, TLV_DATA_CLASS, UWB_RANGING_DATA_CLASS, - VENDOR_RESPONSE_CLASS, + CONFIG_STATUS_DATA_CLASS, DT_RANGING_ROUNDS_STATUS_CLASS, POWER_STATS_CLASS, TLV_DATA_CLASS, + UWB_RANGING_DATA_CLASS, VENDOR_RESPONSE_CLASS, }; use crate::unique_jvm; @@ -37,9 +37,9 @@ use log::{debug, error}; use num_traits::cast::FromPrimitive; use uwb_core::error::Error as UwbCoreError; use uwb_core::params::{ - AppConfigTlv, CountryCode, RawAppConfigTlv, RawVendorMessage, SetAppConfigResponse, + AppConfigTlv, CountryCode, RawAppConfigTlv, RawUciMessage, + SessionUpdateActiveRoundsDtTagResponse, SetAppConfigResponse, }; -use uwb_core::uci::uci_manager_sync::UciManagerSync; use uwb_uci_packets::{ AppConfigTlvType, CapTlv, Controlee, PowerStats, ResetConfig, SessionState, SessionType, StatusCode, UpdateMulticastListAction, @@ -98,39 +98,6 @@ pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeGe 5 } -/// get mutable reference to Dispatcher. -/// -/// # Safety -/// Must be called from a Java object holding a valid or null mDispatcherPointer, and remains valid -/// until env and obj goes out of scope. -unsafe fn get_dispatcher<'a>(env: JNIEnv<'a>, obj: JObject<'a>) -> Result<&'a mut Dispatcher> { - let dispatcher_ptr_value = env.get_field(obj, "mDispatcherPointer", "J")?.j()?; - if dispatcher_ptr_value == 0 { - return Err(Error::UwbCoreError(UwbCoreError::BadParameters)); - } - let dispatcher_ptr = dispatcher_ptr_value as *mut Dispatcher; - Ok(&mut *dispatcher_ptr) -} - -/// get mutable reference to UciManagerSync with chip_id. -/// -/// # Safety -/// Must be called from a Java object holding a valid or null mDispatcherPointer, and remains valid -/// until env and obj goes out of scope. -unsafe fn get_uci_manager<'a>( - env: JNIEnv<'a>, - obj: JObject<'a>, - chip_id: JString, -) -> Result<&'a mut UciManagerSync> { - // Safety: get_dispatcher and get_uci_manager has the same assumption. - let dispatcher_ref = get_dispatcher(env, obj)?; - let chip_id_str = String::from(env.get_string(chip_id)?); - match dispatcher_ref.manager_map.get_mut(&chip_id_str) { - Some(m) => Ok(m), - None => Err(Error::UwbCoreError(UwbCoreError::BadParameters)), - } -} - /// Turn on Single UWB chip. #[no_mangle] pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeDoInitialize( @@ -145,7 +112,7 @@ pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeDo fn native_do_initialize(env: JNIEnv, obj: JObject, chip_id: JString) -> Result<()> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it // goes out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; uci_manager.open_hal().map_err(|e| e.into()) } @@ -163,7 +130,7 @@ pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeDo fn native_do_deinitialize(env: JNIEnv, obj: JObject, chip_id: JString) -> Result<()> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it // goes out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; uci_manager.close_hal(true).map_err(|e| e.into()) } @@ -193,7 +160,7 @@ pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeDe fn native_device_reset(env: JNIEnv, obj: JObject, chip_id: JString) -> Result<()> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it // goes out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; uci_manager.device_reset(ResetConfig::UwbsReset).map_err(|e| e.into()) } @@ -224,7 +191,7 @@ fn native_session_init( SessionType::from_u8(session_type as u8).ok_or(UwbCoreError::BadParameters)?; // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it // goes out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; uci_manager.session_init(session_id as u32, session_type).map_err(|e| e.into()) } @@ -248,7 +215,7 @@ fn native_session_deinit( ) -> Result<()> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it // goes out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; uci_manager.session_deinit(session_id as u32).map_err(|e| e.into()) } @@ -270,7 +237,7 @@ pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeGe fn native_get_session_count(env: JNIEnv, obj: JObject, chip_id: JString) -> Result<u8> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it // goes out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; uci_manager.session_get_count().map_err(|e| e.into()) } @@ -294,7 +261,7 @@ fn native_ranging_start( ) -> Result<()> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it // goes out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; uci_manager.range_start(session_id as u32).map_err(|e| e.into()) } @@ -318,7 +285,7 @@ fn native_ranging_stop( ) -> Result<()> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it // goes out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; uci_manager.range_stop(session_id as u32).map_err(|e| e.into()) } @@ -349,7 +316,7 @@ fn native_get_session_state( ) -> Result<SessionState> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it // goes out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; uci_manager.session_get_state(session_id as u32).map_err(|e| e.into()) } @@ -436,7 +403,7 @@ fn native_set_app_configurations( ) -> Result<SetAppConfigResponse> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it // goes out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; let config_byte_array = env.convert_byte_array(app_config_params)?; let tlvs = parse_app_config_tlv_vec(no_of_params, &config_byte_array)?; uci_manager.session_set_app_config(session_id as u32, tlvs).map_err(|e| e.into()) @@ -500,7 +467,7 @@ fn native_get_app_configurations( ) -> Result<Vec<AppConfigTlv>> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it // goes out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; let app_config_bytearray = env.convert_byte_array(app_config_params)?; uci_manager .session_get_app_config( @@ -557,7 +524,7 @@ pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeGe fn native_get_caps_info(env: JNIEnv, obj: JObject, chip_id: JString) -> Result<Vec<CapTlv>> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it // goes out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; uci_manager.core_get_caps_info().map_err(|e| e.into()) } @@ -603,7 +570,7 @@ fn native_controller_multicast_list_update( ) -> Result<()> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it goes // out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; let mut address_list = vec![ 0i16; env.get_array_length(addresses)?.try_into().map_err(|_| { @@ -656,7 +623,7 @@ fn native_set_country_code( ) -> Result<()> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it goes // out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; let country_code = env.convert_byte_array(country_code)?; debug!("Country code: {:?}", country_code); if country_code.len() != 2 { @@ -684,14 +651,14 @@ pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeSe fn native_set_log_mode(env: JNIEnv, obj: JObject, log_mode_jstring: JString) -> Result<()> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it goes // out of scope. - let dispatcher = unsafe { get_dispatcher(env, obj) }?; + let dispatcher = unsafe { Dispatcher::get_dispatcher(env, obj) }?; let logger_mode_str = String::from(env.get_string(log_mode_jstring)?); debug!("UCI log: log started in {} mode", &logger_mode_str); let logger_mode = logger_mode_str.try_into()?; dispatcher.set_logger_mode(logger_mode).map_err(|e| e.into()) } -fn create_vendor_response(msg: RawVendorMessage, env: JNIEnv) -> Result<jobject> { +fn create_vendor_response(msg: RawUciMessage, env: JNIEnv) -> Result<jobject> { let vendor_response_class = env.find_class(VENDOR_RESPONSE_CLASS)?; match env.new_object( vendor_response_class, @@ -725,6 +692,26 @@ fn create_invalid_vendor_response(env: JNIEnv) -> Result<jobject> { } } +fn create_ranging_round_status( + response: SessionUpdateActiveRoundsDtTagResponse, + env: JNIEnv, +) -> Result<jobject> { + let dt_ranging_rounds_update_status_class = env.find_class(DT_RANGING_ROUNDS_STATUS_CLASS)?; + let indexes = response.ranging_round_indexes; + match env.new_object( + dt_ranging_rounds_update_status_class, + "(II[B)V", + &[ + JValue::Int(response.status as i32), + JValue::Int(indexes.len() as i32), + JValue::Object(JObject::from(env.byte_array_from_slice(indexes.as_ref())?)), + ], + ) { + Ok(o) => Ok(*o), + Err(e) => Err(e.into()), + } +} + /// Send Raw vendor command on a single UWB device. Returns an invalid response if failed. #[no_mangle] pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeSendRawVendorCmd( @@ -759,12 +746,12 @@ fn native_send_raw_vendor_cmd( oid: jint, payload_jarray: jbyteArray, chip_id: JString, -) -> Result<RawVendorMessage> { +) -> Result<RawUciMessage> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it // goes out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; let payload = env.convert_byte_array(payload_jarray)?; - uci_manager.raw_vendor_cmd(gid as u32, oid as u32, payload).map_err(|e| e.into()) + uci_manager.raw_uci_cmd(gid as u32, oid as u32, payload).map_err(|e| e.into()) } fn create_power_stats(power_stats: PowerStats, env: JNIEnv) -> Result<jobject> { @@ -806,10 +793,55 @@ pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeGe fn native_get_power_stats(env: JNIEnv, obj: JObject, chip_id: JString) -> Result<PowerStats> { // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it // goes out of scope. - let uci_manager = unsafe { get_uci_manager(env, obj, chip_id) }?; + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; uci_manager.android_get_power_stats().map_err(|e| e.into()) } +/// Update active ranging rounds for DT-TAG +#[no_mangle] +pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeSessionUpdateActiveRoundsDtTag( + env: JNIEnv, + obj: JObject, + session_id: jint, + _ranging_rounds: jint, + ranging_round_indexes: jbyteArray, + chip_id: JString, +) -> jobject { + debug!("{}: enter", function_name!()); + match option_result_helper( + native_set_ranging_rounds_dt_tag( + env, + obj, + session_id as u32, + ranging_round_indexes, + chip_id, + ), + function_name!(), + ) { + Some(rr) => create_ranging_round_status(rr, env) + .map_err(|e| { + error!("{} failed with {:?}", function_name!(), &e); + e + }) + .unwrap_or(*JObject::null()), + None => *JObject::null(), + } +} + +fn native_set_ranging_rounds_dt_tag( + env: JNIEnv, + obj: JObject, + session_id: u32, + ranging_round_indexes: jbyteArray, + chip_id: JString, +) -> Result<SessionUpdateActiveRoundsDtTagResponse> { + // Safety: Java side owns Dispatcher by pointer, and borrows to this function until it + // goes out of scope. + let uci_manager = unsafe { Dispatcher::get_uci_manager(env, obj, chip_id) }?; + let indexes = env.convert_byte_array(ranging_round_indexes)?; + uci_manager.session_update_active_rounds_dt_tag(session_id, indexes).map_err(|e| e.into()) +} + /// Get the class loader object. Has to be called from a JNIEnv where the local java classes are /// loaded. Results in a global reference to the class loader object that can be used to look for /// classes in other native thread. @@ -876,9 +908,14 @@ pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeDi fn native_dispatcher_destroy(env: JNIEnv, obj: JObject) -> Result<()> { let dispatcher_ptr_long = env.get_field(obj, "mDispatcherPointer", "J")?.j()?; - // Safety: Java side owns Dispatcher through the pointer, and asks it to be destroyed - unsafe { - Dispatcher::destroy_ptr(dispatcher_ptr_long as *mut Dispatcher); + if dispatcher_ptr_long == 0 { + error!("UCI JNI: pointer to dispatcher to be destroyed is null."); + Err(Error::UwbCoreError(UwbCoreError::BadParameters)) + } else { + // Safety: Java side owns Dispatcher through the pointer, and asks it to be destroyed + unsafe { + Dispatcher::destroy_ptr(dispatcher_ptr_long as *mut Dispatcher); + } + Ok(()) } - Ok(()) } diff --git a/service/uci/jni_new/src/unique_jvm.rs b/service/uci/jni/src/unique_jvm.rs index c378fbb1..c378fbb1 100644 --- a/service/uci/jni_new/src/unique_jvm.rs +++ b/service/uci/jni/src/unique_jvm.rs diff --git a/service/uci/jni_new/Android.bp b/service/uci/jni_new/Android.bp deleted file mode 100644 index 0483f679..00000000 --- a/service/uci/jni_new/Android.bp +++ /dev/null @@ -1,64 +0,0 @@ -package { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -rust_defaults { - name: "libuwb_uci_jni_rust_new_defaults", - crate_name: "uwb_uci_jni_rust_new", - lints: "android", - clippy_lints: "android", - min_sdk_version: "Tiramisu", - srcs: ["src/lib.rs"], - rustlibs: [ - "libbinder_rs", - "libjni", - "liblog_rust", - "liblogger", - "libnum_traits", - "libthiserror", - "libtokio", - "libuci_hal_android", - "libuwb_core", - "libuwb_uci_packets", - ], - prefer_rlib: true, - apex_available: [ - "com.android.uwb", - ], - host_supported: true, -} - -rust_ffi_shared { - name: "libuwb_uci_jni_rust_new", - defaults: ["libuwb_uci_jni_rust_new_defaults"], -} - -rust_test { - name: "libuwb_uci_jni_rust_new_tests", - defaults: ["libuwb_uci_jni_rust_new_defaults"], - target: { - android: { - test_suites: [ - "general-tests", - ], - }, - host: { - test_suites: [ - "general-tests", - ], - }, - }, - // Support multilib variants (using different suffix per sub-architecture), which is needed on - // build targets with secondary architectures, as the MTS test suite packaging logic flattens - // all test artifacts into a single `testcases` directory. - compile_multilib: "both", - multilib: { - lib32: { - suffix: "32", - }, - lib64: { - suffix: "", - }, - }, - auto_gen_config: true, -}
\ No newline at end of file diff --git a/service/uci/jni_unused/Android.bp b/service/uci/jni_unused/Android.bp new file mode 100755 index 00000000..7dbf6b64 --- /dev/null +++ b/service/uci/jni_unused/Android.bp @@ -0,0 +1,3 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} diff --git a/service/uci/jni/UwbEventManager.cpp b/service/uci/jni_unused/UwbEventManager.cpp index 1445d674..1445d674 100755 --- a/service/uci/jni/UwbEventManager.cpp +++ b/service/uci/jni_unused/UwbEventManager.cpp diff --git a/service/uci/jni/UwbEventManager.h b/service/uci/jni_unused/UwbEventManager.h index f95e92e1..f95e92e1 100755 --- a/service/uci/jni/UwbEventManager.h +++ b/service/uci/jni_unused/UwbEventManager.h diff --git a/service/uci/jni/UwbJniInternal.h b/service/uci/jni_unused/UwbJniInternal.h index d031e279..d031e279 100755 --- a/service/uci/jni/UwbJniInternal.h +++ b/service/uci/jni_unused/UwbJniInternal.h diff --git a/service/uci/jni/UwbJniTypes.h b/service/uci/jni_unused/UwbJniTypes.h index d3316a29..d3316a29 100755 --- a/service/uci/jni/UwbJniTypes.h +++ b/service/uci/jni_unused/UwbJniTypes.h diff --git a/service/uci/jni/UwbNativeManager.cpp b/service/uci/jni_unused/UwbNativeManager.cpp index 527b39c5..527b39c5 100755 --- a/service/uci/jni/UwbNativeManager.cpp +++ b/service/uci/jni_unused/UwbNativeManager.cpp diff --git a/service/uci/jni/rfTest/UwbRfTestManager.cpp b/service/uci/jni_unused/rfTest/UwbRfTestManager.cpp index d526f254..d526f254 100755 --- a/service/uci/jni/rfTest/UwbRfTestManager.cpp +++ b/service/uci/jni_unused/rfTest/UwbRfTestManager.cpp diff --git a/service/uci/jni/rfTest/UwbRfTestManager.h b/service/uci/jni_unused/rfTest/UwbRfTestManager.h index 1d9ed430..1d9ed430 100755 --- a/service/uci/jni/rfTest/UwbRfTestManager.h +++ b/service/uci/jni_unused/rfTest/UwbRfTestManager.h diff --git a/service/uci/jni/rfTest/UwbRfTestNativeManager.cpp b/service/uci/jni_unused/rfTest/UwbRfTestNativeManager.cpp index 5d5c8529..5d5c8529 100755 --- a/service/uci/jni/rfTest/UwbRfTestNativeManager.cpp +++ b/service/uci/jni_unused/rfTest/UwbRfTestNativeManager.cpp diff --git a/service/uci/jni/utils/CondVar.cpp b/service/uci/jni_unused/utils/CondVar.cpp index 45e6ef59..45e6ef59 100755 --- a/service/uci/jni/utils/CondVar.cpp +++ b/service/uci/jni_unused/utils/CondVar.cpp diff --git a/service/uci/jni/utils/CondVar.h b/service/uci/jni_unused/utils/CondVar.h index 52a74907..52a74907 100755 --- a/service/uci/jni/utils/CondVar.h +++ b/service/uci/jni_unused/utils/CondVar.h diff --git a/service/uci/jni/utils/IntervalTimer.cpp b/service/uci/jni_unused/utils/IntervalTimer.cpp index 3bb4fd48..3bb4fd48 100755 --- a/service/uci/jni/utils/IntervalTimer.cpp +++ b/service/uci/jni_unused/utils/IntervalTimer.cpp diff --git a/service/uci/jni/utils/IntervalTimer.h b/service/uci/jni_unused/utils/IntervalTimer.h index c7dcd7e5..c7dcd7e5 100755 --- a/service/uci/jni/utils/IntervalTimer.h +++ b/service/uci/jni_unused/utils/IntervalTimer.h diff --git a/service/uci/jni/utils/JniLog.h b/service/uci/jni_unused/utils/JniLog.h index ac2a1072..ac2a1072 100755 --- a/service/uci/jni/utils/JniLog.h +++ b/service/uci/jni_unused/utils/JniLog.h diff --git a/service/uci/jni/utils/Mutex.cpp b/service/uci/jni_unused/utils/Mutex.cpp index e2609f0f..e2609f0f 100755 --- a/service/uci/jni/utils/Mutex.cpp +++ b/service/uci/jni_unused/utils/Mutex.cpp diff --git a/service/uci/jni/utils/Mutex.h b/service/uci/jni_unused/utils/Mutex.h index 86fa563f..86fa563f 100755 --- a/service/uci/jni/utils/Mutex.h +++ b/service/uci/jni_unused/utils/Mutex.h diff --git a/service/uci/jni/utils/ScopedJniEnv.h b/service/uci/jni_unused/utils/ScopedJniEnv.h index 77ddc7ab..77ddc7ab 100755 --- a/service/uci/jni/utils/ScopedJniEnv.h +++ b/service/uci/jni_unused/utils/ScopedJniEnv.h diff --git a/service/uci/jni/utils/SyncEvent.cpp b/service/uci/jni_unused/utils/SyncEvent.cpp index e9907e87..e9907e87 100755 --- a/service/uci/jni/utils/SyncEvent.cpp +++ b/service/uci/jni_unused/utils/SyncEvent.cpp diff --git a/service/uci/jni/utils/SyncEvent.h b/service/uci/jni_unused/utils/SyncEvent.h index 45ae85b7..45ae85b7 100755 --- a/service/uci/jni/utils/SyncEvent.h +++ b/service/uci/jni_unused/utils/SyncEvent.h diff --git a/service/uci/jni/utils/UwbJniUtil.cpp b/service/uci/jni_unused/utils/UwbJniUtil.cpp index 94ee987c..94ee987c 100755 --- a/service/uci/jni/utils/UwbJniUtil.cpp +++ b/service/uci/jni_unused/utils/UwbJniUtil.cpp diff --git a/service/uci/jni/utils/UwbJniUtil.h b/service/uci/jni_unused/utils/UwbJniUtil.h index 52be6627..52be6627 100755 --- a/service/uci/jni/utils/UwbJniUtil.h +++ b/service/uci/jni_unused/utils/UwbJniUtil.h diff --git a/tests/cts/hostsidetests/multidevices/uwb/Android.bp b/tests/cts/hostsidetests/multidevices/uwb/Android.bp new file mode 100644 index 00000000..6ea14c31 --- /dev/null +++ b/tests/cts/hostsidetests/multidevices/uwb/Android.bp @@ -0,0 +1,79 @@ +// Copyright (C) 2022 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +python_defaults { + name: "CtsUwbMultiDevicePythonDefaults", + libs: [ + "mobly", + ], + version: { + py3: { + embedded_launcher: true, + }, + }, +} + +python_test_host { + name: "CtsUwbMultiDeviceTestCase_UwbManagerTests", + main: "uwb_manager_test.py", + srcs: [ + "uwb_manager_test.py", + "lib/uwb_base_test.py", + "lib/uwb_ranging_decorator.py", + "lib/uwb_ranging_params.py", + "test_utils/uwb_test_utils.py", + ], + test_suites: [ + "cts", + "general-tests", + ], + test_config: "UwbManagerTests/AndroidTest.xml", + test_options: { + unit_test: false, + }, + data: [ + // Package the snippet with the mobly test + ":uwb_snippet", + ], + defaults: ["CtsUwbMultiDevicePythonDefaults"], +} + +python_test_host { + name: "CtsUwbMultiDeviceTestCase_FiraRangingTests", + main: "ranging_test.py", + srcs: [ + "ranging_test.py", + "lib/uwb_base_test.py", + "lib/uwb_ranging_decorator.py", + "lib/uwb_ranging_params.py", + "test_utils/uwb_test_utils.py", + ], + test_suites: [ + "cts", + "general-tests", + ], + test_config: "FiraRangingTests/AndroidTest.xml", + test_options: { + unit_test: false, + }, + data: [ + // Package the snippet with the mobly test + ":uwb_snippet", + ], + defaults: ["CtsUwbMultiDevicePythonDefaults"], +} diff --git a/tests/cts/hostsidetests/multidevices/uwb/FiraRangingTests/AndroidTest.xml b/tests/cts/hostsidetests/multidevices/uwb/FiraRangingTests/AndroidTest.xml new file mode 100644 index 00000000..94ce8f19 --- /dev/null +++ b/tests/cts/hostsidetests/multidevices/uwb/FiraRangingTests/AndroidTest.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 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. +--> +<configuration description="Config for CTS Uwb multi-device test cases"> + <option name="test-suite-tag" value="cts" /> + <option name="config-descriptor:metadata" key="component" value="uwb" /> + <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" /> + <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> + <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" /> + + <object class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController" + type="module_controller"> + <option name="required-feature" value="android.hardware.uwb" /> + </object> + + <device name="device1"> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="test-file-name" value="uwb_snippet.apk" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="input keyevent KEYCODE_WAKEUP" /> + <option name="run-command" value="wm dismiss-keyguard" /> + </target_preparer> + </device> + <device name="device2"> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="test-file-name" value="uwb_snippet.apk" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="input keyevent KEYCODE_WAKEUP" /> + <option name="run-command" value="wm dismiss-keyguard" /> + </target_preparer> + </device> + + <test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest"> + <!-- The mobly-par-file-name should match the module name --> + <option name="mobly-par-file-name" value="CtsUwbMultiDeviceTestCase_FiraRangingTests" /> + <!-- Timeout limit in milliseconds for all test cases of the python binary --> + <option name="mobly-test-timeout" value="900000" /> + </test> +</configuration> + diff --git a/tests/cts/hostsidetests/multidevices/uwb/OWNERS b/tests/cts/hostsidetests/multidevices/uwb/OWNERS new file mode 100644 index 00000000..ea05fdf0 --- /dev/null +++ b/tests/cts/hostsidetests/multidevices/uwb/OWNERS @@ -0,0 +1,7 @@ +# Bug component: 33618 +include platform/packages/modules/Uwb:/OWNERS + +# Engprod - Not owner of the test but help maintaining the module as an example +jdesprez@google.com +frankfeng@google.com +murj@google.com diff --git a/tests/cts/hostsidetests/multidevices/uwb/UwbManagerTests/AndroidTest.xml b/tests/cts/hostsidetests/multidevices/uwb/UwbManagerTests/AndroidTest.xml new file mode 100644 index 00000000..750f6de1 --- /dev/null +++ b/tests/cts/hostsidetests/multidevices/uwb/UwbManagerTests/AndroidTest.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 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. +--> +<configuration description="Config for CTS Uwb multi-device test cases"> + <option name="test-suite-tag" value="cts" /> + <option name="config-descriptor:metadata" key="component" value="uwb" /> + <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" /> + <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> + <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" /> + + <object class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController" + type="module_controller"> + <option name="required-feature" value="android.hardware.uwb" /> + </object> + + <device name="device1"> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="test-file-name" value="uwb_snippet.apk" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="input keyevent KEYCODE_WAKEUP" /> + <option name="run-command" value="wm dismiss-keyguard" /> + </target_preparer> + </device> + <device name="device2"> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="test-file-name" value="uwb_snippet.apk" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="input keyevent KEYCODE_WAKEUP" /> + <option name="run-command" value="wm dismiss-keyguard" /> + </target_preparer> + </device> + + <test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest"> + <!-- The mobly-par-file-name should match the module name --> + <option name="mobly-par-file-name" value="CtsUwbMultiDeviceTestCase_UwbManagerTests" /> + <!-- Timeout limit in milliseconds for all test cases of the python binary --> + <option name="mobly-test-timeout" value="60000" /> + </test> +</configuration> + diff --git a/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_base_test.py b/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_base_test.py new file mode 100644 index 00000000..bd4dac2e --- /dev/null +++ b/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_base_test.py @@ -0,0 +1,80 @@ +"""Uwb base test.""" + +import logging +import re + +from mobly import base_test +from mobly import test_runner +from mobly import records +from mobly.controllers import android_device + +RELEASE_ID_REGEX = re.compile(r"\w+\.\d+\.\d+") + + +class UwbBaseTest(base_test.BaseTestClass): + """Base class for Uwb tests.""" + + def setup_class(self): + """Sets up the Android devices for Uwb test.""" + super().setup_class() + self.android_devices = self.register_controller(android_device, + min_number=2) + for ad in self.android_devices: + ad.load_snippet("uwb", "com.google.snippet.uwb") + ad.adb.shell(["cmd", "uwb", "force-country-code", "enabled", "US"]) + + def teardown_class(self): + super().teardown_class() + self._record_all() + + def _get_effort_name(self) -> str: + """Gets the TestTracker effort name from the Android build ID. + + Returns: + Testtracker effort name for the test results. + """ + effort_name = "UNKNOWN" + for record in self._controller_manager.get_controller_info_records(): + if record.controller_name == "AndroidDevice" and record.controller_info: + build_info = record.controller_info[0]["build_info"] + if re.match(RELEASE_ID_REGEX, build_info["build_id"]): + effort_name = build_info["build_id"] + else: + effort_name = build_info[android_device.BuildInfoConstants + .BUILD_VERSION_INCREMENTAL.build_info_key] + break + return effort_name + + def _record(self, tr_record: records.TestResultRecord): + """Records TestTracker data for a single test. + + Args: + tr_record: test case record. + """ + self.record_data({ + "Test Class": tr_record.test_class, + "Test Name": tr_record.test_name, + "sponge_properties": { + "test_tracker_effort_name": self._get_effort_name(), + "test_tracker_uuid": tr_record.uid + } + }) + + def _record_all(self): + """Records TestTracker data for all tests.""" + tr_record_dict = {} + for tr_record in self.results.executed: + if not tr_record.uid: + logging.warning("Missing UID for test %s", tr_record.test_name) + continue + if tr_record.uid in tr_record_dict: + record = tr_record_dict[tr_record.uid] + if record in self.results.failed or record in self.results.error: + continue + tr_record_dict[tr_record.uid] = tr_record + for tr_record in tr_record_dict.values(): + self._record(tr_record) + + +if __name__ == "__main__": + test_runner.main() diff --git a/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_ranging_decorator.py b/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_ranging_decorator.py new file mode 100644 index 00000000..658877a9 --- /dev/null +++ b/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_ranging_decorator.py @@ -0,0 +1,209 @@ +"""Decorator for UWB ranging methods.""" + +import time +from typing import List +from mobly.controllers import android_device +from mobly.controllers.android_device_lib import jsonrpc_client_base + +from lib import uwb_ranging_params + +CALLBACK_WAIT_TIME_SEC = 3 + + +class UwbRangingDecorator(): + """Decorator for Uwb ranging methods.""" + + def __init__(self, ad: android_device.AndroidDevice): + """Initialize the ranging device. + + Args: + ad: android device object + + Usage: + The ranging methods should be called in the following order + 1. open_ranging() + 2. start_ranging() + 3. find peer, distance measurement, aoa measurements. + 4. stop_ranging() + 5. close_ranging() + """ + self.ad = ad + self._callback_keys = {} + self._event_handlers = {} + self.log = self.ad.log + + def clear_ranging_session_callback_events(self, ranging_session_id: int = 0): + """Clear 'RangingSessionCallback' events from EventCache. + + Args: + ranging_session_id: ranging session id. + """ + handler = self._event_handlers[ranging_session_id] + handler.getAll("RangingSessionCallback") + + def verify_callback_received(self, + ranging_event: str, + session: int = 0, + timeout: int = CALLBACK_WAIT_TIME_SEC): + """Verifies if the expected callback is received. + + Args: + ranging_event: Expected ranging event. + session: ranging session. + timeout: callback timeout. + + Raises: + TimeoutError: if the expected callback event is not received. + """ + handler = self._event_handlers[session] + start_time = time.time() + while time.time() - start_time < timeout: + try: + event = handler.waitAndGet( + "RangingSessionCallback", timeout=timeout) + event_received = event.data["rangingSessionEvent"] + self.ad.log.debug("Received event - %s" % event_received) + if event_received == ranging_event: + self.ad.log.debug("Received the '%s' callback in %ss" % + (ranging_event, round(time.time() - start_time, 2))) + self.clear_ranging_session_callback_events(session) + return + except TimeoutError: + self.log.warn("Failed to receive 'RangingSessionCallback' event") + raise TimeoutError("Failed to receive '%s' event" % ranging_event) + + def open_fira_ranging(self, + params: uwb_ranging_params.UwbRangingParams, + session: int = 0): + """Opens fira ranging session. + + Args: + params: UWB ranging parameters. + session: ranging session. + """ + callback_key = "fira_session_%s" % session + handler = self.ad.uwb.openFiraRangingSession(callback_key, params.to_dict()) + self._event_handlers[session] = handler + self.verify_callback_received("Opened", session) + self._callback_keys[session] = callback_key + + def start_fira_ranging(self, session: int = 0): + """Starts Fira ranging session. + + Args: + session: ranging session. + """ + self.ad.uwb.startFiraRangingSession(self._callback_keys[session]) + self.verify_callback_received("Started", session) + + def reconfigure_fira_ranging( + self, + params: uwb_ranging_params.UwbRangingReconfigureParams, + session: int = 0): + """Reconfigures Fira ranging parameters. + + Args: + params: UWB reconfigured params. + session: ranging session. + """ + self.ad.uwb.reconfigureFiraRangingSession(self._callback_keys[session], + params.to_dict()) + self.verify_callback_received("Reconfigured", session) + + def is_uwb_peer_found(self, addr: List[int], session: int = 0) -> bool: + """Verifies if the UWB peer is found. + + Args: + addr: peer address. + session: ranging session. + + Returns: + True if peer is found, False if not. + """ + self.verify_callback_received("ReportReceived", session) + return self.ad.uwb.isUwbPeerFound(self._callback_keys[session], addr) + + def get_distance_measurement(self, + addr: List[int], + session: int = 0) -> float: + """Returns distance measurement from peer. + + Args: + addr: peer address. + session: ranging session. + + Returns: + Distance measurement in float. + + Raises: + ValueError: if the DistanceMeasurement object is null. + """ + try: + return self.ad.uwb.getDistanceMeasurement(self._callback_keys[session], + addr) + except jsonrpc_client_base.ApiError as api_error: + raise ValueError("Failed to get distance measurement.") from api_error + + def get_aoa_azimuth_measurement(self, + addr: List[int], + session: int = 0) -> float: + """Returns AoA azimuth measurement data from peer. + + Args: + addr: list, peer address. + session: ranging session. + + Returns: + AoA azimuth measurement in radians in float. + + Raises: + ValueError: if the AngleMeasurement object is null. + """ + try: + return self.ad.uwb.getAoAAzimuthMeasurement( + self._callback_keys[session], addr) + except jsonrpc_client_base.ApiError as api_error: + raise ValueError("Failed to get azimuth measurement.") from api_error + + def get_aoa_altitude_measurement(self, + addr: List[int], + session: int = 0) -> float: + """Gets UWB AoA altitude measurement data. + + Args: + addr: list, peer address. + session: ranging session. + + Returns: + AoA altitude measurement in radians in float. + + Raises: + ValueError: if the AngleMeasurement object is null. + """ + try: + return self.ad.uwb.getAoAAltitudeMeasurement( + self._callback_keys[session], addr) + except jsonrpc_client_base.ApiError as api_error: + raise ValueError("Failed to get altitude measurement.") from api_error + + def stop_ranging(self, session: int = 0): + """Stops UWB ranging session. + + Args: + session: ranging session. + """ + self.ad.uwb.stopRangingSession(self._callback_keys[session]) + self.verify_callback_received("Stopped", session) + + def close_ranging(self, session: int = 0): + """Closes ranging session. + + Args: + session: ranging session. + """ + if session not in self._callback_keys: + return + self.ad.uwb.closeRangingSession(self._callback_keys[session]) + self.verify_callback_received("Closed", session) + self._callback_keys.pop(session, None) + self._event_handlers.pop(session, None) diff --git a/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_ranging_params.py b/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_ranging_params.py new file mode 100644 index 00000000..0d5a333a --- /dev/null +++ b/tests/cts/hostsidetests/multidevices/uwb/lib/uwb_ranging_params.py @@ -0,0 +1,218 @@ +"""Class for UWB ranging parameters.""" + +import dataclasses +from typing import Any, Dict, List, Optional + + +class FiraParamEnums: + """Class for Fira parameter constants.""" + + # channels + UWB_CHANNEL_5 = 5 + UWB_CHANNEL_9 = 9 + + # preamble codes + UWB_PREAMBLE_CODE_INDEX_9 = 9 + UWB_PREAMBLE_CODE_INDEX_10 = 10 + UWB_PREAMBLE_CODE_INDEX_11 = 11 + UWB_PREAMBLE_CODE_INDEX_12 = 12 + + # ranging device types + DEVICE_TYPE_CONTROLEE = 0 + DEVICE_TYPE_CONTROLLER = 1 + + # ranging device roles + DEVICE_ROLE_RESPONDER = 0 + DEVICE_ROLE_INITIATOR = 1 + + # multi node modes + MULTI_NODE_MODE_UNICAST = 0 + MULTI_NODE_MODE_ONE_TO_MANY = 1 + + # hopping modes + HOPPING_MODE_DISABLE = 0 + HOPPING_MODE_FIRA_HOPPING_ENABLE = 1 + + # ranging round usage + RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE = 1 + RANGING_ROUND_USAGE_DS_TWR_DEFERRED_MODE = 2 + RANGING_ROUND_USAGE_SS_TWR_NON_DEFERRED_MODE = 3 + RANGING_ROUND_USAGE_DS_TWR_NON_DEFERRED_MODE = 4 + + # mac address mode + MAC_ADDRESS_MODE_2_BYTES = 0 + MAC_ADDRESS_MODE_8_BYTES = 2 + + # initiation time in ms + INITIATION_TIME_MS = 0 + + # slot duration rstu + SLOT_DURATION_RSTU = 2400 + + # ranging interval ms + RANGING_INTERVAL_MS = 200 + + # slots per ranging round + SLOTS_PER_RR = 30 + + # in band termination attempt count + IN_BAND_TERMINATION_ATTEMPT_COUNT = 1 + + # aoa report request + AOA_RESULT_REQUEST_MODE_NO_AOA_REPORT = 0 + AOA_RESULT_REQUEST_MODE_REQ_AOA_RESULTS = 1 + + # ranging round retries + MAX_RANGING_ROUND_RETRIES = 0 + + # block stride + BLOCK_STRIDE_LENGTH = 0 + + # list update actions + MULTICAST_LIST_UPDATE_ACTION_ADD = 0 + MULTICAST_LIST_UPDATE_ACTION_DELETE = 1 + + +@dataclasses.dataclass +class UwbRangingReconfigureParams(): + """Class for UWB ranging reconfigure parameters. + + Attributes: + action: Type of reconfigure action. + address_list: new address list. + block_stride_length: block stride length + """ + action: Optional[int] = None + address_list: Optional[List[List[int]]] = None + block_stride_length: Optional[int] = None + + def to_dict(self) -> Dict[str, Any]: + """Returns UWB ranging reconfigure parameters in dictionary for sl4a. + + Returns: + UWB ranging reconfigure parameters in dictionary. + """ + reconfigure_params = {} + if self.address_list is not None: + reconfigure_params["action"] = self.action + reconfigure_params["addressList"] = self.address_list + elif self.block_stride_length is not None: + reconfigure_params["blockStrideLength"] = self.block_stride_length + return reconfigure_params + + +@dataclasses.dataclass +class UwbRangingParams(): + """Class for Uwb ranging parameters. + + Attributes: + device_type: Type of ranging device - Controller or Controlee. + device_role: Role of ranging device - Initiator or Responder. + device_address: Address of the UWB device. + destination_addresses: List of UWB peer addresses. + channel: Channel for ranging. Possible values 5 or 9. + preamble: Preamble for ranging. + ranging_round_usage : Ranging Round Usage values. + hopping_mode : Hopping modes. + mac_address_mode : MAC address modes. + initiation_time_ms : Initiation Time in ms. + slot_duration_rstu : Slot duration RSTU. + ranging_interval_ms : Ranging interval in ms. + slots_per_ranging_round : Slots per Ranging Round. + in_band_termination_attempt_count : In Band Termination Attempt count. + aoa_result_request : AOA report request. + max_ranging_round_retries : Max Ranging round retries. + block_stride_length: Block Stride Length + session_id: Ranging session ID. + multi_node_mode: Ranging mode. Possible values 1 to 1 or 1 to many. + vendor_id: Ranging device vendor ID. + static_sts_iv: Static STS value. + + Example: + An example of UWB ranging parameters passed to sl4a is below. + + self.initiator_params = { + "sessionId": 10, + "deviceType": FiraParamEnums.RANGING_DEVICE_TYPE_CONTROLLER, + "deviceRole": FiraParamEnums.RANGING_DEVICE_ROLE_INITIATOR, + "multiNodeMode": FiraParamEnums.MULTI_NODE_MODE_ONE_TO_MANY, + "channel": FiraParamEnums.UWB_CHANNEL_9, + "deviceAddress": [1, 2], + "destinationAddresses": [[3, 4],], + "vendorId": [5, 6], + "staticStsIV": [5, 6, 7, 8, 9, 10], + } + + The UwbRangingParams are passed to UwbManagerFacade#openRaningSession() + from the open_ranging() method as a JSONObject. + These are converted to FiraOpenSessionParams using + UwbManagerFacade#generateFiraOpenSessionParams(). + If some of the values are skipped in the params, default values are used. + Please see com/google/uwb/support/fira/FiraParams.java for more details + on the default values. + + If the passed params are invalid, then open_ranging() will fail. + """ + + device_type: int + device_role: int + device_address: List[int] + destination_addresses: List[List[int]] + session_id: int = 10 + channel: int = FiraParamEnums.UWB_CHANNEL_9 + preamble: int = FiraParamEnums.UWB_PREAMBLE_CODE_INDEX_10 + multi_node_mode: int = FiraParamEnums.MULTI_NODE_MODE_ONE_TO_MANY + ranging_round_usage: int = FiraParamEnums.RANGING_ROUND_USAGE_DS_TWR_DEFERRED_MODE + mac_address_mode: int = FiraParamEnums.MAC_ADDRESS_MODE_2_BYTES + initiation_time_ms: int = FiraParamEnums.INITIATION_TIME_MS + slot_duration_rstu: int = FiraParamEnums.SLOT_DURATION_RSTU + ranging_interval_ms: int = FiraParamEnums.RANGING_INTERVAL_MS + slots_per_ranging_round: int = FiraParamEnums.SLOTS_PER_RR + in_band_termination_attempt_count: int = FiraParamEnums.IN_BAND_TERMINATION_ATTEMPT_COUNT + aoa_result_request: int = FiraParamEnums.AOA_RESULT_REQUEST_MODE_REQ_AOA_RESULTS + hopping_mode: int = FiraParamEnums.HOPPING_MODE_DISABLE + max_ranging_round_retries: int = FiraParamEnums.MAX_RANGING_ROUND_RETRIES + block_stride_length: int = FiraParamEnums.BLOCK_STRIDE_LENGTH + vendor_id: List[int] = dataclasses.field(default_factory=lambda: [5, 6]) + static_sts_iv: List[int] = dataclasses.field( + default_factory=lambda: [5, 6, 7, 8, 9, 10]) + + def to_dict(self) -> Dict[str, Any]: + """Returns UWB ranging parameters in dictionary for sl4a. + + Returns: + UWB ranging parameters in dictionary. + """ + return { + "deviceType": self.device_type, + "deviceRole": self.device_role, + "deviceAddress": self.device_address, + "destinationAddresses": self.destination_addresses, + "channel": self.channel, + "preamble": self.preamble, + "rangingRoundUsage": self.ranging_round_usage, + "macAddressMode": self.mac_address_mode, + "initiationTimeMs": self.initiation_time_ms, + "slotDurationRstu": self.slot_duration_rstu, + "slotsPerRangingRound": self.slots_per_ranging_round, + "rangingIntervalMs": self.ranging_interval_ms, + "hoppingMode": self.hopping_mode, + "maxRangingRoundRetries": self.max_ranging_round_retries, + "inBandTerminationAttemptCount": self.in_band_termination_attempt_count, + "aoaResultRequest": self.aoa_result_request, + "blockStrideLength": self.block_stride_length, + "sessionId": self.session_id, + "multiNodeMode": self.multi_node_mode, + "vendorId": self.vendor_id, + "staticStsIV": self.static_sts_iv, + } + + def update(self, **kwargs: Any): + """Updates the UWB parameters with the new values. + + Args: + **kwargs: uwb attributes with new values. + """ + for key, value in kwargs.items(): + if hasattr(self, key): + setattr(self, key, value) diff --git a/tests/cts/hostsidetests/multidevices/uwb/ranging_test.py b/tests/cts/hostsidetests/multidevices/uwb/ranging_test.py new file mode 100644 index 00000000..da0596e3 --- /dev/null +++ b/tests/cts/hostsidetests/multidevices/uwb/ranging_test.py @@ -0,0 +1,1117 @@ +"""Tests for Uwb Ranging APIs.""" + +import logging +import random +import sys +from typing import List + +from mobly import asserts +from mobly import config_parser +from mobly import test_runner +from mobly import records +from mobly import signals +import timeout_decorator + +from lib import uwb_base_test +from lib import uwb_ranging_decorator +from lib import uwb_ranging_params +from test_utils import uwb_test_utils + +RESPONDER_STOP_CALLBACK_TIMEOUT = 60 + +_TEST_CASES = ( + "test_ranging_device_tracker_profile_default", + "test_ranging_nearby_share_profile_default", + "test_ranging_device_tracker_profile_reconfigure_ranging_interval", + "test_ranging_nearby_share_profile_reconfigure_ranging_interval", + "test_ranging_device_tracker_profile_no_aoa_report", + "test_ranging_nearby_share_profile_hopping_mode_enabled", + "test_ranging_rr_ss_twr_deferred_device_tracker_profile", + "test_ranging_rr_ss_twr_deferred_nearby_share_profile", + "test_stop_initiator_ranging_device_tracker_profile", + "test_stop_initiator_ranging_nearby_share_profile", + "test_stop_responder_ranging_device_tracker_profile", + "test_stop_responder_ranging_nearby_share_profile", + "test_ranging_device_tracker_profile_with_airplane_mode_toggle", + "test_ranging_nearby_share_profile_with_airplane_mode_toggle", +) + + +class RangingTest(uwb_base_test.UwbBaseTest): + """Tests for UWB Ranging APIs. + + Attributes: + android_devices: list of android device objects. + """ + + def __init__(self, configs: config_parser.TestRunConfig): + """Init method for the test class. + + Args: + configs: A config_parser.TestRunConfig object. + """ + super().__init__(configs) + self.tests = _TEST_CASES + + def setup_class(self): + super().setup_class() + self.uwb_devices = [ + uwb_ranging_decorator.UwbRangingDecorator(ad) + for ad in self.android_devices + ] + self.initiator, self.responder = self.uwb_devices + self.device_addresses = self.user_params.get("device_addresses", + [[1, 2], [3, 4]]) + self.initiator_addr, self.responder_addr = self.device_addresses + self.new_responder_addr = [4, 5] + self.block_stride_length = random.randint(1, 10) + + def setup_test(self): + super().setup_test() + for uwb_device in self.uwb_devices: + try: + uwb_device.close_ranging() + except timeout_decorator.TimeoutError: + uwb_device.log.warn("Failed to cleanup ranging sessions") + for uwb_device in self.uwb_devices: + uwb_test_utils.set_airplane_mode(uwb_device.ad, False) + + def teardown_test(self): + super().teardown_test() + self.responder.stop_ranging() + self.initiator.stop_ranging() + self.responder.close_ranging() + self.initiator.close_ranging() + + def teardown_class(self): + super().teardown_class() + for count, ad in enumerate(self.android_devices): + test_name = "initiator" if not count else "responder" + ad.take_bug_report( + test_name=test_name, destination=self.current_test_info.output_path) + + ### Helper Methods ### + + def _verify_one_to_one_ranging( + self, initiator: uwb_ranging_decorator.UwbRangingDecorator, + responder: uwb_ranging_decorator.UwbRangingDecorator, + initiator_params: uwb_ranging_params.UwbRangingParams, + responder_params: uwb_ranging_params.UwbRangingParams, + peer_addr: List[int]): + """Verifies ranging between two uwb devices. + + Args: + initiator: uwb device object. + responder: uwb device object. + initiator_params: ranging params for initiator. + responder_params: ranging params for responder. + peer_addr: address of uwb device. + """ + initiator.open_fira_ranging(initiator_params) + responder.open_fira_ranging(responder_params) + initiator.start_fira_ranging() + responder.start_fira_ranging() + uwb_test_utils.verify_peer_found(initiator, peer_addr) + + def _verify_one_to_one_ranging_reconfigured_params( + self, initiator: uwb_ranging_decorator.UwbRangingDecorator, + responder: uwb_ranging_decorator.UwbRangingDecorator, + initiator_params: uwb_ranging_params.UwbRangingParams, + responder_params: uwb_ranging_params.UwbRangingParams, + peer_addr: List[int]): + """Verifies ranging between two uwb devices with reconfigured params. + + Args: + initiator: The uwb device object. + responder: The uwb device object. + initiator_params: The ranging params for initiator. + responder_params: The ranging params for responder. + peer_addr: The new address of uwb device. + """ + # change responder addr and verify peer cannot be found + responder_params.update(device_address=peer_addr) + responder.open_fira_ranging(responder_params) + responder.start_fira_ranging() + try: + uwb_test_utils.verify_peer_found(initiator, peer_addr) + asserts.fail("Peer found without reconfiguring initiator.") + except signals.TestFailure: + logging.info("Peer %s not found as expected", peer_addr) + + # reconfigure initiator with new peer addr and verify peer found + reconfigure_params = uwb_ranging_params.UwbRangingReconfigureParams( + action=uwb_ranging_params.FiraParamEnums + .MULTICAST_LIST_UPDATE_ACTION_ADD, + address_list=[peer_addr]) + initiator.reconfigure_fira_ranging(reconfigure_params) + uwb_test_utils.verify_peer_found(initiator, peer_addr) + + def _verify_stop_initiator_callback( + self, initiator: uwb_ranging_decorator.UwbRangingDecorator, + responder: uwb_ranging_decorator.UwbRangingDecorator, + initiator_params: uwb_ranging_params.UwbRangingParams, + responder_params: uwb_ranging_params.UwbRangingParams, + peer_addr: List[int]): + """Verifies stop callback on initiator. + + Args: + initiator: uwb device object. + responder: uwb device object. + initiator_params: ranging params for initiator. + responder_params: ranging params for responder. + peer_addr: address of uwb device. + """ + + # Verify ranging + self._verify_one_to_one_ranging(initiator, responder, initiator_params, + responder_params, peer_addr) + + # Verify Stopped callbacks + initiator.stop_ranging() + responder.verify_callback_received( + "Stopped", timeout=RESPONDER_STOP_CALLBACK_TIMEOUT) + + # Restart and verify ranging + initiator.start_fira_ranging() + responder.start_fira_ranging() + uwb_test_utils.verify_peer_found(initiator, peer_addr) + + def _verify_stop_responder_callback( + self, initiator: uwb_ranging_decorator.UwbRangingDecorator, + responder: uwb_ranging_decorator.UwbRangingDecorator, + initiator_params: uwb_ranging_params.UwbRangingParams, + responder_params: uwb_ranging_params.UwbRangingParams, + peer_addr: List[int]): + """Verifies stop callback on responder. + + Args: + initiator: uwb device object. + responder: uwb device object. + initiator_params: ranging params for initiator. + responder_params: ranging params for responder. + peer_addr: address of uwb device. + """ + + # Verify ranging + self._verify_one_to_one_ranging(initiator, responder, initiator_params, + responder_params, peer_addr) + + # Verify Stopped callbacks + responder.stop_ranging() + + # Restart and verify ranging + responder.start_fira_ranging() + uwb_test_utils.verify_peer_found(initiator, peer_addr) + + def _verify_one_to_one_ranging_airplane_mode_toggle( + self, initiator: uwb_ranging_decorator.UwbRangingDecorator, + responder: uwb_ranging_decorator.UwbRangingDecorator, + initiator_params: uwb_ranging_params.UwbRangingParams, + responder_params: uwb_ranging_params.UwbRangingParams, + peer_addr: List[int]): + """Verifies ranging with airplane mode toggle. + + Args: + initiator: uwb device object. + responder: uwb device object. + initiator_params: ranging params for initiator. + responder_params: ranging params for responder. + peer_addr: address of uwb device. + """ + + # Verify ranging before APM toggle + self._verify_one_to_one_ranging(initiator, responder, initiator_params, + responder_params, peer_addr) + + # Enable APM on initiator and verify callbacks + initiator.clear_ranging_session_callback_events() + responder.clear_ranging_session_callback_events() + uwb_test_utils.set_airplane_mode(initiator.ad, True) + initiator.verify_callback_received("Closed") + responder.verify_callback_received( + "Stopped", timeout=RESPONDER_STOP_CALLBACK_TIMEOUT) + + # Disable APM, restart and verify ranging + uwb_test_utils.set_airplane_mode(initiator.ad, False) + uwb_test_utils.verify_uwb_state_callback(initiator.ad, "Inactive") + initiator.open_fira_ranging(initiator_params) + initiator.start_fira_ranging() + responder.start_fira_ranging() + uwb_test_utils.verify_peer_found(initiator, peer_addr) + + # Enable APM on responder and verify callbacks + responder.clear_ranging_session_callback_events() + uwb_test_utils.set_airplane_mode(responder.ad, True) + responder.verify_callback_received("Closed") + + # Disable APM, restart and verify ranging + uwb_test_utils.set_airplane_mode(responder.ad, False) + uwb_test_utils.verify_uwb_state_callback(responder.ad, "Inactive") + responder.open_fira_ranging(responder_params) + responder.start_fira_ranging() + uwb_test_utils.verify_peer_found(initiator, peer_addr) + + def _verify_one_to_one_ranging_reconfigure_ranging_interval( + self, initiator: uwb_ranging_decorator.UwbRangingDecorator, + block_stride_length: int, peer_addr: List[int]): + """Verifies ranging with reconfigured ranging interval. + + Args: + initiator: The uwb device object. + block_stride_length: The new block stride length to reconfigure. + peer_addr: address of the responder. + """ + initiator.log.info("Reconfigure block stride length to: %s" % + block_stride_length) + reconfigure_params = uwb_ranging_params.UwbRangingReconfigureParams( + block_stride_length=block_stride_length) + initiator.reconfigure_fira_ranging(reconfigure_params) + uwb_test_utils.verify_peer_found(initiator, peer_addr) + + ### Test Cases ### + + @records.uid("534e80d7-cfba-47c1-9ae1-62b3630458ac") + def test_ranging_default_params(self): + """Verifies ranging with default Fira parameters.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + self.responder.stop_ranging() + self.responder.close_ranging() + self._verify_one_to_one_ranging_reconfigured_params( + self.initiator, self.responder, initiator_params, responder_params, + self.new_responder_addr) + + @records.uid("04ec82db-be17-4aaf-911c-56e702c1ea55") + def test_ranging_device_tracker_profile_default(self): + """Verifies ranging with device tracker profile default values.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + + @records.uid("05fa3bc8-b4c8-4bd4-a802-80250aa27a0b") + def test_ranging_nearby_share_profile_default(self): + """Verifies ranging for device nearby share with default profile.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + self.responder.stop_ranging() + self.responder.close_ranging() + self._verify_one_to_one_ranging_reconfigured_params( + self.initiator, self.responder, initiator_params, responder_params, + self.new_responder_addr) + + @records.uid("fa9b18c2-a1e1-4bf8-bc76-796700a1c2cb") + def test_open_ranging_with_same_session_id_nearby_share(self): + """Verifies ranging for device nearby share with same session id.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + self.responder.close_ranging() + self.initiator.close_ranging() + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + + @records.uid("973a083a-f01a-44cc-a706-3b2953ca8e22") + def test_open_ranging_with_same_session_id_device_tracker(self): + """Verifies ranging with device tracker profile with same session id.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + self.responder.close_ranging() + self.initiator.close_ranging() + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + + @records.uid("dff3aaff-c3ed-47fb-b99f-d47f55c06770") + def test_ranging_default_params_reconfigure_ranging_interval(self): + """Verifies ranging with default Fira parameters.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + self._verify_one_to_one_ranging_reconfigure_ranging_interval( + self.initiator, self.block_stride_length, self.responder_addr) + + @records.uid("7800a229-af09-48e1-a6f9-bdd159afc460") + def test_ranging_device_tracker_profile_reconfigure_ranging_interval(self): + """Verifies ranging with device tracker profile default values.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + self._verify_one_to_one_ranging_reconfigure_ranging_interval( + self.initiator, self.block_stride_length, self.responder_addr) + + @records.uid("c1d98ba2-93bd-4458-abfe-0194307ac2d4") + def test_ranging_nearby_share_profile_reconfigure_ranging_interval(self): + """Verifies ranging for device nearby share with default profile.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + self._verify_one_to_one_ranging_reconfigure_ranging_interval( + self.initiator, self.block_stride_length, self.responder_addr) + + @records.uid("19ed274a-5cb2-4210-9592-0843313cb88f") + def test_ranging_device_tracker_profile_ch9_pr12(self): + """Verifies ranging with device tracker for channel 9 and preamble 12.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + preamble=uwb_ranging_params.FiraParamEnums.UWB_PREAMBLE_CODE_INDEX_12, + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + preamble=uwb_ranging_params.FiraParamEnums.UWB_PREAMBLE_CODE_INDEX_12, + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + + @records.uid("810d13ef-678f-4ee5-affc-b0b50efaafc5") + def test_ranging_device_tracker_profile_ch5_pr11(self): + """Verifies ranging with device tracker for channel 5 and preamble 11.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + channel=uwb_ranging_params.FiraParamEnums.UWB_CHANNEL_5, + preamble=uwb_ranging_params.FiraParamEnums.UWB_PREAMBLE_CODE_INDEX_11, + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + channel=uwb_ranging_params.FiraParamEnums.UWB_CHANNEL_5, + preamble=uwb_ranging_params.FiraParamEnums.UWB_PREAMBLE_CODE_INDEX_11, + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + + @records.uid("90d73266-07f2-46d8-bc06-a4a1206d693a") + def test_ranging_device_tracker_profile_ch9_pr11(self): + """Verifies device tracking profile with channel 9 and preamble 11.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + preamble=uwb_ranging_params.FiraParamEnums.UWB_PREAMBLE_CODE_INDEX_11, + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + preamble=uwb_ranging_params.FiraParamEnums.UWB_PREAMBLE_CODE_INDEX_11, + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + + @records.uid("827c969e-15dc-43f9-ad65-c8dbcc91dd56") + def test_ranging_device_tracker_profile_ch5_pr10(self): + """Verifies device tracking profile with channel 5 and preamble 10.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + channel=uwb_ranging_params.FiraParamEnums.UWB_CHANNEL_5, + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + channel=uwb_ranging_params.FiraParamEnums.UWB_CHANNEL_5, + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + + @records.uid("f7a4fad5-9340-461c-bfb3-e9e530a54a97") + def test_ranging_device_tracker_profile_ch9_pr9(self): + """Verifies ranging with device tracker for channel 9 and preamble 9.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + channel=uwb_ranging_params.FiraParamEnums.UWB_CHANNEL_9, + preamble=uwb_ranging_params.FiraParamEnums.UWB_PREAMBLE_CODE_INDEX_9, + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + channel=uwb_ranging_params.FiraParamEnums.UWB_CHANNEL_9, + preamble=uwb_ranging_params.FiraParamEnums.UWB_PREAMBLE_CODE_INDEX_9, + multi_node_mode=uwb_ranging_params + .FiraParamEnums.MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + + @records.uid("d75d7443-904c-4ebf-9cb2-5e1df484db7a") + def test_ranging_device_tracker_profile_ch5_pr9(self): + """Verifies ranging with device tracker for channel 5 and preamble 9.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + channel=uwb_ranging_params.FiraParamEnums.UWB_CHANNEL_5, + preamble=uwb_ranging_params.FiraParamEnums.UWB_PREAMBLE_CODE_INDEX_9, + multi_node_mode=uwb_ranging_params + .FiraParamEnums.MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + channel=uwb_ranging_params.FiraParamEnums.UWB_CHANNEL_5, + preamble=uwb_ranging_params.FiraParamEnums.UWB_PREAMBLE_CODE_INDEX_9, + multi_node_mode=uwb_ranging_params + .FiraParamEnums.MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + + @records.uid("0413d123-8578-477f-b07e-be2e8e0a8652") + def test_ranging_device_tracker_profile_ch5_pr12(self): + """Verifies ranging with device tracker for channel 5 and preamble 12.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + channel=uwb_ranging_params.FiraParamEnums.UWB_CHANNEL_5, + preamble=uwb_ranging_params.FiraParamEnums.UWB_PREAMBLE_CODE_INDEX_12, + multi_node_mode=uwb_ranging_params + .FiraParamEnums.MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + channel=uwb_ranging_params.FiraParamEnums.UWB_CHANNEL_5, + preamble=uwb_ranging_params.FiraParamEnums.UWB_PREAMBLE_CODE_INDEX_12, + multi_node_mode=uwb_ranging_params + .FiraParamEnums.MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + + # disable due to b/212455943 + @records.uid("73c9e335-c1da-432f-937a-d4bf15f6b338") + def _test_ranging_device_nearby_share_profile_block_stride(self): + """Verifies nearby share profile with block stride.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + block_stride_length=0xFF, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + block_stride_length=0xFF, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + + @records.uid("cf9d3a96-433e-40d6-a74e-5413c975da78") + def test_ranging_device_tracker_profile_no_aoa_report(self): + """Verifies ranging with device tracker profile with no aoa report.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + aoa_result_request=uwb_ranging_params.FiraParamEnums + .AOA_RESULT_REQUEST_MODE_NO_AOA_REPORT, + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + aoa_result_request=uwb_ranging_params.FiraParamEnums + .AOA_RESULT_REQUEST_MODE_NO_AOA_REPORT, + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + try: + self.initiator.get_aoa_azimuth_measurement(self.responder_addr) + asserts.fail("Received AoA measurement.") + except ValueError: + pass + + @records.uid("68c6e10c-749f-448b-b650-a1edaa56a833") + def test_ranging_nearby_share_profile_hopping_mode_enabled(self): + """Verifies ranging with nearby share profile with hopping mode enabled.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + ranging_interval_ms=200, + hopping_mode=uwb_ranging_params.FiraParamEnums + .HOPPING_MODE_FIRA_HOPPING_ENABLE, + slots_per_ranging_round=20, + initiation_time_ms=100, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + ranging_interval_ms=200, + hopping_mode=uwb_ranging_params.FiraParamEnums + .HOPPING_MODE_FIRA_HOPPING_ENABLE, + slots_per_ranging_round=20, + initiation_time_ms=100, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + + @records.uid("7c63291f-d610-4360-88c9-ae17c46cc0ba") + def test_ranging_rr_ss_twr_deferred_default_params(self): + """Verifies ranging with default Fira parameters and Ranging Round 1.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + ranging_round_usage=uwb_ranging_params.FiraParamEnums + .RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + ranging_round_usage=uwb_ranging_params.FiraParamEnums + .RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + + @records.uid("cb404b61-626a-4bbe-87b2-a45bf1fa4174") + def test_ranging_rr_ss_twr_deferred_device_tracker_profile(self): + """Verifies ranging with device tracker profile and ranging round 1.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ranging_round_usage=uwb_ranging_params.FiraParamEnums + .RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ranging_round_usage=uwb_ranging_params.FiraParamEnums + .RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + + @records.uid("1990b59c-dd57-4f24-8134-f1ccb2db3062") + def test_ranging_rr_ss_twr_deferred_nearby_share_profile(self): + """Verifies ranging for nearby share profile and ranging round 1.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + ranging_round_usage=uwb_ranging_params.FiraParamEnums + .RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + ranging_round_usage=uwb_ranging_params.FiraParamEnums + .RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE, + ) + self._verify_one_to_one_ranging(self.initiator, self.responder, + initiator_params, responder_params, + self.responder_addr) + + @records.uid("ae7a95b3-e8f1-4358-b3cc-ecb10cb6d20c") + def test_stop_initiator_ranging_device_tracker_profile(self): + """Verifies initiator stop ranging callbacks with device tracker profile.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + self._verify_stop_initiator_callback( + self.initiator, self.responder, initiator_params, responder_params, + self.responder_addr) + + @records.uid("cd339d74-910a-49e9-9201-b4868adf6db7") + def test_stop_initiator_ranging_nearby_share_profile(self): + """Verifies initiator stop ranging callbacks for nearby share profile.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + ) + self._verify_stop_initiator_callback( + self.initiator, self.responder, initiator_params, responder_params, + self.responder_addr) + + @records.uid("c7757248-362f-4efa-95f4-344360e163ad") + def test_stop_responder_ranging_device_tracker_profile(self): + """Verifies responder stop ranging callbacks with device tracker profile.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + self._verify_stop_responder_callback( + self.initiator, self.responder, initiator_params, responder_params, + self.responder_addr) + + @records.uid("79f9dff3-6397-495f-9a35-bc1b21b7b500") + def test_stop_responder_ranging_nearby_share_profile(self): + """Verifies responder stop ranging callbacks for nearby share profile.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + ) + self._verify_stop_responder_callback( + self.initiator, self.responder, initiator_params, responder_params, + self.responder_addr) + + @records.uid("8109b3b3-bd2d-4d0e-be70-844d98f4d6fb") + def test_ranging_device_tracker_profile_with_airplane_mode_toggle(self): + """Verifies ranging with device tracker profile and airplane mode toggle.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + multi_node_mode=uwb_ranging_params.FiraParamEnums + .MULTI_NODE_MODE_UNICAST, + initiation_time_ms=100, + ranging_interval_ms=240, + slots_per_ranging_round=6, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging_airplane_mode_toggle( + self.initiator, self.responder, initiator_params, responder_params, + self.responder_addr) + + @records.uid("583d3c33-d41a-4d91-93ef-19dc5e064fb7") + def test_ranging_nearby_share_profile_with_airplane_mode_toggle(self): + """Verifies ranging for nearby share profile and APM toggle.""" + initiator_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_INITIATOR, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLLER, + device_address=self.initiator_addr, + destination_addresses=[self.responder_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + ) + responder_params = uwb_ranging_params.UwbRangingParams( + device_role=uwb_ranging_params.FiraParamEnums.DEVICE_ROLE_RESPONDER, + device_type=uwb_ranging_params.FiraParamEnums.DEVICE_TYPE_CONTROLEE, + device_address=self.responder_addr, + destination_addresses=[self.initiator_addr], + initiation_time_ms=100, + ranging_interval_ms=200, + slots_per_ranging_round=20, + in_band_termination_attempt_count=3, + ) + self._verify_one_to_one_ranging_airplane_mode_toggle( + self.initiator, self.responder, initiator_params, responder_params, + self.responder_addr) + +if __name__ == "__main__": + index = sys.argv.index('--') + sys.argv = sys.argv[:1] + sys.argv[index + 1:] + test_runner.main() diff --git a/tests/cts/hostsidetests/multidevices/uwb/snippet/Android.bp b/tests/cts/hostsidetests/multidevices/uwb/snippet/Android.bp new file mode 100644 index 00000000..2ad7dd2e --- /dev/null +++ b/tests/cts/hostsidetests/multidevices/uwb/snippet/Android.bp @@ -0,0 +1,35 @@ +// Copyright (C) 2022 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test { + name: "uwb_snippet", + sdk_version: "system_current", + srcs: [ + "UwbManagerSnippet.java", + ], + manifest: "AndroidManifest.xml", + static_libs: [ + "androidx.test.runner", + "guava", + "mobly-snippet-lib", + "com.uwb.support.ccc", + "com.uwb.support.fira", + "com.uwb.support.generic", + "com.uwb.support.multichip", + ], +} diff --git a/tests/cts/hostsidetests/multidevices/uwb/snippet/AndroidManifest.xml b/tests/cts/hostsidetests/multidevices/uwb/snippet/AndroidManifest.xml new file mode 100644 index 00000000..6b28be9b --- /dev/null +++ b/tests/cts/hostsidetests/multidevices/uwb/snippet/AndroidManifest.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.snippet.uwb"> + + <uses-permission android:name="android.permissions.UWB_PRIVILEGED" /> + <uses-permission android:name="android.permission.UWB_RANGING" /> + + <application> + <!-- Add any classes that implement the Snippet interface as meta-data, whose + value is a comma-separated string, each section being the package path + of a snippet class --> + <meta-data + android:name="mobly-snippets" + android:value="com.google.snippet.uwb.UwbManagerSnippet" /> + </application> + <!-- Add an instrumentation tag so that the app can be launched through an + instrument command. The runner `com.google.android.mobly.snippet.SnippetRunner` + is derived from `AndroidJUnitRunner`, and is required to use the + Mobly Snippet Lib. --> + <instrumentation + android:name="com.google.android.mobly.snippet.SnippetRunner" + android:targetPackage="com.google.snippet.uwb" /> +</manifest> diff --git a/tests/cts/hostsidetests/multidevices/uwb/snippet/UwbManagerSnippet.java b/tests/cts/hostsidetests/multidevices/uwb/snippet/UwbManagerSnippet.java new file mode 100644 index 00000000..484d8cbd --- /dev/null +++ b/tests/cts/hostsidetests/multidevices/uwb/snippet/UwbManagerSnippet.java @@ -0,0 +1,79 @@ +/* + * Copyright 2022 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.google.snippet.uwb; + +import android.app.UiAutomation; +import android.content.Context; +import android.uwb.UwbManager; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.google.android.mobly.snippet.Snippet; +import com.google.android.mobly.snippet.rpc.Rpc; +import com.google.android.mobly.snippet.util.Log; + +import java.lang.reflect.Method; + +/** Snippet class exposing Android APIs for Uwb. */ +public class UwbManagerSnippet implements Snippet { + private static class UwbManagerSnippetException extends Exception { + private static final long serialVersionUID = 1; + + UwbManagerSnippetException(String msg) { + super(msg); + } + + UwbManagerSnippetException(String msg, Throwable err) { + super(msg, err); + } + } + private final UwbManager mUwbManager; + private final Context mContext; + + public UwbManagerSnippet() throws Throwable { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mUwbManager = mContext.getSystemService(UwbManager.class); + adoptShellPermission(); + } + + /** Get the UWB state. */ + @Rpc(description = "Get Uwb state") + public boolean isUwbEnabled() { + return mUwbManager.isUwbEnabled(); + } + + /** Get the UWB state. */ + @Rpc(description = "Set Uwb state") + public void setUwbEnabled(boolean enabled) { + mUwbManager.setUwbEnabled(enabled); + } + + @Override + public void shutdown() {} + + private void adoptShellPermission() throws Throwable { + UiAutomation uia = InstrumentationRegistry.getInstrumentation().getUiAutomation(); + uia.adoptShellPermissionIdentity(); + try { + Class<?> cls = Class.forName("android.app.UiAutomation"); + Method destroyMethod = cls.getDeclaredMethod("destroy"); + destroyMethod.invoke(uia); + } catch (ReflectiveOperationException e) { + throw new UwbManagerSnippetException("Failed to cleaup Ui Automation", e); + } + } +} diff --git a/tests/cts/hostsidetests/multidevices/uwb/test_utils/uwb_test_utils.py b/tests/cts/hostsidetests/multidevices/uwb/test_utils/uwb_test_utils.py new file mode 100644 index 00000000..341bd396 --- /dev/null +++ b/tests/cts/hostsidetests/multidevices/uwb/test_utils/uwb_test_utils.py @@ -0,0 +1,155 @@ +"""Test utils for UWB.""" + +import logging +import random +import time +from typing import List +from mobly import asserts +from mobly.controllers import android_device + +from lib import uwb_ranging_decorator + + +WAIT_TIME_SEC = 3 + + +def _get_uwb_state_callback_status(ad: android_device.AndroidDevice, + uwb_event: str) -> bool: + """Checks if expected callback is received. + + Args: + ad: android device object. + uwb_event: uwb state callback. + + Returns: + True if expected callback is received, False if not. + """ + callback_key = "uwb_state_%s" % random.randint(1, 100) + handler = ad.uwb.registerUwbAdapterStateCallback(callback_key) + try: + event = handler.waitAndGet("UwbAdapterStateCallback", timeout=WAIT_TIME_SEC) + event_received = event.data["uwbAdapterStateEvent"] + logging.debug("Received event - %s", event_received) + status = event_received == uwb_event + except TimeoutError as t: + raise Exception("Event 'UwbAdapterStateCallback' not received.") from t + finally: + ad.uwb.unregisterUwbAdapterStateCallback(callback_key) + return status + + +def verify_uwb_state_callback(ad: android_device.AndroidDevice, + uwb_event: str, + timeout: int = WAIT_TIME_SEC) -> bool: + """Verifies expected UWB callback is received. + + Args: + ad: android device object. + uwb_event: expected callback event. + timeout: timeout for callback event. + + Returns: + True if expected callback is received, False if not. + """ + callback_status = False + start_time = time.time() + while time.time() - start_time < timeout: + time.sleep(0.1) + if _get_uwb_state_callback_status(ad, uwb_event): + logging.debug("Received the '%s' callback in %ss", uwb_event, + round(time.time() - start_time, 2)) + callback_status = True + break + return callback_status + + +def get_uwb_state(ad: android_device.AndroidDevice): + """Gets the current UWB state. + + Args: + ad: android device object. + + Returns: + UWB state, True if enabled, False if not. + """ + if ad.build_info["build_id"].startswith("S"): + uwb_state = bool(ad.uwb.getAdapterState()) + else: + uwb_state = ad.uwb.isUwbEnabled() + return uwb_state + + +def set_uwb_state_and_verify(ad: android_device.AndroidDevice, state: bool): + """Sets UWB state to on or off and verifies it. + + Args: + ad: android device object. + state: bool, True for UWB on, False for off. + """ + failure_msg = "enabled" if state else "disabled" + ad.uwb.setUwbEnabled(state) + event_str = "Inactive" if state else "Disabled" + asserts.assert_true(verify_uwb_state_callback(ad, event_str), + "Uwb is not %s" % failure_msg) + + +def verify_peer_found(ranging_dut: uwb_ranging_decorator.UwbRangingDecorator, + peer_addr: List[int]): + """Verifies if the UWB peer is found. + + Args: + ranging_dut: uwb ranging device. + peer_addr: uwb peer device address. + """ + start_time = time.time() + while not ranging_dut.is_uwb_peer_found(peer_addr): + if time.time() - start_time > WAIT_TIME_SEC: + asserts.fail("UWB peer with address %s not found" % peer_addr) + logging.info("Peer %s found in %s seconds", peer_addr, + round(time.time() - start_time, 2)) + + +def set_airplane_mode(ad: android_device.AndroidDevice, state: bool): + """Sets the airplane mode to the given state. + + Args: + ad: android device object. + state: bool, True for Airplane mode on, False for off. + """ + ad.adb.shell( + ["settings", "put", "global", "airplane_mode_on", + str(int(state))]) + ad.adb.shell([ + "am", "broadcast", "-a", "android.intent.action.AIRPLANE_MODE", "--ez", + "state", + str(state) + ]) + start_time = time.time() + while get_airplane_mode(ad) != state: + time.sleep(0.5) + if time.time() - start_time > WAIT_TIME_SEC: + asserts.fail("Failed to set airplane mode to: %s" % state) + + +def get_airplane_mode(ad: android_device.AndroidDevice) -> bool: + """Gets the airplane mode. + + Args: + ad: android device object. + + Returns: + True if airplane mode On, False for Off. + """ + state = ad.adb.shell(["settings", "get", "global", "airplane_mode_on"]) + return bool(int(state.decode().strip())) + + +def set_screen_rotation(ad: android_device.AndroidDevice, val: int): + """Sets screen orientation to landscape or portrait mode. + + Args: + ad: android device object. + val: False for potrait, True 1 for landscape mode. + """ + ad.adb.shell(["settings", "put", "system", "accelerometer_rotation", "0"]) + ad.adb.shell(["settings", "put", "system", "user_rotation", str(val)]) diff --git a/tests/cts/hostsidetests/multidevices/uwb/uwb_manager_test.py b/tests/cts/hostsidetests/multidevices/uwb/uwb_manager_test.py new file mode 100644 index 00000000..a107d988 --- /dev/null +++ b/tests/cts/hostsidetests/multidevices/uwb/uwb_manager_test.py @@ -0,0 +1,152 @@ +"""Tests for UwbManager APIs.""" + +import sys + +from mobly import asserts +from mobly import config_parser +from mobly import test_runner +from mobly import records +from mobly.controllers import android_device + +from lib import uwb_base_test +from test_utils import uwb_test_utils + + +_TEST_CASES = ( + "test_default_uwb_state", + "test_disable_uwb_state", + "test_enable_uwb_state", + # "test_uwb_state_after_reboot_with_uwb_off", + # "test_uwb_state_after_reboot_with_uwb_on", + "test_uwb_state_with_airplane_mode_on", + "test_toggle_uwb_state_with_airplane_mode_on", + "test_uwb_state_with_airplane_mode_off", + "test_uwb_state_off_with_airplane_mode_toggle", +) + + +class UwbManagerTest(uwb_base_test.UwbBaseTest): + """Tests for UwbManager platform APIs. + + Attributes: + android_devices: list of android device objects. + """ + + def __init__(self, configs: config_parser.TestRunConfig): + """Init method for the test class. + + Args: + configs: A config_parser.TestRunConfig object. + """ + super().__init__(configs) + self.tests = _TEST_CASES + + def setup_class(self): + super().setup_class() + self.dut = self.android_devices[0] + + def teardown_class(self): + super().teardown_class() + self.dut.take_bug_report(destination=self.current_test_info.output_path) + + ### Helper methods ### + + def _test_uwb_state_after_reboot(self, dut: android_device.AndroidDevice, + state: bool): + """Sets UWB state and verifies it is persistent after reboot. + + Args: + dut: android device object. + state: bool, True for UWB mode on, False for off. + """ + uwb_test_utils.set_uwb_state_and_verify(dut, state) + dut.reboot() + dut.adb.shell(["cmd", "uwb", "force-country-code", "enabled", "US"]) + state_after_reboot = uwb_test_utils.get_uwb_state(dut) + asserts.assert_equal( + state, state_after_reboot, + "Uwb state before reboot: %s; after reboot: %s" % + (state, state_after_reboot)) + + ### Test Cases ### + + @records.uid("2d2aff95-0273-478c-be85-097085db74cd") + def test_default_uwb_state(self): + """Verifies default UWB state is On after flashing the device.""" + asserts.assert_true(uwb_test_utils.get_uwb_state(self.dut), + "UWB state: Off; Expected: On.") + + @records.uid("a6a69945-39e8-404f-8d15-d87857cdf5af") + def test_disable_uwb_state(self): + """Disables and verifies UWB state.""" + uwb_test_utils.set_uwb_state_and_verify(self.dut, False) + + @records.uid("a9283694-aa0b-4032-9701-f45bb15cb0fa") + def test_enable_uwb_state(self): + """Enables and verifies UWB state.""" + uwb_test_utils.set_uwb_state_and_verify(self.dut, True) + + @records.uid("cda92ecc-c04f-46c6-b433-a5fc30fbd822") + def test_uwb_state_after_reboot_with_uwb_off(self): + """Sets UWB state to off and verifies it is persistent after reboot.""" + self._test_uwb_state_after_reboot(self.dut, False) + + @records.uid("6d8c4e95-57d6-4d97-9838-ab5c492b6f1c") + def test_uwb_state_after_reboot_with_uwb_on(self): + """Sets UWB state to on and verifies it is persistent after reboot.""" + self._test_uwb_state_after_reboot(self.dut, True) + + @records.uid("a9d2b85a-895f-48ad-87a0-aebd19b45264") + def test_uwb_state_with_airplane_mode_on(self): + """Verifies UWB is disabled with airplane mode on.""" + uwb_test_utils.set_airplane_mode(self.dut, True) + asserts.assert_true( + uwb_test_utils.verify_uwb_state_callback(self.dut, "Disabled"), + "UWB is not disabled with airplane mode On.") + + @records.uid("29886f5b-5f91-4ffa-99e8-6d90c21d70de") + def test_toggle_uwb_state_with_airplane_mode_on(self): + """Verifies UWB cannot be turned on with airplane mode On.""" + uwb_test_utils.set_airplane_mode(self.dut, True) + asserts.assert_true( + uwb_test_utils.verify_uwb_state_callback(self.dut, "Disabled"), + "UWB is not disabled with airplane mode On.") + self.dut.uwb.setUwbEnabled(True) + asserts.assert_true( + uwb_test_utils.verify_uwb_state_callback(self.dut, "Disabled"), + "Enabling UWB with airplane mode On should not work.") + + @records.uid("f1008ca6-88d6-4fad-8b88-7dea4f331810") + def test_uwb_state_with_airplane_mode_off(self): + """Verifies UWB is disabled with airplane mode off.""" + uwb_test_utils.set_airplane_mode(self.dut, False) + asserts.assert_true( + uwb_test_utils.verify_uwb_state_callback(self.dut, "Inactive"), + "UWB is not enabled with airplane mode Off.") + + @records.uid("dd3a7c5c-25b2-4c86-8ed1-2b6ff246b7b6") + def test_uwb_state_off_with_airplane_mode_toggle(self): + """Verifies UWB disabled state is persistent with airplane mode toggle.""" + + # disable UWB + uwb_test_utils.set_uwb_state_and_verify(self.dut, False) + + # enable airplane mode and verify UWB is disabled. + uwb_test_utils.set_airplane_mode(self.dut, True) + asserts.assert_true( + uwb_test_utils.verify_uwb_state_callback(self.dut, "Disabled"), + "UWB is not disabled with airplane mode On.") + + # disable airplane mode and verify UWB is disabled. + uwb_test_utils.set_airplane_mode(self.dut, False) + asserts.assert_false( + uwb_test_utils.verify_uwb_state_callback(self.dut, "Inactive"), + "UWB state: On, Expected state: Off") + + # enable UWB + uwb_test_utils.set_uwb_state_and_verify(self.dut, True) + +if __name__ == "__main__": + index = sys.argv.index('--') + sys.argv = sys.argv[:1] + sys.argv[index + 1:] + test_runner.main() diff --git a/tests/cts/tests/Android.bp b/tests/cts/tests/Android.bp new file mode 100644 index 00000000..306606fb --- /dev/null +++ b/tests/cts/tests/Android.bp @@ -0,0 +1,43 @@ +// Copyright (C) 2021 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test { + name: "CtsUwbTestCases", + defaults: [ + "cts_defaults", + ], + // Tag this module as a cts test artifact + test_suites: [ + "cts", + "general-tests", + "mts-uwb", + ], + libs: ["android.test.runner"], + static_libs: [ + "androidx.test.ext.junit", + "ctstestrunner-axt", + "compatibility-device-util-axt", + "mockito-target-minus-junit4", + "com.uwb.support.fira", + "com.uwb.support.multichip", + "com.uwb.support.oemextension", + "com.uwb.support.dltdoa", + ], + srcs: ["src/**/*.java"], + platform_apis: true, +} diff --git a/tests/cts/tests/AndroidManifest.xml b/tests/cts/tests/AndroidManifest.xml new file mode 100644 index 00000000..adecade4 --- /dev/null +++ b/tests/cts/tests/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2021 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.uwb.cts"> + + <!-- self-instrumenting test package. --> + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:label="CTS tests for android.uwb" + android:targetPackage="android.uwb.cts" > + </instrumentation> +</manifest> + diff --git a/tests/cts/tests/AndroidTest.xml b/tests/cts/tests/AndroidTest.xml new file mode 100644 index 00000000..3104ec54 --- /dev/null +++ b/tests/cts/tests/AndroidTest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 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. +--> +<configuration description="Config for CTS UWB test cases"> + <option name="test-suite-tag" value="cts" /> + <option name="config-descriptor:metadata" key="component" value="framework" /> + <option name="config-descriptor:metadata" key="parameter" value="instant_app" /> + <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> + <option name="config-descriptor:metadata" key="parameter" value="secondary_user" /> + <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.uwb.apex" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="CtsUwbTestCases.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="android.uwb.cts" /> + </test> + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> + <option name="mainline-module-package-name" value="com.google.android.uwb" /> + </object> +</configuration> diff --git a/tests/cts/tests/OWNERS b/tests/cts/tests/OWNERS new file mode 100644 index 00000000..c4ad4164 --- /dev/null +++ b/tests/cts/tests/OWNERS @@ -0,0 +1,2 @@ +# Bug component: 1042770 +include platform/packages/modules/Uwb:/OWNERS diff --git a/tests/cts/tests/src/android/uwb/cts/AngleMeasurementTest.java b/tests/cts/tests/src/android/uwb/cts/AngleMeasurementTest.java new file mode 100644 index 00000000..f96b7988 --- /dev/null +++ b/tests/cts/tests/src/android/uwb/cts/AngleMeasurementTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.uwb.cts; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.os.Parcel; +import android.uwb.AngleMeasurement; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test of {@link AngleMeasurement}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AngleMeasurementTest { + @Test + public void testConstructs() { + double radians = 0.1234; + double errorRadians = 0.5678; + double confidence = 0.5; + + AngleMeasurement measurement = new AngleMeasurement(radians, errorRadians, confidence); + assertEquals(measurement.getRadians(), radians, 0); + assertEquals(measurement.getErrorRadians(), errorRadians, 0); + assertEquals(measurement.getConfidenceLevel(), confidence, 0); + } + + @Test + public void testInvalidRadians() { + double radians = Math.PI + 0.01; + double errorRadians = 0.5678; + double confidence = 0.5; + + constructExpectFailure(radians, errorRadians, confidence); + constructExpectFailure(-radians, errorRadians, confidence); + } + + @Test + public void testInvalidErrorRadians() { + double radians = 0.1234; + double confidence = 0.5; + + constructExpectFailure(radians, -0.01, confidence); + constructExpectFailure(-radians, Math.PI + 0.01, confidence); + } + + @Test + public void testInvalidConfidence() { + double radians = 0.1234; + double errorRadians = 0.5678; + + constructExpectFailure(radians, errorRadians, -0.01); + constructExpectFailure(radians, errorRadians, 1.01); + } + + private void constructExpectFailure(double radians, double errorRadians, double confidence) { + try { + new AngleMeasurement(radians, errorRadians, confidence); + fail(); + } catch (IllegalArgumentException e) { + // Expected + } + } + + @Test + public void testParcel() { + Parcel parcel = Parcel.obtain(); + AngleMeasurement measurement = UwbTestUtils.getAngleMeasurement(); + measurement.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + AngleMeasurement fromParcel = AngleMeasurement.CREATOR.createFromParcel(parcel); + assertEquals(measurement, fromParcel); + } +} diff --git a/tests/cts/tests/src/android/uwb/cts/AngleOfArrivalMeasurementTest.java b/tests/cts/tests/src/android/uwb/cts/AngleOfArrivalMeasurementTest.java new file mode 100644 index 00000000..085ce2e5 --- /dev/null +++ b/tests/cts/tests/src/android/uwb/cts/AngleOfArrivalMeasurementTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.uwb.cts; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.os.Parcel; +import android.uwb.AngleMeasurement; +import android.uwb.AngleOfArrivalMeasurement; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test of {@link AngleOfArrivalMeasurement}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AngleOfArrivalMeasurementTest { + + @Test + public void testBuilder() { + AngleMeasurement azimuth = UwbTestUtils.getAngleMeasurement(); + AngleMeasurement altitude = UwbTestUtils.getAngleMeasurement(); + + AngleOfArrivalMeasurement.Builder builder = new AngleOfArrivalMeasurement.Builder(azimuth); + builder.setAltitude(altitude); + + AngleOfArrivalMeasurement measurement = tryBuild(builder, true); + + assertEquals(azimuth, measurement.getAzimuth()); + assertEquals(altitude, measurement.getAltitude()); + } + + private AngleOfArrivalMeasurement tryBuild(AngleOfArrivalMeasurement.Builder builder, + boolean expectSuccess) { + AngleOfArrivalMeasurement measurement = null; + try { + measurement = builder.build(); + if (!expectSuccess) { + fail("Expected AngleOfArrivalMeasurement.Builder.build() to fail"); + } + } catch (IllegalStateException e) { + if (expectSuccess) { + fail("Expected AngleOfArrivalMeasurement.Builder.build() to succeed"); + } + } + return measurement; + } + + @Test + public void testParcel() { + Parcel parcel = Parcel.obtain(); + AngleOfArrivalMeasurement measurement = UwbTestUtils.getAngleOfArrivalMeasurement(); + measurement.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + AngleOfArrivalMeasurement fromParcel = + AngleOfArrivalMeasurement.CREATOR.createFromParcel(parcel); + assertEquals(measurement, fromParcel); + } +} diff --git a/tests/cts/tests/src/android/uwb/cts/DistanceMeasurementTest.java b/tests/cts/tests/src/android/uwb/cts/DistanceMeasurementTest.java new file mode 100644 index 00000000..fdebc78d --- /dev/null +++ b/tests/cts/tests/src/android/uwb/cts/DistanceMeasurementTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.uwb.cts; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.os.Parcel; +import android.uwb.DistanceMeasurement; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test of {@link DistanceMeasurement}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DistanceMeasurementTest { + private static final double EPSILON = 0.00000000001; + + @Test + public void testBuilder() { + double meters = 0.12; + double error = 0.54; + double confidence = 0.99; + + DistanceMeasurement.Builder builder = new DistanceMeasurement.Builder(); + tryBuild(builder, false); + + builder.setMeters(meters); + tryBuild(builder, false); + + builder.setErrorMeters(error); + tryBuild(builder, false); + + builder.setConfidenceLevel(confidence); + DistanceMeasurement measurement = tryBuild(builder, true); + + assertEquals(meters, measurement.getMeters(), 0); + assertEquals(error, measurement.getErrorMeters(), 0); + assertEquals(confidence, measurement.getConfidenceLevel(), 0); + } + + private DistanceMeasurement tryBuild(DistanceMeasurement.Builder builder, + boolean expectSuccess) { + DistanceMeasurement measurement = null; + try { + measurement = builder.build(); + if (!expectSuccess) { + fail("Expected DistanceMeasurement.Builder.build() to fail"); + } + } catch (IllegalStateException e) { + if (expectSuccess) { + fail("Expected DistanceMeasurement.Builder.build() to succeed"); + } + } + return measurement; + } + + @Test + public void testParcel() { + Parcel parcel = Parcel.obtain(); + DistanceMeasurement measurement = UwbTestUtils.getDistanceMeasurement(); + measurement.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + DistanceMeasurement fromParcel = + DistanceMeasurement.CREATOR.createFromParcel(parcel); + assertEquals(measurement, fromParcel); + } +} diff --git a/tests/cts/tests/src/android/uwb/cts/RangingMeasurementTest.java b/tests/cts/tests/src/android/uwb/cts/RangingMeasurementTest.java new file mode 100644 index 00000000..5c0a23cc --- /dev/null +++ b/tests/cts/tests/src/android/uwb/cts/RangingMeasurementTest.java @@ -0,0 +1,134 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.uwb.cts; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.os.Parcel; +import android.os.SystemClock; +import android.uwb.AngleOfArrivalMeasurement; +import android.uwb.DistanceMeasurement; +import android.uwb.RangingMeasurement; +import android.uwb.UwbAddress; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test of {@link RangingMeasurement}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RangingMeasurementTest { + private static final int TEST_RSSI_DBM = -80; + private static final int INVALID_RSSI_DBM = -129; + + @Test + public void testBuilder() { + int status = RangingMeasurement.RANGING_STATUS_SUCCESS; + UwbAddress address = UwbTestUtils.getUwbAddress(false); + long time = SystemClock.elapsedRealtimeNanos(); + AngleOfArrivalMeasurement angleMeasurement = UwbTestUtils.getAngleOfArrivalMeasurement(); + AngleOfArrivalMeasurement destinationAngleMeasurement = + UwbTestUtils.getAngleOfArrivalMeasurement(); + DistanceMeasurement distanceMeasurement = UwbTestUtils.getDistanceMeasurement(); + int los = RangingMeasurement.NLOS; + int measurementFocus = RangingMeasurement.MEASUREMENT_FOCUS_RANGE; + + RangingMeasurement.Builder builder = new RangingMeasurement.Builder(); + + builder.setStatus(status); + tryBuild(builder, false); + + builder.setElapsedRealtimeNanos(time); + tryBuild(builder, false); + + builder.setAngleOfArrivalMeasurement(angleMeasurement); + tryBuild(builder, false); + + builder.setDestinationAngleOfArrivalMeasurement(destinationAngleMeasurement); + tryBuild(builder, false); + + builder.setDistanceMeasurement(distanceMeasurement); + tryBuild(builder, false); + + builder.setRssiDbm(TEST_RSSI_DBM); + tryBuild(builder, false); + + builder.setRemoteDeviceAddress(address); + tryBuild(builder, true); + + builder.setLineOfSight(los); + tryBuild(builder, true); + + builder.setMeasurementFocus(measurementFocus); + RangingMeasurement measurement = tryBuild(builder, true); + + assertEquals(status, measurement.getStatus()); + assertEquals(address, measurement.getRemoteDeviceAddress()); + assertEquals(time, measurement.getElapsedRealtimeNanos()); + assertEquals(angleMeasurement, measurement.getAngleOfArrivalMeasurement()); + assertEquals(destinationAngleMeasurement, + measurement.getDestinationAngleOfArrivalMeasurement()); + assertEquals(distanceMeasurement, measurement.getDistanceMeasurement()); + assertEquals(los, measurement.getLineOfSight()); + assertEquals(measurementFocus, measurement.getMeasurementFocus()); + assertEquals(TEST_RSSI_DBM, measurement.getRssiDbm()); + } + + @Test + public void testInvalidRssi() { + RangingMeasurement.Builder builder = new RangingMeasurement.Builder(); + try { + builder.setRssiDbm(INVALID_RSSI_DBM); + fail("Expected RangingMeasurement.Builder.setRssiDbm() to fail"); + } catch (Exception e) { + assertTrue(e.getMessage().contains("Invalid")); + } + } + + private RangingMeasurement tryBuild(RangingMeasurement.Builder builder, + boolean expectSuccess) { + RangingMeasurement measurement = null; + try { + measurement = builder.build(); + if (!expectSuccess) { + fail("Expected RangingMeasurement.Builder.build() to fail"); + } + } catch (IllegalStateException e) { + if (expectSuccess) { + fail("Expected DistanceMeasurement.Builder.build() to succeed"); + } + } + return measurement; + } + + @Test + public void testParcel() { + Parcel parcel = Parcel.obtain(); + RangingMeasurement measurement = UwbTestUtils.getRangingMeasurement(); + measurement.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + RangingMeasurement fromParcel = RangingMeasurement.CREATOR.createFromParcel(parcel); + assertEquals(measurement, fromParcel); + } +} diff --git a/tests/cts/tests/src/android/uwb/cts/RangingReportTest.java b/tests/cts/tests/src/android/uwb/cts/RangingReportTest.java new file mode 100644 index 00000000..af4ebcb0 --- /dev/null +++ b/tests/cts/tests/src/android/uwb/cts/RangingReportTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.uwb.cts; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.os.Parcel; +import android.uwb.RangingMeasurement; +import android.uwb.RangingReport; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +/** + * Test of {@link RangingReport}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RangingReportTest { + + @Test + public void testBuilder() { + List<RangingMeasurement> measurements = UwbTestUtils.getRangingMeasurements(5); + + RangingReport.Builder builder = new RangingReport.Builder(); + builder.addMeasurements(measurements); + RangingReport report = tryBuild(builder, true); + verifyMeasurementsEqual(measurements, report.getMeasurements()); + builder = new RangingReport.Builder(); + for (RangingMeasurement measurement : measurements) { + builder.addMeasurement(measurement); + } + report = tryBuild(builder, true); + verifyMeasurementsEqual(measurements, report.getMeasurements()); + } + + private void verifyMeasurementsEqual(List<RangingMeasurement> expected, + List<RangingMeasurement> actual) { + assertEquals(expected.size(), actual.size()); + for (int i = 0; i < expected.size(); i++) { + assertEquals(expected.get(i), actual.get(i)); + } + } + + private RangingReport tryBuild(RangingReport.Builder builder, + boolean expectSuccess) { + RangingReport report = null; + try { + report = builder.build(); + if (!expectSuccess) { + fail("Expected RangingReport.Builder.build() to fail"); + } + } catch (IllegalStateException e) { + if (expectSuccess) { + fail("Expected RangingReport.Builder.build() to succeed"); + } + } + return report; + } + + @Test + public void testParcel() { + Parcel parcel = Parcel.obtain(); + RangingReport report = UwbTestUtils.getRangingReports(5); + report.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + RangingReport fromParcel = RangingReport.CREATOR.createFromParcel(parcel); + assertEquals(report, fromParcel); + } +} diff --git a/tests/cts/tests/src/android/uwb/cts/UwbAddressTest.java b/tests/cts/tests/src/android/uwb/cts/UwbAddressTest.java new file mode 100644 index 00000000..d2f42286 --- /dev/null +++ b/tests/cts/tests/src/android/uwb/cts/UwbAddressTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.uwb.cts; + +import static org.junit.Assert.assertEquals; + +import android.os.Parcel; +import android.uwb.UwbAddress; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test of {@link UwbAddress}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class UwbAddressTest { + + @Test + public void testFromBytes_Short() { + runFromBytes(UwbAddress.SHORT_ADDRESS_BYTE_LENGTH); + } + + @Test + public void testFromBytes_Extended() { + runFromBytes(UwbAddress.EXTENDED_ADDRESS_BYTE_LENGTH); + } + + private void runFromBytes(int len) { + byte[] addressBytes = getByteArray(len); + UwbAddress address = UwbAddress.fromBytes(addressBytes); + assertEquals(address.size(), len); + assertEquals(addressBytes, address.toBytes()); + } + + private byte[] getByteArray(int len) { + byte[] res = new byte[len]; + for (int i = 0; i < len; i++) { + res[i] = (byte) i; + } + return res; + } + + @Test + public void testParcel_Short() { + runParcel(true); + } + + @Test + public void testParcel_Extended() { + runParcel(false); + } + + private void runParcel(boolean useShortAddress) { + Parcel parcel = Parcel.obtain(); + UwbAddress address = UwbTestUtils.getUwbAddress(useShortAddress); + address.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + UwbAddress fromParcel = UwbAddress.CREATOR.createFromParcel(parcel); + assertEquals(address, fromParcel); + } +} diff --git a/tests/cts/tests/src/android/uwb/cts/UwbFrameworkInitializerTest.java b/tests/cts/tests/src/android/uwb/cts/UwbFrameworkInitializerTest.java new file mode 100644 index 00000000..ddd22c6c --- /dev/null +++ b/tests/cts/tests/src/android/uwb/cts/UwbFrameworkInitializerTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.uwb.cts; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +import android.content.Context; +import android.platform.test.annotations.AppModeFull; +import android.uwb.UwbFrameworkInitializer; +import android.uwb.UwbManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +/** + * Test of {@link UwbManager}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +@AppModeFull(reason = "Cannot get UwbManager in instant app mode") +public class UwbFrameworkInitializerTest { + private final Context mContext = InstrumentationRegistry.getContext(); + private UwbManager mUwbManager; + + @Before + public void setup() throws Exception { + mUwbManager = mContext.getSystemService(UwbManager.class); + assumeTrue(UwbTestUtils.isUwbSupported(mContext)); + assertThat(mUwbManager).isNotNull(); + } + + /** + * UwbFrameworkInitializer.registerServiceWrappers() should only be called by + * SystemServiceRegistry during boot up when Uwb is first initialized. Calling this API at + * any other time should throw an exception. + */ + @Test + public void testRegisterServiceWrappers_failsWhenCalledOutsideOfSystemServiceRegistry() { + try { + UwbFrameworkInitializer.registerServiceWrappers(); + fail("Expected exception when calling " + + "UwbFrameworkInitializer.registerServiceWrappers() outside of " + + "SystemServiceRegistry!"); + } catch (IllegalStateException expected) { } + } +} diff --git a/tests/cts/tests/src/android/uwb/cts/UwbManagerTest.java b/tests/cts/tests/src/android/uwb/cts/UwbManagerTest.java new file mode 100644 index 00000000..731d8944 --- /dev/null +++ b/tests/cts/tests/src/android/uwb/cts/UwbManagerTest.java @@ -0,0 +1,1175 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.uwb.cts; + +import static android.Manifest.permission.UWB_PRIVILEGED; +import static android.Manifest.permission.UWB_RANGING; +import static android.uwb.UwbManager.AdapterStateCallback.STATE_DISABLED; +import static android.uwb.UwbManager.AdapterStateCallback.STATE_ENABLED_ACTIVE; +import static android.uwb.UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.UiAutomation; +import android.content.AttributionSource; +import android.content.Context; +import android.content.ContextParams; +import android.os.CancellationSignal; +import android.os.PersistableBundle; +import android.os.Process; +import android.permission.PermissionManager; +import android.platform.test.annotations.AppModeFull; +import android.util.Log; +import android.uwb.RangingReport; +import android.uwb.RangingSession; +import android.uwb.UwbAddress; +import android.uwb.UwbManager; + +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.compatibility.common.util.CddTest; +import com.android.compatibility.common.util.ShellIdentityUtils; + +import com.google.uwb.support.fira.FiraControleeParams; +import com.google.uwb.support.fira.FiraOpenSessionParams; +import com.google.uwb.support.fira.FiraParams; +import com.google.uwb.support.fira.FiraProtocolVersion; +import com.google.uwb.support.fira.FiraSpecificationParams; +import com.google.uwb.support.multichip.ChipInfoParams; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.EnumSet; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Test of {@link UwbManager}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +@AppModeFull(reason = "Cannot get UwbManager in instant app mode") +public class UwbManagerTest { + private static final String TAG = "UwbManagerTest"; + + private final Context mContext = InstrumentationRegistry.getContext(); + private UwbManager mUwbManager; + private String mDefaultChipId; + public static final int UWB_SESSION_STATE_IDLE = 0x03; + public static final byte DEVICE_STATE_ACTIVE = 0x02; + public static final int REASON_STATE_CHANGE_WITH_SESSION_MANAGEMENT_COMMANDS = 0x00; + + @Before + public void setup() throws Exception { + mUwbManager = mContext.getSystemService(UwbManager.class); + assumeTrue(UwbTestUtils.isUwbSupported(mContext)); + assertThat(mUwbManager).isNotNull(); + + // Ensure UWB is toggled on. + ShellIdentityUtils.invokeWithShellPermissions(() -> { + if (!mUwbManager.isUwbEnabled()) { + try { + setUwbEnabledAndWaitForCompletion(true); + } catch (Exception e) { + fail("Exception while processing UWB toggle " + e); + } + } + mDefaultChipId = mUwbManager.getDefaultChipId(); + }); + } + + // Should be invoked with shell permissions. + private void setUwbEnabledAndWaitForCompletion(boolean enabled) throws Exception { + CountDownLatch countDownLatch = new CountDownLatch(1); + int adapterState = enabled ? STATE_ENABLED_INACTIVE : STATE_DISABLED; + AdapterStateCallback adapterStateCallback = + new AdapterStateCallback(countDownLatch, adapterState); + try { + mUwbManager.registerAdapterStateCallback( + Executors.newSingleThreadExecutor(), adapterStateCallback); + mUwbManager.setUwbEnabled(enabled); + assertThat(countDownLatch.await(2, TimeUnit.SECONDS)).isTrue(); + assertThat(mUwbManager.isUwbEnabled()).isEqualTo(enabled); + assertThat(adapterStateCallback.state).isEqualTo(adapterState); + } finally { + mUwbManager.unregisterAdapterStateCallback(adapterStateCallback); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testGetSpecificationInfo() { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + try { + // Needs UWB_PRIVILEGED permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + PersistableBundle persistableBundle = mUwbManager.getSpecificationInfo(); + assertThat(persistableBundle).isNotNull(); + assertThat(persistableBundle.isEmpty()).isFalse(); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testGetSpecificationInfoWithChipId() { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + try { + // Needs UWB_PRIVILEGED permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + PersistableBundle persistableBundle = + mUwbManager.getSpecificationInfo(mDefaultChipId); + assertThat(persistableBundle).isNotNull(); + assertThat(persistableBundle.isEmpty()).isFalse(); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testGetChipInfos() { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + try { + // Needs UWB_PRIVILEGED permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + List<PersistableBundle> chipInfos = mUwbManager.getChipInfos(); + assertThat(chipInfos).hasSize(1); + ChipInfoParams chipInfoParams = ChipInfoParams.fromBundle(chipInfos.get(0)); + assertThat(chipInfoParams.getChipId()).isEqualTo(mDefaultChipId); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testGetSpecificationInfoWithInvalidChipId() { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + try { + // Needs UWB_PRIVILEGED permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + assertThrows(IllegalArgumentException.class, + () -> mUwbManager.getSpecificationInfo("invalidChipId")); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testGetSpecificationInfoWithoutUwbPrivileged() { + try { + mUwbManager.getSpecificationInfo(); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testGetSpecificationInfoWithChipIdWithoutUwbPrivileged() { + try { + mUwbManager.getSpecificationInfo(mDefaultChipId); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } + } + + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testElapsedRealtimeResolutionNanos() { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + try { + // Needs UWB_PRIVILEGED permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + assertThat(mUwbManager.elapsedRealtimeResolutionNanos() >= 0L).isTrue(); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testElapsedRealtimeResolutionNanosWithChipId() { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + try { + // Needs UWB_PRIVILEGED permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + assertThat(mUwbManager.elapsedRealtimeResolutionNanos(mDefaultChipId) >= 0L) + .isTrue(); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testElapsedRealtimeResolutionNanosWithInvalidChipId() { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + try { + // Needs UWB_PRIVILEGED permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + assertThrows(IllegalArgumentException.class, + () -> mUwbManager.elapsedRealtimeResolutionNanos("invalidChipId")); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testElapsedRealtimeResolutionNanosWithoutUwbPrivileged() { + try { + mUwbManager.elapsedRealtimeResolutionNanos(); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testElapsedRealtimeResolutionNanosWithChipIdWithoutUwbPrivileged() { + try { + mUwbManager.elapsedRealtimeResolutionNanos(mDefaultChipId); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testAddServiceProfileWithoutUwbPrivileged() { + try { + mUwbManager.addServiceProfile(new PersistableBundle()); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testRemoveServiceProfileWithoutUwbPrivileged() { + try { + mUwbManager.removeServiceProfile(new PersistableBundle()); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } + } + + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testGetAllServiceProfilesWithoutUwbPrivileged() { + try { + mUwbManager.getAllServiceProfiles(); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testGetAdfProvisioningAuthoritiesWithoutUwbPrivileged() { + try { + mUwbManager.getAdfProvisioningAuthorities(new PersistableBundle()); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testGetAdfCertificateInfoWithoutUwbPrivileged() { + try { + mUwbManager.getAdfCertificateInfo(new PersistableBundle()); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testGetChipInfosWithoutUwbPrivileged() { + try { + mUwbManager.getChipInfos(); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testSendVendorUciWithoutUwbPrivileged() { + try { + mUwbManager.sendVendorUciMessage(10, 0, new byte[0]); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } + } + + private class AdfProvisionStateCallback extends UwbManager.AdfProvisionStateCallback { + private final CountDownLatch mCountDownLatch; + + public boolean onSuccessCalled; + public boolean onFailedCalled; + + AdfProvisionStateCallback(@NonNull CountDownLatch countDownLatch) { + mCountDownLatch = countDownLatch; + } + + @Override + public void onProfileAdfsProvisioned(@NonNull PersistableBundle params) { + onSuccessCalled = true; + mCountDownLatch.countDown(); + } + + @Override + public void onProfileAdfsProvisionFailed(int reason, @NonNull PersistableBundle params) { + onFailedCalled = true; + mCountDownLatch.countDown(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testProvisionProfileAdfByScriptWithoutUwbPrivileged() { + CountDownLatch countDownLatch = new CountDownLatch(1); + AdfProvisionStateCallback adfProvisionStateCallback = + new AdfProvisionStateCallback(countDownLatch); + try { + mUwbManager.provisionProfileAdfByScript( + new PersistableBundle(), + Executors.newSingleThreadExecutor(), + adfProvisionStateCallback); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testRemoveProfileAdfWithoutUwbPrivileged() { + try { + mUwbManager.removeProfileAdf(new PersistableBundle()); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } + } + + private class UwbVendorUciCallback implements UwbManager.UwbVendorUciCallback { + private final CountDownLatch mRspCountDownLatch; + private final CountDownLatch mNtfCountDownLatch; + + public int gid; + public int oid; + public byte[] payload; + + UwbVendorUciCallback( + @NonNull CountDownLatch rspCountDownLatch, + @NonNull CountDownLatch ntfCountDownLatch) { + mRspCountDownLatch = rspCountDownLatch; + mNtfCountDownLatch = ntfCountDownLatch; + } + + @Override + public void onVendorUciResponse(int gid, int oid, byte[] payload) { + this.gid = gid; + this.oid = oid; + this.payload = payload; + mRspCountDownLatch.countDown(); + } + + @Override + public void onVendorUciNotification(int gid, int oid, byte[] payload) { + this.gid = gid; + this.oid = oid; + this.payload = payload; + mNtfCountDownLatch.countDown(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testRegisterVendorUciCallbackWithoutUwbPrivileged() { + UwbManager.UwbVendorUciCallback cb = + new UwbVendorUciCallback(new CountDownLatch(1), new CountDownLatch(1)); + try { + mUwbManager.registerUwbVendorUciCallback( + Executors.newSingleThreadExecutor(), cb); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testUnregisterVendorUciCallbackWithoutUwbPrivileged() { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + UwbManager.UwbVendorUciCallback cb = + new UwbVendorUciCallback(new CountDownLatch(1), new CountDownLatch(1)); + try { + // Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + mUwbManager.registerUwbVendorUciCallback( + Executors.newSingleThreadExecutor(), cb); + } catch (SecurityException e) { + /* pass */ + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + try { + mUwbManager.unregisterUwbVendorUciCallback(cb); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } + try { + uiAutomation.adoptShellPermissionIdentity(); + mUwbManager.unregisterUwbVendorUciCallback(cb); + /* pass */ + } catch (SecurityException e) { + /* fail */ + fail(); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testInvalidCallbackUnregisterVendorUciCallback() { + UwbManager.UwbVendorUciCallback cb = + new UwbVendorUciCallback(new CountDownLatch(1), new CountDownLatch(1)); + try { + mUwbManager.registerUwbVendorUciCallback( + Executors.newSingleThreadExecutor(), cb); + } catch (SecurityException e) { + /* registration failed */ + } + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + try { + // Needs UWB_PRIVILEGED permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + mUwbManager.unregisterUwbVendorUciCallback(cb); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + private class RangingSessionCallback implements RangingSession.Callback { + private CountDownLatch mCtrlCountDownLatch; + private CountDownLatch mResultCountDownLatch; + + public boolean onOpenedCalled; + public boolean onOpenFailedCalled; + public boolean onStartedCalled; + public boolean onStartFailedCalled; + public boolean onReconfiguredCalled; + public boolean onReconfiguredFailedCalled; + public boolean onClosedCalled; + public RangingSession rangingSession; + public RangingReport rangingReport; + + RangingSessionCallback( + @NonNull CountDownLatch ctrlCountDownLatch) { + this(ctrlCountDownLatch, null /* resultCountDownLaynch */); + } + + RangingSessionCallback( + @NonNull CountDownLatch ctrlCountDownLatch, + @Nullable CountDownLatch resultCountDownLatch) { + mCtrlCountDownLatch = ctrlCountDownLatch; + mResultCountDownLatch = resultCountDownLatch; + } + + public void replaceCtrlCountDownLatch(@NonNull CountDownLatch countDownLatch) { + mCtrlCountDownLatch = countDownLatch; + } + + public void onOpened(@NonNull RangingSession session) { + onOpenedCalled = true; + rangingSession = session; + mCtrlCountDownLatch.countDown(); + } + + public void onOpenFailed(int reason, @NonNull PersistableBundle params) { + onOpenFailedCalled = true; + mCtrlCountDownLatch.countDown(); + } + + public void onStarted(@NonNull PersistableBundle sessionInfo) { + onStartedCalled = true; + mCtrlCountDownLatch.countDown(); + } + + public void onStartFailed(int reason, @NonNull PersistableBundle params) { + onStartFailedCalled = true; + mCtrlCountDownLatch.countDown(); + } + + public void onReconfigured(@NonNull PersistableBundle params) { + onReconfiguredCalled = true; + mCtrlCountDownLatch.countDown(); + } + + public void onReconfigureFailed(int reason, @NonNull PersistableBundle params) { + onReconfiguredFailedCalled = true; + mCtrlCountDownLatch.countDown(); + } + + public void onStopped(int reason, @NonNull PersistableBundle parameters) { } + + public void onStopFailed(int reason, @NonNull PersistableBundle params) { } + + public void onClosed(int reason, @NonNull PersistableBundle parameters) { + onClosedCalled = true; + mCtrlCountDownLatch.countDown(); + } + + public void onReportReceived(@NonNull RangingReport rangingReport) { + if (mResultCountDownLatch != null) { + this.rangingReport = rangingReport; + mResultCountDownLatch.countDown(); + } + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testOpenRangingSessionWithInvalidChipId() { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + CountDownLatch countDownLatch = new CountDownLatch(1); + RangingSessionCallback rangingSessionCallback = new RangingSessionCallback(countDownLatch); + try { + // Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + // Try to start a ranging session with invalid params, should fail. + assertThrows(IllegalArgumentException.class, () -> mUwbManager.openRangingSession( + new PersistableBundle(), + Executors.newSingleThreadExecutor(), + rangingSessionCallback, + "invalidChipId")); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testOpenRangingSessionWithChipIdWithBadParams() throws Exception { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + CancellationSignal cancellationSignal = null; + CountDownLatch countDownLatch = new CountDownLatch(1); + RangingSessionCallback rangingSessionCallback = new RangingSessionCallback(countDownLatch); + try { + // Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + // Try to start a ranging session with invalid params, should fail. + cancellationSignal = mUwbManager.openRangingSession( + new PersistableBundle(), + Executors.newSingleThreadExecutor(), + rangingSessionCallback, + mDefaultChipId); + // Wait for the on start failed callback. + assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(rangingSessionCallback.onOpenedCalled).isFalse(); + assertThat(rangingSessionCallback.onOpenFailedCalled).isTrue(); + } finally { + if (cancellationSignal != null) { + cancellationSignal.cancel(); + } + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testOpenRangingSessionWithInvalidChipIdWithBadParams() throws Exception { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + CancellationSignal cancellationSignal = null; + CountDownLatch countDownLatch = new CountDownLatch(1); + RangingSessionCallback rangingSessionCallback = new RangingSessionCallback(countDownLatch); + try { + // Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + // Try to start a ranging session with invalid params, should fail. + cancellationSignal = mUwbManager.openRangingSession( + new PersistableBundle(), + Executors.newSingleThreadExecutor(), + rangingSessionCallback, + mDefaultChipId); + // Wait for the on start failed callback. + assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(rangingSessionCallback.onOpenedCalled).isFalse(); + assertThat(rangingSessionCallback.onOpenFailedCalled).isTrue(); + } finally { + if (cancellationSignal != null) { + cancellationSignal.cancel(); + } + uiAutomation.dropShellPermissionIdentity(); + } + } + + /** + * Simulates the app holding UWB_RANGING permission, but not UWB_PRIVILEGED. + */ + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testOpenRangingSessionWithoutUwbPrivileged() { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + try { + // Only hold UWB_RANGING permission + uiAutomation.adoptShellPermissionIdentity(UWB_RANGING); + mUwbManager.openRangingSession(new PersistableBundle(), + Executors.newSingleThreadExecutor(), + new RangingSessionCallback(new CountDownLatch(1))); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testOpenRangingSessionWithChipIdWithoutUwbPrivileged() { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + try { + // Only hold UWB_RANGING permission + uiAutomation.adoptShellPermissionIdentity(UWB_RANGING); + mUwbManager.openRangingSession(new PersistableBundle(), + Executors.newSingleThreadExecutor(), + new RangingSessionCallback(new CountDownLatch(1)), + mDefaultChipId); + // should fail if the call was successful without UWB_PRIVILEGED permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + /** + * Simulates the app holding UWB_PRIVILEGED permission, but not UWB_RANGING. + */ + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testOpenRangingSessionWithoutUwbRanging() { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + try { + // Needs UWB_PRIVILEGED permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(UWB_PRIVILEGED); + mUwbManager.openRangingSession(new PersistableBundle(), + Executors.newSingleThreadExecutor(), + new RangingSessionCallback(new CountDownLatch(1))); + // should fail if the call was successful without UWB_RANGING permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testOpenRangingSessionWithChipIdWithoutUwbRanging() { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + try { + // Needs UWB_PRIVILEGED permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(UWB_PRIVILEGED); + mUwbManager.openRangingSession(new PersistableBundle(), + Executors.newSingleThreadExecutor(), + new RangingSessionCallback(new CountDownLatch(1)), + mDefaultChipId); + // should fail if the call was successful without UWB_RANGING permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + private AttributionSource getShellAttributionSourceWithRenouncedPermissions( + @Nullable Set<String> renouncedPermissions) { + try { + AttributionSource shellAttributionSource = + new AttributionSource.Builder(Process.SHELL_UID) + .setPackageName("com.android.shell") + .setRenouncedPermissions(renouncedPermissions) + .build(); + PermissionManager permissionManager = + mContext.getSystemService(PermissionManager.class); + permissionManager.registerAttributionSource(shellAttributionSource); + return shellAttributionSource; + } catch (SecurityException e) { + fail("Failed to create shell attribution source" + e); + return null; + } + } + + private Context createShellContextWithRenouncedPermissionsAndAttributionSource( + @Nullable Set<String> renouncedPermissions) { + return mContext.createContext(new ContextParams.Builder() + .setRenouncedPermissions(renouncedPermissions) + .setNextAttributionSource( + getShellAttributionSourceWithRenouncedPermissions(renouncedPermissions)) + .build()); + } + + /** + * Simulates the calling app holding UWB_PRIVILEGED permission and UWB_RANGING permission, but + * the proxied app not holding UWB_RANGING permission. + */ + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-5"}) + public void testOpenRangingSessionWithoutUwbRangingInNextAttributeSource() { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + try { + // Only hold UWB_PRIVILEGED permission + uiAutomation.adoptShellPermissionIdentity(); + Context shellContextWithUwbRangingRenounced = + createShellContextWithRenouncedPermissionsAndAttributionSource( + Set.of(UWB_RANGING)); + UwbManager uwbManagerWithUwbRangingRenounced = + shellContextWithUwbRangingRenounced.getSystemService(UwbManager.class); + uwbManagerWithUwbRangingRenounced.openRangingSession(new PersistableBundle(), + Executors.newSingleThreadExecutor(), + new RangingSessionCallback(new CountDownLatch(1))); + // should fail if the call was successful without UWB_RANGING permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-5"}) + public void testOpenRangingSessionWithChipIdWithoutUwbRangingInNextAttributeSource() { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + try { + // Only hold UWB_PRIVILEGED permission + uiAutomation.adoptShellPermissionIdentity(); + Context shellContextWithUwbRangingRenounced = + createShellContextWithRenouncedPermissionsAndAttributionSource( + Set.of(UWB_RANGING)); + UwbManager uwbManagerWithUwbRangingRenounced = + shellContextWithUwbRangingRenounced.getSystemService(UwbManager.class); + uwbManagerWithUwbRangingRenounced.openRangingSession(new PersistableBundle(), + Executors.newSingleThreadExecutor(), + new RangingSessionCallback(new CountDownLatch(1)), + mDefaultChipId); + // should fail if the call was successful without UWB_RANGING permission. + fail(); + } catch (SecurityException e) { + /* pass */ + Log.i(TAG, "Failed with expected security exception: " + e); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-5"}) + public void testFiraRangingSession() throws Exception { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + CancellationSignal cancellationSignal = null; + CountDownLatch countDownLatch = new CountDownLatch(1); + CountDownLatch resultCountDownLatch = new CountDownLatch(1); + RangingSessionCallback rangingSessionCallback = + new RangingSessionCallback(countDownLatch, resultCountDownLatch); + FiraOpenSessionParams firaOpenSessionParams = new FiraOpenSessionParams.Builder() + .setProtocolVersion(new FiraProtocolVersion(1, 1)) + .setSessionId(1) + .setStsConfig(FiraParams.STS_CONFIG_STATIC) + .setVendorId(new byte[]{0x5, 0x6}) + .setStaticStsIV(new byte[]{0x5, 0x6, 0x9, 0xa, 0x4, 0x6}) + .setDeviceType(FiraParams.RANGING_DEVICE_TYPE_CONTROLLER) + .setDeviceRole(FiraParams.RANGING_DEVICE_ROLE_INITIATOR) + .setMultiNodeMode(FiraParams.MULTI_NODE_MODE_UNICAST) + .setDeviceAddress(UwbAddress.fromBytes(new byte[] {0x5, 6})) + .setDestAddressList(List.of(UwbAddress.fromBytes(new byte[] {0x5, 6}))) + .build(); + try { + // Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + // Try to start a ranging session with invalid params, should fail. + cancellationSignal = mUwbManager.openRangingSession( + firaOpenSessionParams.toBundle(), + Executors.newSingleThreadExecutor(), + rangingSessionCallback, + mDefaultChipId); + // Wait for the on opened callback. + assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(rangingSessionCallback.onOpenedCalled).isTrue(); + assertThat(rangingSessionCallback.onOpenFailedCalled).isFalse(); + assertThat(rangingSessionCallback.rangingSession).isNotNull(); + + countDownLatch = new CountDownLatch(1); + rangingSessionCallback.replaceCtrlCountDownLatch(countDownLatch); + rangingSessionCallback.rangingSession.start(new PersistableBundle()); + // Wait for the on started callback. + assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(rangingSessionCallback.onStartedCalled).isTrue(); + assertThat(rangingSessionCallback.onStartFailedCalled).isFalse(); + + // Wait for the on ranging report callback. + assertThat(resultCountDownLatch.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(rangingSessionCallback.rangingReport).isNotNull(); + + // Check the UWB state. + assertThat(mUwbManager.getAdapterState()).isEqualTo(STATE_ENABLED_ACTIVE); + + // Stop ongoing session. + rangingSessionCallback.rangingSession.stop(); + } finally { + if (cancellationSignal != null) { + countDownLatch = new CountDownLatch(1); + rangingSessionCallback.replaceCtrlCountDownLatch(countDownLatch); + + // Close session. + cancellationSignal.cancel(); + + // Wait for the on closed callback. + assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(rangingSessionCallback.onClosedCalled).isTrue(); + } + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-5"}) + public void testFiraRangingSessionWithProvisionedSTS() throws Exception { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + // Needs UWB_PRIVILEGED permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + CancellationSignal cancellationSignal = null; + CountDownLatch countDownLatch = new CountDownLatch(1); + CountDownLatch resultCountDownLatch = new CountDownLatch(1); + RangingSessionCallback rangingSessionCallback = + new RangingSessionCallback(countDownLatch, resultCountDownLatch); + PersistableBundle bundle = mUwbManager.getSpecificationInfo(); + if (bundle.keySet().contains(FiraParams.PROTOCOL_NAME)) { + bundle = requireNonNull(bundle.getPersistableBundle(FiraParams.PROTOCOL_NAME)); + } + FiraSpecificationParams params = + FiraSpecificationParams.fromBundle(bundle); + EnumSet<FiraParams.StsCapabilityFlag> stsCapabilities = EnumSet.of( + FiraParams.StsCapabilityFlag.HAS_STATIC_STS_SUPPORT, + FiraParams.StsCapabilityFlag.HAS_PROVISIONED_STS_SUPPORT); + assumeTrue(params.getStsCapabilities() == stsCapabilities); + + FiraOpenSessionParams firaOpenSessionParams = new FiraOpenSessionParams.Builder() + .setProtocolVersion(new FiraProtocolVersion(1, 1)) + .setSessionId(1) + .setStsConfig(FiraParams.STS_CONFIG_PROVISIONED) + .setSessionKey(new byte[]{ + 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, + 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8 + }) + .setSubsessionKey(new byte[]{ + 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, + 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8 + }) + .setDeviceType(FiraParams.RANGING_DEVICE_TYPE_CONTROLLER) + .setDeviceRole(FiraParams.RANGING_DEVICE_ROLE_INITIATOR) + .setMultiNodeMode(FiraParams.MULTI_NODE_MODE_UNICAST) + .setDeviceAddress(UwbAddress.fromBytes(new byte[] {0x5, 6})) + .setDestAddressList(List.of(UwbAddress.fromBytes(new byte[] {0x5, 6}))) + .build(); + try { + // Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + cancellationSignal = mUwbManager.openRangingSession( + firaOpenSessionParams.toBundle(), + Executors.newSingleThreadExecutor(), + rangingSessionCallback, + mDefaultChipId); + // Wait for the on opened callback. + assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(rangingSessionCallback.onOpenedCalled).isTrue(); + assertThat(rangingSessionCallback.onOpenFailedCalled).isFalse(); + assertThat(rangingSessionCallback.rangingSession).isNotNull(); + + countDownLatch = new CountDownLatch(1); + rangingSessionCallback.replaceCtrlCountDownLatch(countDownLatch); + rangingSessionCallback.rangingSession.start(new PersistableBundle()); + // Wait for the on started callback. + assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(rangingSessionCallback.onStartedCalled).isTrue(); + assertThat(rangingSessionCallback.onStartFailedCalled).isFalse(); + + countDownLatch = new CountDownLatch(1); + rangingSessionCallback.replaceCtrlCountDownLatch(countDownLatch); + UwbAddress uwbAddress = UwbAddress.fromBytes(new byte[] {0x5, 5}); + rangingSessionCallback.rangingSession.addControlee( + new FiraControleeParams.Builder() + .setAddressList(new UwbAddress[] {uwbAddress}) + .setSubSessionIdList(new int[] {1}) + .build().toBundle() + ); + // Wait for the on reconfigured callback. + assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(rangingSessionCallback.onReconfiguredCalled).isTrue(); + assertThat(rangingSessionCallback.onReconfiguredFailedCalled).isFalse(); + + // Wait for the on ranging report callback. + assertThat(resultCountDownLatch.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(rangingSessionCallback.rangingReport).isNotNull(); + + // Check the UWB state. + assertThat(mUwbManager.getAdapterState()).isEqualTo(STATE_ENABLED_ACTIVE); + + // Stop ongoing session. + rangingSessionCallback.rangingSession.stop(); + } finally { + if (cancellationSignal != null) { + countDownLatch = new CountDownLatch(1); + rangingSessionCallback.replaceCtrlCountDownLatch(countDownLatch); + + // Close session. + cancellationSignal.cancel(); + + // Wait for the on closed callback. + assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(rangingSessionCallback.onClosedCalled).isTrue(); + } + uiAutomation.dropShellPermissionIdentity(); + } + } + + private class AdapterStateCallback implements UwbManager.AdapterStateCallback { + private final CountDownLatch mCountDownLatch; + private final Integer mWaitForState; + public int state; + public int reason; + + AdapterStateCallback(@NonNull CountDownLatch countDownLatch, + @Nullable Integer waitForState) { + mCountDownLatch = countDownLatch; + mWaitForState = waitForState; + } + + public void onStateChanged(int state, int reason) { + this.state = state; + this.reason = reason; + if (mWaitForState != null) { + if (mWaitForState == state) { + mCountDownLatch.countDown(); + } + } else { + mCountDownLatch.countDown(); + } + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-4"}) + public void testUwbStateToggle() throws Exception { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + try { + // Needs UWB_PRIVILEGED permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + assertThat(mUwbManager.isUwbEnabled()).isTrue(); + assertThat(mUwbManager.getAdapterState()).isEqualTo(STATE_ENABLED_INACTIVE); + // Toggle the state + setUwbEnabledAndWaitForCompletion(false); + assertThat(mUwbManager.getAdapterState()).isEqualTo(STATE_DISABLED); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testSendVendorUciMessageVendorGid() throws Exception { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + CountDownLatch rspCountDownLatch = new CountDownLatch(1); + CountDownLatch ntfCountDownLatch = new CountDownLatch(1); + UwbVendorUciCallback cb = + new UwbVendorUciCallback(rspCountDownLatch, ntfCountDownLatch); + try { + // Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + mUwbManager.registerUwbVendorUciCallback( + Executors.newSingleThreadExecutor(), cb); + + // Send random payload with a vendor gid. + byte[] payload = new byte[100]; + new Random().nextBytes(payload); + int gid = 9; + int oid = 1; + mUwbManager.sendVendorUciMessage(gid, oid, payload); + + // Wait for response. + assertThat(rspCountDownLatch.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(cb.gid).isEqualTo(gid); + assertThat(cb.oid).isEqualTo(oid); + assertThat(cb.payload).isNotEmpty(); + } catch (SecurityException e) { + /* pass */ + } finally { + mUwbManager.unregisterUwbVendorUciCallback(cb); + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testSendVendorUciMessageFiraGid() throws Exception { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + CountDownLatch rspCountDownLatch = new CountDownLatch(1); + CountDownLatch ntfCountDownLatch = new CountDownLatch(1); + UwbVendorUciCallback cb = + new UwbVendorUciCallback(rspCountDownLatch, ntfCountDownLatch); + try { + // Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + mUwbManager.registerUwbVendorUciCallback( + Executors.newSingleThreadExecutor(), cb); + + // Send random payload with a FIRA gid. + byte[] payload = new byte[100]; + new Random().nextBytes(payload); + int gid = 1; + int oid = 3; + mUwbManager.sendVendorUciMessage(gid, oid, payload); + + // Wait for response. + assertThat(rspCountDownLatch.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(cb.gid).isEqualTo(gid); + assertThat(cb.oid).isEqualTo(oid); + assertThat(cb.payload).isNotEmpty(); + } catch (SecurityException e) { + /* pass */ + } finally { + mUwbManager.unregisterUwbVendorUciCallback(cb); + uiAutomation.dropShellPermissionIdentity(); + } + } + + @Test + @CddTest(requirements = {"7.3.13/C-1-1,C-1-2"}) + public void testSendVendorUciMessageWithFragmentedPackets() throws Exception { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + CountDownLatch rspCountDownLatch = new CountDownLatch(1); + CountDownLatch ntfCountDownLatch = new CountDownLatch(1); + UwbVendorUciCallback cb = + new UwbVendorUciCallback(rspCountDownLatch, ntfCountDownLatch); + try { + // Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell. + uiAutomation.adoptShellPermissionIdentity(); + mUwbManager.registerUwbVendorUciCallback( + Executors.newSingleThreadExecutor(), cb); + + // Send random payload > 255 bytes with a vendor gid. + byte[] payload = new byte[400]; + new Random().nextBytes(payload); + int gid = 9; + int oid = 1; + mUwbManager.sendVendorUciMessage(gid, oid, payload); + + // Wait for response. + assertThat(rspCountDownLatch.await(1, TimeUnit.SECONDS)).isTrue(); + assertThat(cb.gid).isEqualTo(gid); + assertThat(cb.oid).isEqualTo(oid); + assertThat(cb.payload).isNotEmpty(); + } catch (SecurityException e) { + /* pass */ + } finally { + mUwbManager.unregisterUwbVendorUciCallback(cb); + uiAutomation.dropShellPermissionIdentity(); + } + } +} diff --git a/tests/cts/tests/src/android/uwb/cts/UwbTestUtils.java b/tests/cts/tests/src/android/uwb/cts/UwbTestUtils.java new file mode 100644 index 00000000..150823a7 --- /dev/null +++ b/tests/cts/tests/src/android/uwb/cts/UwbTestUtils.java @@ -0,0 +1,124 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.uwb.cts; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.PersistableBundle; +import android.os.SystemClock; +import android.uwb.AngleMeasurement; +import android.uwb.AngleOfArrivalMeasurement; +import android.uwb.DistanceMeasurement; +import android.uwb.RangingMeasurement; +import android.uwb.RangingReport; +import android.uwb.UwbAddress; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +public class UwbTestUtils { + private UwbTestUtils() {} + + public static boolean isUwbSupported(Context context) { + PackageManager packageManager = context.getPackageManager(); + return packageManager.hasSystemFeature(PackageManager.FEATURE_UWB); + } + + public static AngleMeasurement getAngleMeasurement() { + return new AngleMeasurement( + getDoubleInRange(-Math.PI, Math.PI), + getDoubleInRange(0, Math.PI), + getDoubleInRange(0, 1)); + } + + public static AngleOfArrivalMeasurement getAngleOfArrivalMeasurement() { + return new AngleOfArrivalMeasurement.Builder(getAngleMeasurement()) + .setAltitude(getAngleMeasurement()) + .build(); + } + + public static DistanceMeasurement getDistanceMeasurement() { + return new DistanceMeasurement.Builder() + .setMeters(getDoubleInRange(0, 100)) + .setErrorMeters(getDoubleInRange(0, 10)) + .setConfidenceLevel(getDoubleInRange(0, 1)) + .build(); + } + + public static RangingMeasurement getRangingMeasurement() { + return getRangingMeasurement(getUwbAddress(false)); + } + + public static PersistableBundle getTestRangingMetadata() { + PersistableBundle bundle = new PersistableBundle(); + bundle.putInt("TEST_KEY", 1); + return bundle; + } + + public static RangingMeasurement getRangingMeasurement(UwbAddress address) { + return new RangingMeasurement.Builder() + .setDistanceMeasurement(getDistanceMeasurement()) + .setAngleOfArrivalMeasurement(getAngleOfArrivalMeasurement()) + .setDestinationAngleOfArrivalMeasurement(getAngleOfArrivalMeasurement()) + .setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()) + .setRemoteDeviceAddress(address != null ? address : getUwbAddress(false)) + .setStatus(RangingMeasurement.RANGING_STATUS_SUCCESS) + .setLineOfSight(RangingMeasurement.NLOS) + .setMeasurementFocus(RangingMeasurement.MEASUREMENT_FOCUS_RANGE) + .setRssiDbm(-85) + .build(); + } + + public static List<RangingMeasurement> getRangingMeasurements(int num) { + List<RangingMeasurement> result = new ArrayList<>(); + for (int i = 0; i < num; i++) { + result.add(getRangingMeasurement()); + } + return result; + } + + public static RangingReport getRangingReports(int numMeasurements) { + RangingReport.Builder builder = new RangingReport.Builder(); + for (int i = 0; i < numMeasurements; i++) { + builder.addMeasurement(getRangingMeasurement()); + } + return builder.build(); + } + + private static double getDoubleInRange(double min, double max) { + return min + (max - min) * Math.random(); + } + + public static UwbAddress getUwbAddress(boolean isShortAddress) { + byte[] addressBytes = new byte[isShortAddress ? UwbAddress.SHORT_ADDRESS_BYTE_LENGTH : + UwbAddress.EXTENDED_ADDRESS_BYTE_LENGTH]; + for (int i = 0; i < addressBytes.length; i++) { + addressBytes[i] = (byte) getDoubleInRange(1, 255); + } + return UwbAddress.fromBytes(addressBytes); + } + + public static Executor getExecutor() { + return new Executor() { + @Override + public void execute(Runnable command) { + command.run(); + } + }; + } +} |