blob: 642727bc8a1aca625d6b3bf70a8303951dce61f3 [file] [log] [blame]
/*
* 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 android.uwb.UwbManager.MESSAGE_TYPE_COMMAND;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
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 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.RangingMeasurement;
import android.uwb.RangingReport;
import android.uwb.RangingSession;
import android.uwb.UwbActivityEnergyInfo;
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.android.modules.utils.build.SdkLevel;
import com.google.uwb.support.dltdoa.DlTDoAMeasurement;
import com.google.uwb.support.dltdoa.DlTDoARangingRoundsUpdate;
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.FiraPoseUpdateParams;
import com.google.uwb.support.fira.FiraProtocolVersion;
import com.google.uwb.support.fira.FiraRangingReconfigureParams;
import com.google.uwb.support.fira.FiraSpecificationParams;
import com.google.uwb.support.multichip.ChipInfoParams;
import com.google.uwb.support.oemextension.DeviceStatus;
import com.google.uwb.support.oemextension.RangingReportMetadata;
import com.google.uwb.support.oemextension.SessionStatus;
import org.junit.Assume;
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;
import java.util.function.Consumer;
/**
* 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 onStoppedCalled;
public boolean onClosedCalled;
public boolean onControleeAddCalled;
public boolean onControleeAddFailedCalled;
public boolean onControleeRemoveCalled;
public boolean onControleeRemoveFailedCalled;
public boolean onUpdateDtTagStatusCalled;
public boolean onDataSentCalled;
public boolean onDataSendFailedCalled;
public boolean onPauseCalled;
public boolean onPauseFailedCalled;
public boolean onResumeCalled;
public boolean onResumeFailedCalled;
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 replaceResultCountDownLatch(@NonNull CountDownLatch countDownLatch) {
mResultCountDownLatch = 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) {
onStoppedCalled = true;
mCtrlCountDownLatch.countDown();
}
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();
}
}
public void onControleeAdded(PersistableBundle params) {
onControleeAddCalled = true;
mCtrlCountDownLatch.countDown();
}
public void onControleeAddFailed(int reason, PersistableBundle params) {
onControleeAddFailedCalled = true;
mCtrlCountDownLatch.countDown();
}
public void onControleeRemoved(PersistableBundle params) {
onControleeRemoveCalled = true;
mCtrlCountDownLatch.countDown();
}
public void onControleeRemoveFailed(int reason, PersistableBundle params) {
onControleeRemoveFailedCalled = true;
mCtrlCountDownLatch.countDown();
}
public void onPaused(PersistableBundle params) {
onPauseCalled = true;
mCtrlCountDownLatch.countDown();
}
public void onPauseFailed(int reason, PersistableBundle params) {
onPauseFailedCalled = true;
mCtrlCountDownLatch.countDown();
}
public void onResumed(PersistableBundle params) {
onResumeCalled = true;
mCtrlCountDownLatch.countDown();
}
public void onResumeFailed(int reason, PersistableBundle params) {
onResumeFailedCalled = true;
mCtrlCountDownLatch.countDown();
}
public void onDataSent(UwbAddress remoteDeviceAddress, PersistableBundle params) {
onDataSentCalled = true;
mCtrlCountDownLatch.countDown();
}
public void onDataSendFailed(UwbAddress remoteDeviceAddress,
int reason, PersistableBundle params) {
onDataSendFailedCalled = true;
mCtrlCountDownLatch.countDown();
}
public void onDataReceived(UwbAddress remoteDeviceAddress,
PersistableBundle params, byte[] data) { }
public void onDataReceiveFailed(UwbAddress remoteDeviceAddress,
int reason, PersistableBundle params) { }
public void onServiceDiscovered(PersistableBundle params) { }
public void onServiceConnected(PersistableBundle params) { }
public void onRangingRoundsUpdateDtTagStatus(@NonNull PersistableBundle parameters) {
onUpdateDtTagStatusCalled = true;
mCtrlCountDownLatch.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();
}
}
private FiraOpenSessionParams.Builder makeOpenSessionBuilder() {
return new FiraOpenSessionParams.Builder()
.setProtocolVersion(new FiraProtocolVersion(1, 1))
.setSessionId(1)
.setSessionType(FiraParams.SESSION_TYPE_RANGING)
.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, 0x6}))
.setDestAddressList(List.of(UwbAddress.fromBytes(new byte[] {0x5, 0x7})));
}
private interface VerifyRangingReportInterface {
void verify(RangingReport rangingReport) throws Exception;
}
private interface RunOperationWhenSessionIsRunningInterface {
void run(@NonNull RangingSessionCallback rangingSessionCallback) throws Exception;
}
private void verifyFiraRangingSession(
@NonNull FiraOpenSessionParams firaOpenSessionParams,
@Nullable VerifyRangingReportInterface verifyRangingReport,
@Nullable RunOperationWhenSessionIsRunningInterface runOperationWhenSessionIsRunning)
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);
try {
// Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell.
uiAutomation.adoptShellPermissionIdentity();
// Start ranging session
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();
// If the test needs to verify the ranging report, do it now.
if (verifyRangingReport != null) {
verifyRangingReport.verify(rangingSessionCallback.rangingReport);
}
// If the test needs any operation to be run when the session is ongoing, do it now.
if (runOperationWhenSessionIsRunning != null) {
runOperationWhenSessionIsRunning.run(rangingSessionCallback);
}
// Check the UWB state.
assertThat(mUwbManager.getAdapterState()).isEqualTo(STATE_ENABLED_ACTIVE);
countDownLatch = new CountDownLatch(1);
rangingSessionCallback.replaceCtrlCountDownLatch(countDownLatch);
// Stop ongoing session.
rangingSessionCallback.rangingSession.stop();
// Wait for on stopped callback.
assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue();
assertThat(rangingSessionCallback.onStoppedCalled).isTrue();
} 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 FiraSpecificationParams getFiraSpecificationParams() {
UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
try {
// Only hold UWB_PRIVILEGED permission
uiAutomation.adoptShellPermissionIdentity();
PersistableBundle bundle = mUwbManager.getSpecificationInfo();
if (bundle.keySet().contains(FiraParams.PROTOCOL_NAME)) {
bundle = requireNonNull(bundle.getPersistableBundle(FiraParams.PROTOCOL_NAME));
}
return FiraSpecificationParams.fromBundle(bundle);
} finally {
uiAutomation.dropShellPermissionIdentity();
}
}
@Test
@CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-5"})
public void testFiraRangingSession() throws Exception {
FiraOpenSessionParams firaOpenSessionParams = makeOpenSessionBuilder()
.build();
verifyFiraRangingSession(firaOpenSessionParams, null, null);
}
@Test
@CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-5"})
public void testDlTdoaRangingSession() throws Exception {
FiraSpecificationParams params = getFiraSpecificationParams();
FiraProtocolVersion firaProtocolVersion = params.getMaxMacVersionSupported();
// DlTDoA is supported only for devices with FiRa 2.0 support.
assumeTrue(firaProtocolVersion.getMajor() >= 2);
FiraOpenSessionParams firaOpenSessionParams = new FiraOpenSessionParams.Builder()
.setProtocolVersion(new FiraProtocolVersion(2, 0))
.setSessionId(1)
.setSessionType(FiraParams.SESSION_TYPE_RANGING)
.setStsConfig(FiraParams.STS_CONFIG_STATIC)
.setVendorId(new byte[]{0x5, 0x6})
.setStaticStsIV(new byte[]{0x5, 0x6, 0x9, 0xa, 0x4, 0x6})
.setDeviceType(FiraParams.RANGING_DEVICE_TYPE_DT_TAG)
.setDeviceRole(FiraParams.RANGING_DEVICE_DT_TAG)
.setMultiNodeMode(FiraParams.MULTI_NODE_MODE_UNICAST)
.setRangingRoundUsage(FiraParams.RANGING_ROUND_USAGE_DL_TDOA)
.setDeviceAddress(UwbAddress.fromBytes(new byte[]{0x5, 6}))
.setDestAddressList(List.of(UwbAddress.fromBytes(new byte[]{0x5, 6})))
.build();
verifyFiraRangingSession(
firaOpenSessionParams,
(rangingReport) -> {
RangingMeasurement rangingMeasurement =
rangingReport.getMeasurements().get(0);
PersistableBundle rangingMeasurementMetadata =
rangingMeasurement.getRangingMeasurementMetadata();
assertThat(DlTDoAMeasurement.isDlTDoAMeasurement(rangingMeasurementMetadata))
.isTrue();
},
(rangingSessionCallback) -> {
CountDownLatch countDownLatch = new CountDownLatch(1);
rangingSessionCallback.replaceCtrlCountDownLatch(countDownLatch);
DlTDoARangingRoundsUpdate rangingRoundsUpdate =
new DlTDoARangingRoundsUpdate.Builder()
.setSessionId(1)
.setNoOfActiveRangingRounds(1)
.setRangingRoundIndexes(new byte[]{1})
.build();
// Update Ranging Rounds for DT Tag.
rangingSessionCallback.rangingSession.updateRangingRoundsDtTag(
rangingRoundsUpdate.toBundle());
assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue();
assertThat(rangingSessionCallback.onUpdateDtTagStatusCalled).isTrue();
});
}
@Test
@CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-5"})
public void testAdvertisingRangingSession() throws Exception {
UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
FiraSpecificationParams params = getFiraSpecificationParams();
FiraProtocolVersion firaProtocolVersion = params.getMaxMacVersionSupported();
// Advertising profile is supported only for devices with FiRa 2.0 support.
assumeTrue(firaProtocolVersion.getMajor() >= 2);
// Setup the Fira Configuration Parameters.
FiraOpenSessionParams firaOpenSessionParams = new FiraOpenSessionParams.Builder()
.setProtocolVersion(new FiraProtocolVersion(2, 0))
.setSessionId(1)
.setSessionType(FiraParams.SESSION_TYPE_RANGING)
.setStsConfig(FiraParams.STS_CONFIG_STATIC)
.setVendorId(new byte[]{0x5, 0x6})
.setStaticStsIV(new byte[]{0x5, 0x6, 0x9, 0xa, 0x4, 0x6})
// TODO(b/275077682): We likely don't need to set the DeviceType for an OWR_AoA
// ranging session, update the test based on the bug.
.setDeviceType(FiraParams.RANGING_DEVICE_TYPE_CONTROLLER)
.setDeviceRole(FiraParams.RANGING_DEVICE_ROLE_OBSERVER)
.setMultiNodeMode(FiraParams.MULTI_NODE_MODE_UNICAST)
.setRangingRoundUsage(FiraParams.RANGING_ROUND_USAGE_OWR_AOA_MEASUREMENT)
.setDeviceAddress(UwbAddress.fromBytes(new byte[]{0x5, 0x6}))
.setDestAddressList(List.of(UwbAddress.fromBytes(new byte[]{0x5, 0x6})))
.build();
// Register the UwbOemExtensionCallback with UwbManager, this requires both an API SDK
// level of at least U, and UWB_PRIVILEGED permission.
assumeTrue(SdkLevel.isAtLeastU());
UwbOemExtensionCallback uwbOemExtensionCallback = new UwbOemExtensionCallback();
try {
uiAutomation.adoptShellPermissionIdentity();
mUwbManager.registerUwbOemExtensionCallback(
Executors.newSingleThreadExecutor(), uwbOemExtensionCallback);
uiAutomation.dropShellPermissionIdentity();
} catch (SecurityException e) {
Log.i(TAG, "registerUwbOemExtensionCallback() failed with security exception: " + e);
fail();
}
verifyFiraRangingSession(
firaOpenSessionParams,
(rangingReport) -> {
assertThat(rangingReport.getMeasurements()).isNotNull();
// TODO(b/275137744): Consider adding a RangingMeasurementType field to the
// top-level RangingReportMetadata, and then confirm it's of type OwrAoa.
},
(rangingSessionCallback) -> {
// Check that onCheckPointedTarget() is called, this should happen when an
// OWR_AOA Ranging report is received (on the observer).
assertThat(uwbOemExtensionCallback.onCheckPointedTargetCalled).isTrue();
// Send a Data packet to the remote device (Advertiser)
CountDownLatch countDownLatch = new CountDownLatch(1);
rangingSessionCallback.replaceCtrlCountDownLatch(countDownLatch);
rangingSessionCallback.rangingSession.sendData(
UwbAddress.fromBytes(new byte[]{0x1, 0x2}),
new PersistableBundle(),
new byte[]{0x01, 0x02, 0x03, 0x04}
);
// Wait for the onDataSent callback.
assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue();
assertThat(rangingSessionCallback.onDataSentCalled).isTrue();
assertThat(rangingSessionCallback.onDataSendFailedCalled).isFalse();
});
try {
uiAutomation.adoptShellPermissionIdentity();
mUwbManager.unregisterUwbOemExtensionCallback(uwbOemExtensionCallback);
uiAutomation.dropShellPermissionIdentity();
} catch (SecurityException e) {
Log.i(TAG, "unregisterUwbOemExtensionCallback() failed with security exception: " + e);
fail();
}
}
@Test
@CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-5"})
public void testFiraRangingSessionWithProvisionedSTS() throws Exception {
FiraSpecificationParams params = getFiraSpecificationParams();
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();
verifyFiraRangingSession(firaOpenSessionParams, null, null);
}
@Test
@CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-5"})
public void testQueryMaxDataSizeBytes() throws Exception {
FiraSpecificationParams params = getFiraSpecificationParams();
FiraProtocolVersion firaProtocolVersion = params.getMaxMacVersionSupported();
// The "SESSION_QUERY_DATA_SIZE_IN_RANGING_CMD" is added in the UCI v2.0 spec, and so
// check if the device supports FiRa 2.0 or above.
assumeTrue(firaProtocolVersion.getMajor() >= 2);
FiraOpenSessionParams firaOpenSessionParams = makeOpenSessionBuilder().build();
verifyFiraRangingSession(
firaOpenSessionParams,
null,
(rangingSessionCallback) -> {
int dataSize = rangingSessionCallback.rangingSession.queryMaxDataSizeBytes();
assertThat(dataSize).isGreaterThan(-1);
});
}
@Test
@CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-5"})
public void testFiraPoseChanges() throws Exception {
FiraPoseUpdateParams poseVQUpdate = new FiraPoseUpdateParams.Builder()
.setPose(new float[] {0, 0, 0, 0, 0, 0, 1}) // identity vector & quaternion
.build();
float[] identityMatrix = new float[] {
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
FiraPoseUpdateParams poseMatrixUpdate = new FiraPoseUpdateParams.Builder()
.setPose(identityMatrix)
.build();
FiraOpenSessionParams firaOpenSessionParams = makeOpenSessionBuilder()
.setFilterType(FiraParams.FILTER_TYPE_APPLICATION)
.build();
assertThat(firaOpenSessionParams.getFilterType())
.isEqualTo(FiraParams.FILTER_TYPE_APPLICATION);
// Rebundle to make sure bundling/unbundling works.
FiraOpenSessionParams rebuiltParams = FiraOpenSessionParams.fromBundle(
firaOpenSessionParams.toBundle());
assertThat(rebuiltParams.getFilterType())
.isEqualTo(FiraParams.FILTER_TYPE_APPLICATION);
verifyFiraRangingSession(
firaOpenSessionParams,
null,
(rangingSessionCallback) -> {
// For practical reasons, we will not go through the [extraordinary] effort to
// check the pose change results in the CTS test due to the complexity of the
// scenario.
// Must not throw.
rangingSessionCallback.rangingSession.updatePose(poseVQUpdate.toBundle());
// Must not throw.
rangingSessionCallback.rangingSession.updatePose(poseMatrixUpdate.toBundle());
// Wrong number of values.
assertThrows(IllegalArgumentException.class,
() -> new FiraPoseUpdateParams.Builder()
.setPose(new float[] {5, 1})
.build());
// Nonreal numbers.
assertThrows(IllegalArgumentException.class,
() -> new FiraPoseUpdateParams.Builder()
.setPose(new float[] {1, 2, 3, 4, 5, Float.NaN, 7})
.build());
assertThrows(IllegalArgumentException.class,
() -> new FiraPoseUpdateParams.Builder()
.setPose(new float[] {
Float.NEGATIVE_INFINITY, 2, 3, 4, 5, 6, 7})
.build());
});
}
@Test
@CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-5"})
public void testFiraRangingPoseFailures() throws Exception {
FiraPoseUpdateParams poseUpdateParams = new FiraPoseUpdateParams.Builder()
.setPose(new float[] {1, 2, 3, 4, 5, 6, 7})
.build();
FiraOpenSessionParams firaOpenSessionParams = makeOpenSessionBuilder()
.setFilterType(FiraParams.FILTER_TYPE_NONE)
.build();
verifyFiraRangingSession(
firaOpenSessionParams,
null,
(rangingSessionCallback) -> {
assertThrows(IllegalStateException.class,
() -> rangingSessionCallback.rangingSession.updatePose(
poseUpdateParams.toBundle()
));
});
}
@Test
@CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-5"})
public void testFiraRangingSessionAddRemoveControlee() throws Exception {
FiraOpenSessionParams firaOpenSessionParams = makeOpenSessionBuilder()
.setMultiNodeMode(FiraParams.MULTI_NODE_MODE_ONE_TO_MANY)
.build();
verifyFiraRangingSession(
firaOpenSessionParams,
null,
(rangingSessionCallback) -> {
// Add new controlee
CountDownLatch countDownLatch = new CountDownLatch(2);
rangingSessionCallback.replaceCtrlCountDownLatch(countDownLatch);
UwbAddress uwbAddress = UwbAddress.fromBytes(new byte[]{0x5, 0x5});
rangingSessionCallback.rangingSession.addControlee(
new FiraControleeParams.Builder()
.setAddressList(new UwbAddress[]{uwbAddress})
.build().toBundle()
);
// Wait for the on reconfigured and controlee added callback.
assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue();
assertThat(rangingSessionCallback.onReconfiguredCalled).isTrue();
assertThat(rangingSessionCallback.onReconfiguredFailedCalled).isFalse();
assertThat(rangingSessionCallback.onControleeAddCalled).isTrue();
assertThat(rangingSessionCallback.onControleeAddFailedCalled).isFalse();
// Remove controlee
countDownLatch = new CountDownLatch(2);
rangingSessionCallback.replaceCtrlCountDownLatch(countDownLatch);
rangingSessionCallback.rangingSession.removeControlee(
new FiraControleeParams.Builder()
.setAddressList(new UwbAddress[]{uwbAddress})
.build().toBundle()
);
// Wait for the on reconfigured and controlee added callback.
assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue();
assertThat(rangingSessionCallback.onReconfiguredCalled).isTrue();
assertThat(rangingSessionCallback.onReconfiguredFailedCalled).isFalse();
assertThat(rangingSessionCallback.onControleeRemoveCalled).isTrue();
assertThat(rangingSessionCallback.onControleeRemoveFailedCalled).isFalse();
});
}
@Test
@CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-5"})
public void testFiraRangingSessionPauseResume() throws Exception {
FiraOpenSessionParams firaOpenSessionParams = makeOpenSessionBuilder()
.setMultiNodeMode(FiraParams.MULTI_NODE_MODE_ONE_TO_MANY)
.build();
verifyFiraRangingSession(
firaOpenSessionParams,
null,
(rangingSessionCallback) -> {
// Pause the session - not supported yet.
assertThrows(IllegalStateException.class,
() -> rangingSessionCallback.rangingSession.pause(
new PersistableBundle()
));
assertThat(rangingSessionCallback.onPauseCalled).isFalse();
assertThat(rangingSessionCallback.onPauseFailedCalled).isFalse();
// Resume the session - not supported yet.
assertThrows(IllegalStateException.class,
() -> rangingSessionCallback.rangingSession.resume(
new PersistableBundle()
));
assertThat(rangingSessionCallback.onResumeCalled).isFalse();
assertThat(rangingSessionCallback.onResumeFailedCalled).isFalse();
});
}
@Test
@CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-5"})
public void testFiraRangingSessionReconfigure() throws Exception {
FiraOpenSessionParams firaOpenSessionParams = makeOpenSessionBuilder()
.setMultiNodeMode(FiraParams.MULTI_NODE_MODE_ONE_TO_MANY)
.build();
verifyFiraRangingSession(
firaOpenSessionParams,
null,
(rangingSessionCallback) -> {
// Reconfigure to disable notifications.
CountDownLatch countDownLatch = new CountDownLatch(1);
rangingSessionCallback.replaceCtrlCountDownLatch(countDownLatch);
FiraRangingReconfigureParams reconfigureParams =
new FiraRangingReconfigureParams.Builder()
.setRangeDataNtfConfig(FiraParams.RANGE_DATA_NTF_CONFIG_DISABLE)
.build();
rangingSessionCallback.rangingSession.reconfigure(reconfigureParams.toBundle());
// Wait for the on reconfigured and controlee added callback.
assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue();
assertThat(rangingSessionCallback.onReconfiguredCalled).isTrue();
assertThat(rangingSessionCallback.onReconfiguredFailedCalled).isFalse();
// Ensure no more ranging reports are received.
CountDownLatch resultCountDownLatch = new CountDownLatch(1);
rangingSessionCallback.replaceResultCountDownLatch(resultCountDownLatch);
assertThat(resultCountDownLatch.await(1, TimeUnit.SECONDS)).isFalse();
});
}
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();
}
}
@Test
@CddTest(requirements = {"7.3.13/C-1-1,C-1-2"})
public void testSendVendorUciMessageWithMessageType() throws Exception {
Assume.assumeTrue(SdkLevel.isAtLeastU());
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(MESSAGE_TYPE_COMMAND, 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();
}
}
private class UwbOemExtensionCallback implements UwbManager.UwbOemExtensionCallback {
public PersistableBundle mSessionChangeNtf;
public PersistableBundle mDeviceStatusNtf;
public PersistableBundle mSessionConfig;
public RangingReport mRangingReport;
public boolean onSessionConfigCompleteCalled = false;
public boolean onRangingReportReceivedCalled = false;
public boolean onSessionChangedCalled = false;
public boolean onDeviceStatusNtfCalled = false;
public boolean onCheckPointedTargetCalled = false;
@Override
public void onSessionStatusNotificationReceived(
@NonNull PersistableBundle sessionStatusBundle) {
mSessionChangeNtf = sessionStatusBundle;
onSessionChangedCalled = true;
}
@Override
public void onDeviceStatusNotificationReceived(PersistableBundle deviceStatusBundle) {
mDeviceStatusNtf = deviceStatusBundle;
onDeviceStatusNtfCalled = true;
}
@NonNull
@Override
public int onSessionConfigurationComplete(@NonNull PersistableBundle openSessionBundle) {
mSessionConfig = openSessionBundle;
onSessionConfigCompleteCalled = true;
return 0;
}
@NonNull
@Override
public RangingReport onRangingReportReceived(
@NonNull RangingReport rangingReport) {
onRangingReportReceivedCalled = true;
mRangingReport = rangingReport;
return mRangingReport;
}
@Override
public boolean onCheckPointedTarget(
@NonNull PersistableBundle pointedTargetBundle) {
onCheckPointedTargetCalled = true;
return true;
}
}
@Test
@CddTest(requirements = {"7.3.13/C-1-1,C-1-2,C-1-5"})
public void testOemCallbackExtension() throws Exception {
Assume.assumeTrue(SdkLevel.isAtLeastU());
UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
CancellationSignal cancellationSignal = null;
CountDownLatch countDownLatch = new CountDownLatch(1);
CountDownLatch resultCountDownLatch = new CountDownLatch(1);
UwbOemExtensionCallback uwbOemExtensionCallback = new UwbOemExtensionCallback();
int sessionId = 1;
RangingSessionCallback rangingSessionCallback =
new RangingSessionCallback(countDownLatch, resultCountDownLatch);
FiraOpenSessionParams firaOpenSessionParams = new FiraOpenSessionParams.Builder()
.setProtocolVersion(new FiraProtocolVersion(1, 1))
.setSessionId(sessionId)
.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();
mUwbManager.registerUwbOemExtensionCallback(
Executors.newSingleThreadExecutor(), uwbOemExtensionCallback);
// Start ranging session
cancellationSignal = mUwbManager.openRangingSession(
firaOpenSessionParams.toBundle(),
Executors.newSingleThreadExecutor(),
rangingSessionCallback,
mDefaultChipId);
// Wait for the on opened callback.
assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue();
assertThat(uwbOemExtensionCallback.onSessionConfigCompleteCalled).isTrue();
assertThat(uwbOemExtensionCallback.mSessionConfig).isNotNull();
FiraOpenSessionParams openSessionParamsBundle = FiraOpenSessionParams
.fromBundle(uwbOemExtensionCallback.mSessionConfig);
assertEquals(openSessionParamsBundle.getSessionId(), sessionId);
assertEquals(openSessionParamsBundle.getStsConfig(), FiraParams.STS_CONFIG_STATIC);
assertEquals(openSessionParamsBundle.getDeviceType(),
FiraParams.RANGING_DEVICE_TYPE_CONTROLLER);
assertThat(uwbOemExtensionCallback.onSessionChangedCalled).isTrue();
assertThat(uwbOemExtensionCallback.mSessionChangeNtf).isNotNull();
SessionStatus sessionStatusBundle = SessionStatus
.fromBundle(uwbOemExtensionCallback.mSessionChangeNtf);
assertEquals(sessionStatusBundle.getSessionId(), sessionId);
assertEquals(sessionStatusBundle.getState(), UWB_SESSION_STATE_IDLE);
assertEquals(sessionStatusBundle.getReasonCode(),
REASON_STATE_CHANGE_WITH_SESSION_MANAGEMENT_COMMANDS);
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(uwbOemExtensionCallback.onSessionChangedCalled).isTrue();
assertThat(uwbOemExtensionCallback.mSessionChangeNtf).isNotNull();
assertThat(uwbOemExtensionCallback.onDeviceStatusNtfCalled).isTrue();
assertThat(uwbOemExtensionCallback.mDeviceStatusNtf).isNotNull();
DeviceStatus deviceStatusBundle = DeviceStatus
.fromBundle(uwbOemExtensionCallback.mDeviceStatusNtf);
assertEquals(deviceStatusBundle.getDeviceState(), DEVICE_STATE_ACTIVE);
// Wait for the on ranging report callback.
assertThat(resultCountDownLatch.await(1, TimeUnit.SECONDS)).isTrue();
assertThat(rangingSessionCallback.rangingReport).isNotNull();
assertThat(uwbOemExtensionCallback.onRangingReportReceivedCalled).isTrue();
assertThat(uwbOemExtensionCallback.mRangingReport).isNotNull();
PersistableBundle reportMetadataBundle = uwbOemExtensionCallback
.mRangingReport.getRangingReportMetadata();
RangingReportMetadata reportMetadata = RangingReportMetadata
.fromBundle(reportMetadataBundle);
assertEquals(reportMetadata.getSessionId(), sessionId);
assertThat(reportMetadata.getRawNtfData()).isNotEmpty();
// Check the UWB state.
assertThat(mUwbManager.getAdapterState()).isEqualTo(STATE_ENABLED_ACTIVE);
countDownLatch = new CountDownLatch(1);
rangingSessionCallback.replaceCtrlCountDownLatch(countDownLatch);
// Stop ongoing session.
rangingSessionCallback.rangingSession.stop();
// Wait for on stopped callback.
assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue();
assertThat(rangingSessionCallback.onStoppedCalled).isTrue();
} 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();
}
try {
mUwbManager.unregisterUwbOemExtensionCallback(uwbOemExtensionCallback);
} catch (SecurityException e) {
/* pass */
fail();
}
uiAutomation.dropShellPermissionIdentity();
}
}
@Test
@CddTest(requirements = {"7.3.13/C-1-1,C-1-2"})
public void testRegisterUwbOemExtensionCallbackWithoutUwbPrivileged() {
Assume.assumeTrue(SdkLevel.isAtLeastU());
UwbManager.UwbOemExtensionCallback cb = new UwbOemExtensionCallback();
try {
mUwbManager.registerUwbOemExtensionCallback(
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 testUnregisterUwbOemExtensionCallbackWithoutUwbPrivileged() {
Assume.assumeTrue(SdkLevel.isAtLeastU());
UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
UwbManager.UwbOemExtensionCallback cb = new UwbOemExtensionCallback();
try {
// Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell.
uiAutomation.adoptShellPermissionIdentity();
mUwbManager.registerUwbOemExtensionCallback(
Executors.newSingleThreadExecutor(), cb);
} catch (SecurityException e) {
/* fail */
fail();
} finally {
uiAutomation.dropShellPermissionIdentity();
}
try {
mUwbManager.unregisterUwbOemExtensionCallback(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 {
// Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell.
uiAutomation.adoptShellPermissionIdentity();
mUwbManager.unregisterUwbOemExtensionCallback(cb);
} catch (SecurityException e) {
/* pass */
fail();
}
}
private static class OnUwbActivityEnergyInfoListener implements
Consumer<UwbActivityEnergyInfo> {
private final CountDownLatch mCountDownLatch;
public UwbActivityEnergyInfo mPowerStats;
public boolean mIsListenerInvoked = false;
OnUwbActivityEnergyInfoListener(@NonNull CountDownLatch countDownLatch) {
mCountDownLatch = countDownLatch;
}
@Override
public void accept(UwbActivityEnergyInfo info) {
mIsListenerInvoked = true;
mPowerStats = info;
mCountDownLatch.countDown();
}
}
@Test
@CddTest(requirements = {"7.3.13/C-1-1,C-1-2"})
public void testGetUwbActivityEnergyInfoAsync() throws Exception {
UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
CountDownLatch countDownLatch = new CountDownLatch(1);
OnUwbActivityEnergyInfoListener listener =
new OnUwbActivityEnergyInfoListener(countDownLatch);
try {
uiAutomation.adoptShellPermissionIdentity();
mUwbManager.getUwbActivityEnergyInfoAsync(Executors.newSingleThreadExecutor(),
listener);
assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue();
assertThat(listener.mIsListenerInvoked).isTrue();
if (listener.mPowerStats != null) {
assertThat(listener.mPowerStats.getControllerIdleDurationMillis() >= 0)
.isTrue();
assertThat(listener.mPowerStats.getControllerWakeCount() >= 0).isTrue();
}
} catch (SecurityException e) {
/* pass */
} finally {
uiAutomation.dropShellPermissionIdentity();
}
}
@Test
@CddTest(requirements = {"7.3.13/C-1-1,C-1-2"})
public void testGetUwbActivityEnergyInfoAsyncWithoutUwbPrivileged() throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(1);
OnUwbActivityEnergyInfoListener listener =
new OnUwbActivityEnergyInfoListener(countDownLatch);
try {
mUwbManager.getUwbActivityEnergyInfoAsync(Executors.newSingleThreadExecutor(),
listener);
// 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 testGetUwbActivityEnergyInfoAsyncWithBadParams() throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(1);
OnUwbActivityEnergyInfoListener listener =
new OnUwbActivityEnergyInfoListener(countDownLatch);
// null Executor
assertThrows(NullPointerException.class,
() -> mUwbManager.getUwbActivityEnergyInfoAsync(null, listener));
// null listener
assertThrows(NullPointerException.class,
() -> mUwbManager.getUwbActivityEnergyInfoAsync(Executors.newSingleThreadExecutor(),
null));
}
}