| /* |
| * 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 com.android.server.uwb; |
| |
| |
| import static com.android.server.uwb.UwbTestUtils.MAX_DATA_SIZE; |
| import static com.android.server.uwb.data.UwbUciConstants.STATUS_CODE_ANDROID_REGULATION_UWB_OFF; |
| import static com.android.server.uwb.data.UwbUciConstants.STATUS_CODE_FAILED; |
| import static com.android.server.uwb.data.UwbUciConstants.STATUS_CODE_OK; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.uwb.support.ccc.CccParams.CHAPS_PER_SLOT_3; |
| import static com.google.uwb.support.ccc.CccParams.HOPPING_CONFIG_MODE_NONE; |
| import static com.google.uwb.support.ccc.CccParams.HOPPING_SEQUENCE_DEFAULT; |
| import static com.google.uwb.support.ccc.CccParams.PULSE_SHAPE_SYMMETRICAL_ROOT_RAISED_COSINE; |
| import static com.google.uwb.support.ccc.CccParams.SLOTS_PER_ROUND_6; |
| import static com.google.uwb.support.ccc.CccParams.UWB_CHANNEL_9; |
| 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; |
| import static com.google.uwb.support.fira.FiraParams.MULTI_NODE_MODE_UNICAST; |
| import static com.google.uwb.support.fira.FiraParams.RANGE_DATA_NTF_CONFIG_ENABLE_PROXIMITY_LEVEL_TRIG; |
| import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_ROLE_RESPONDER; |
| import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_TYPE_CONTROLLER; |
| import static com.google.uwb.support.fira.FiraParams.RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE; |
| import static com.google.uwb.support.fira.FiraParams.SESSION_TYPE_RANGING; |
| |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyBoolean; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.anyString; |
| import static org.mockito.ArgumentMatchers.argThat; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.Mockito.clearInvocations; |
| import static org.mockito.Mockito.doAnswer; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.validateMockitoUsage; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.verifyNoMoreInteractions; |
| import static org.mockito.Mockito.verifyZeroInteractions; |
| import static org.mockito.Mockito.when; |
| |
| import android.content.AttributionSource; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.os.IBinder; |
| import android.os.PersistableBundle; |
| import android.os.PowerManager; |
| import android.os.RemoteException; |
| import android.os.test.TestLooper; |
| import android.platform.test.annotations.Presubmit; |
| import android.test.suitebuilder.annotation.SmallTest; |
| import android.util.Pair; |
| import android.uwb.AdapterState; |
| import android.uwb.IOnUwbActivityEnergyInfoListener; |
| import android.uwb.IUwbAdapterStateCallbacks; |
| import android.uwb.IUwbOemExtensionCallback; |
| import android.uwb.IUwbRangingCallbacks; |
| import android.uwb.IUwbVendorUciCallback; |
| import android.uwb.SessionHandle; |
| import android.uwb.StateChangeReason; |
| import android.uwb.UwbAddress; |
| import android.uwb.UwbManager; |
| |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.dx.mockito.inline.extended.ExtendedMockito; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.server.uwb.data.UwbUciConstants; |
| import com.android.server.uwb.data.UwbVendorUciResponse; |
| import com.android.server.uwb.jni.NativeUwbManager; |
| import com.android.server.uwb.multchip.UwbMultichipData; |
| import com.android.server.uwb.multichip.MultichipConfigFileCreator; |
| import com.android.server.uwb.pm.ProfileManager; |
| import com.android.uwb.resources.R; |
| |
| import com.google.uwb.support.ccc.CccOpenRangingParams; |
| import com.google.uwb.support.ccc.CccParams; |
| import com.google.uwb.support.ccc.CccProtocolVersion; |
| import com.google.uwb.support.ccc.CccPulseShapeCombo; |
| import com.google.uwb.support.ccc.CccSpecificationParams; |
| import com.google.uwb.support.ccc.CccStartRangingParams; |
| 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.FiraRangingReconfigureParams; |
| import com.google.uwb.support.fira.FiraSpecificationParams; |
| import com.google.uwb.support.generic.GenericParams; |
| import com.google.uwb.support.generic.GenericSpecificationParams; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TemporaryFolder; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.Mockito; |
| import org.mockito.MockitoAnnotations; |
| import org.mockito.MockitoSession; |
| import org.mockito.invocation.InvocationOnMock; |
| import org.mockito.quality.Strictness; |
| import org.mockito.stubbing.Answer; |
| |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.FutureTask; |
| |
| /** |
| * Tests for {@link UwbServiceCore}. |
| */ |
| @RunWith(AndroidJUnit4.class) |
| @SmallTest |
| @Presubmit |
| public class UwbServiceCoreTest { |
| @Rule |
| public TemporaryFolder mTempFolder = TemporaryFolder.builder().build(); |
| |
| private static final int TEST_UID = 44; |
| private static final String TEST_PACKAGE_NAME = "com.android.uwb"; |
| private static final String TEST_DEFAULT_CHIP_ID = "default"; |
| private static final String TEST_CHIP_ONE_CHIP_ID = "chipIdString1"; |
| private static final String VALID_COUNTRY_CODE = "US"; |
| |
| private static final AttributionSource TEST_ATTRIBUTION_SOURCE = |
| new AttributionSource.Builder(TEST_UID) |
| .setPackageName(TEST_PACKAGE_NAME) |
| .build(); |
| private static final FiraOpenSessionParams.Builder TEST_FIRA_OPEN_SESSION_PARAMS = |
| new FiraOpenSessionParams.Builder() |
| .setProtocolVersion(FiraParams.PROTOCOL_VERSION_1_1) |
| .setSessionId(1) |
| .setSessionType(SESSION_TYPE_RANGING) |
| .setDeviceType(RANGING_DEVICE_TYPE_CONTROLLER) |
| .setDeviceRole(RANGING_DEVICE_ROLE_RESPONDER) |
| .setDeviceAddress(UwbAddress.fromBytes(new byte[] { 0x4, 0x6})) |
| .setDestAddressList(Arrays.asList(UwbAddress.fromBytes(new byte[] { 0x4, 0x6}))) |
| .setMultiNodeMode(MULTI_NODE_MODE_UNICAST) |
| .setRangingRoundUsage(RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE) |
| .setVendorId(new byte[]{0x5, 0x78}) |
| .setStaticStsIV(new byte[]{0x1a, 0x55, 0x77, 0x47, 0x7e, 0x7d}); |
| |
| @VisibleForTesting |
| private static final CccOpenRangingParams.Builder TEST_CCC_OPEN_RANGING_PARAMS = |
| new CccOpenRangingParams.Builder() |
| .setProtocolVersion(CccParams.PROTOCOL_VERSION_1_0) |
| .setUwbConfig(CccParams.UWB_CONFIG_0) |
| .setPulseShapeCombo( |
| new CccPulseShapeCombo( |
| PULSE_SHAPE_SYMMETRICAL_ROOT_RAISED_COSINE, |
| PULSE_SHAPE_SYMMETRICAL_ROOT_RAISED_COSINE)) |
| .setSessionId(1) |
| .setRanMultiplier(4) |
| .setChannel(UWB_CHANNEL_9) |
| .setNumChapsPerSlot(CHAPS_PER_SLOT_3) |
| .setNumResponderNodes(1) |
| .setNumSlotsPerRound(SLOTS_PER_ROUND_6) |
| .setSyncCodeIndex(1) |
| .setHoppingConfigMode(HOPPING_CONFIG_MODE_NONE) |
| .setHoppingSequence(HOPPING_SEQUENCE_DEFAULT); |
| @Mock private Context mContext; |
| @Mock private NativeUwbManager mNativeUwbManager; |
| @Mock private UwbMetrics mUwbMetrics; |
| @Mock private UwbCountryCode mUwbCountryCode; |
| @Mock private UwbSessionManager mUwbSessionManager; |
| @Mock private UwbConfigurationManager mUwbConfigurationManager; |
| @Mock private UwbInjector mUwbInjector; |
| @Mock DeviceConfigFacade mDeviceConfigFacade; |
| @Mock private ProfileManager mProfileManager; |
| @Mock private PowerManager.WakeLock mUwbWakeLock; |
| @Mock private Resources mResources; |
| |
| private TestLooper mTestLooper; |
| private MockitoSession mMockitoSession; |
| |
| private UwbServiceCore mUwbServiceCore; |
| |
| private static final int MESSAGE_TYPE_TEST_1 = 4; |
| |
| @Before |
| public void setUp() throws Exception { |
| MockitoAnnotations.initMocks(this); |
| mTestLooper = new TestLooper(); |
| PowerManager powerManager = mock(PowerManager.class); |
| when(powerManager.newWakeLock(anyInt(), anyString())) |
| .thenReturn(mUwbWakeLock); |
| when(mContext.getSystemService(PowerManager.class)).thenReturn(powerManager); |
| when(mUwbInjector.getDeviceConfigFacade()).thenReturn(mDeviceConfigFacade); |
| UwbMultichipData uwbMultichipData = setUpMultichipDataForOneChip(); |
| when(mUwbInjector.getMultichipData()).thenReturn(uwbMultichipData); |
| when(mDeviceConfigFacade.getBugReportMinIntervalMs()) |
| .thenReturn(DeviceConfigFacade.DEFAULT_BUG_REPORT_MIN_INTERVAL_MS); |
| when(mUwbInjector.getProfileManager()).thenReturn(mProfileManager); |
| mUwbServiceCore = new UwbServiceCore(mContext, mNativeUwbManager, mUwbMetrics, |
| mUwbCountryCode, mUwbSessionManager, mUwbConfigurationManager, |
| mUwbInjector, mTestLooper.getLooper()); |
| |
| uwbMultichipData.initialize(); |
| |
| // static mocking for executor service. |
| mMockitoSession = ExtendedMockito.mockitoSession() |
| .mockStatic(Executors.class, Mockito.withSettings().lenient()) |
| .strictness(Strictness.LENIENT) |
| .startMocking(); |
| ExecutorService executorService = mock(ExecutorService.class); |
| doAnswer(invocation -> { |
| FutureTask t = invocation.getArgument(1); |
| t.run(); |
| return t; |
| }).when(executorService).submit(any(Callable.class)); |
| doAnswer(invocation -> { |
| FutureTask t = invocation.getArgument(0); |
| t.run(); |
| return t; |
| }).when(executorService).submit(any(Runnable.class)); |
| when(Executors.newSingleThreadExecutor()).thenReturn(executorService); |
| } |
| |
| /** |
| * Called after each test |
| */ |
| @After |
| public void cleanup() { |
| validateMockitoUsage(); |
| if (mMockitoSession != null) { |
| mMockitoSession.finishMocking(); |
| } |
| } |
| |
| private UwbMultichipData setUpMultichipDataForOneChip() throws Exception { |
| when(mResources.getBoolean(R.bool.config_isMultichip)).thenReturn(false); |
| when(mContext.getResources()).thenReturn(mResources); |
| return new UwbMultichipData(mContext); |
| } |
| |
| private UwbMultichipData setUpMultichipDataForTwoChips() throws Exception { |
| when(mResources.getBoolean(R.bool.config_isMultichip)).thenReturn(true); |
| String path = MultichipConfigFileCreator.createTwoChipFileFromResource(mTempFolder, |
| getClass()).getCanonicalPath(); |
| when(mResources.getString(R.string.config_multichipConfigPath)).thenReturn(path); |
| when(mContext.getResources()).thenReturn(mResources); |
| return new UwbMultichipData(mContext); |
| } |
| |
| private void verifyGetSpecificationInfoSuccess() throws Exception { |
| GenericSpecificationParams genericSpecificationParams = |
| mock(GenericSpecificationParams.class); |
| PersistableBundle genericSpecificationBundle = mock(PersistableBundle.class); |
| when(genericSpecificationParams.toBundle()).thenReturn(genericSpecificationBundle); |
| |
| when(mUwbConfigurationManager |
| .getCapsInfo(eq(GenericParams.PROTOCOL_NAME), any(), anyString())) |
| .thenReturn(Pair.create( |
| UwbUciConstants.STATUS_CODE_OK, genericSpecificationParams)); |
| |
| PersistableBundle specifications = mUwbServiceCore.getSpecificationInfo( |
| TEST_DEFAULT_CHIP_ID); |
| assertThat(specifications).isEqualTo(genericSpecificationBundle); |
| verify(mUwbConfigurationManager) |
| .getCapsInfo(eq(GenericParams.PROTOCOL_NAME), any(), eq(TEST_DEFAULT_CHIP_ID)); |
| |
| assertThat(mUwbServiceCore.getCachedSpecificationParams(TEST_DEFAULT_CHIP_ID)).isEqualTo( |
| genericSpecificationParams); |
| } |
| |
| private void enableUwb() throws Exception { |
| when(mNativeUwbManager.doInitialize()).thenReturn(true); |
| when(mUwbCountryCode.getCountryCode()).thenReturn(null); |
| when(mUwbCountryCode.setCountryCode(anyBoolean())).thenReturn( |
| Pair.create(STATUS_CODE_FAILED, null)); |
| |
| mUwbServiceCore.setEnabled(true); |
| mTestLooper.dispatchAll(); |
| } |
| |
| private void enableUwbWithCountryCodeChangedCallback() throws Exception { |
| when(mNativeUwbManager.doInitialize()).thenReturn(true); |
| |
| // When there is a valid country code, we expect an immediate (inline) invocation of the |
| // mUwbServiceCore.onCountryCodeChanged() callback to happen. |
| doAnswer(new Answer() { |
| @Override |
| public Object answer(InvocationOnMock invocation) throws Throwable { |
| mUwbServiceCore.onCountryCodeChanged(VALID_COUNTRY_CODE); |
| return Pair.create(STATUS_CODE_OK, VALID_COUNTRY_CODE); |
| } |
| }).when(mUwbCountryCode).setCountryCode(anyBoolean()); |
| |
| // UwbCountryCode class will return the VALID_COUNTRY_CODE, once setCountryCode() has been |
| // called on it, and it has picked the country code. |
| when(mUwbCountryCode.getCountryCode()).thenReturn(VALID_COUNTRY_CODE); |
| |
| mUwbServiceCore.setEnabled(true); |
| mTestLooper.dispatchAll(); |
| } |
| |
| private void disableUwb() throws Exception { |
| when(mNativeUwbManager.doDeinitialize()).thenReturn(true); |
| |
| mUwbServiceCore.setEnabled(false); |
| mTestLooper.dispatchAll(); |
| } |
| |
| @Test |
| public void testGetSpecificationInfoSuccess() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| verifyGetSpecificationInfoSuccess(); |
| } |
| |
| @Test |
| public void testGetSpecificationInfoFailWhenUwbDisabled() throws Exception { |
| try { |
| mUwbServiceCore.getCachedSpecificationParams(TEST_DEFAULT_CHIP_ID); |
| fail(); |
| } catch (IllegalStateException e) { |
| // pass |
| } |
| } |
| |
| @Test |
| public void testEnableWithCountryCode_success() throws Exception { |
| IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); |
| when(cb.asBinder()).thenReturn(mock(IBinder.class)); |
| mUwbServiceCore.registerAdapterStateCallbacks(cb); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| clearInvocations(cb); |
| |
| // Enable (with country code initially unknown, like at boot time). |
| enableUwb(); |
| |
| verify(mNativeUwbManager).doInitialize(); |
| verify(mUwbCountryCode).setCountryCode(true); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_POLICY); |
| verifyNoMoreInteractions(cb); |
| |
| clearInvocations(cb); |
| |
| // We receive an initial onCountryCodeChanged() notification with the default (invalid) |
| // country code. We don't expect any more AdapterState notifications as the Adapter State |
| // is still considered to be the same (STATE_DISABLED). |
| mUwbServiceCore.onCountryCodeChanged("00"); |
| verifyNoMoreInteractions(cb); |
| |
| // Valid country code changed notification is received after some time (before the timeout). |
| // The message queue immediately has a message to process, which results in a call to the |
| // adapter state callback. |
| mUwbServiceCore.onCountryCodeChanged("US"); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| } |
| |
| // Unit test for scenario when setting the country code (during UWB Enable) fails with a UWB |
| // regulatory error (eg: UWB not available in the configured country). In this case, we expect |
| // the apps to be notified with UWB state as Disabled and reason as SYSTEM_REGULATION. |
| @Test |
| public void testEnableWithCountryCode_statusRegulationUwbOff() throws Exception { |
| IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); |
| when(cb.asBinder()).thenReturn(mock(IBinder.class)); |
| mUwbServiceCore.registerAdapterStateCallbacks(cb); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| |
| clearInvocations(mNativeUwbManager, mUwbCountryCode, cb); |
| |
| // Enable (with country code that results in the Vendor-specific UWB_REGULATION error). |
| when(mNativeUwbManager.doInitialize()).thenReturn(true); |
| |
| when(mUwbCountryCode.getCountryCode()).thenReturn("JP"); |
| when(mUwbCountryCode.setCountryCode(anyBoolean())) |
| .thenReturn(Pair.create(STATUS_CODE_ANDROID_REGULATION_UWB_OFF, "JP")); |
| |
| mUwbServiceCore.setEnabled(true); |
| mTestLooper.dispatchAll(); |
| |
| verify(mNativeUwbManager).doInitialize(); |
| verify(mUwbCountryCode).setCountryCode(true); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_REGULATION); |
| } |
| |
| // Unit test for scenario when setting the country code (during UWB Enable) fails with a generic |
| // error (eg: UWBS internal error). In this case, we expect the apps to be notified with |
| // UWB state as Disabled and reason as SYSTEM_POLICY. |
| @Test |
| public void testEnableWithCountryCode_statusFailed() throws Exception { |
| IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); |
| when(cb.asBinder()).thenReturn(mock(IBinder.class)); |
| mUwbServiceCore.registerAdapterStateCallbacks(cb); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| |
| clearInvocations(mNativeUwbManager, mUwbCountryCode, cb); |
| |
| // Enable (with a valid country code), but the firmware returns some error. |
| when(mNativeUwbManager.doInitialize()).thenReturn(true); |
| when(mUwbCountryCode.getCountryCode()).thenReturn(VALID_COUNTRY_CODE); |
| when(mUwbCountryCode.setCountryCode(anyBoolean())).thenReturn(Pair.create( |
| STATUS_CODE_FAILED, VALID_COUNTRY_CODE)); |
| |
| mUwbServiceCore.setEnabled(true); |
| mTestLooper.dispatchAll(); |
| |
| verify(mNativeUwbManager).doInitialize(); |
| verify(mUwbCountryCode).setCountryCode(true); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_POLICY); |
| } |
| |
| @Test |
| public void testEnableWhenAlreadyEnabled() throws Exception { |
| IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); |
| when(cb.asBinder()).thenReturn(mock(IBinder.class)); |
| mUwbServiceCore.registerAdapterStateCallbacks(cb); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| |
| // Enabled UWB, we expect an Adapter State notification with State ENABLED_INACTIVE as |
| // there is a valid country code. |
| enableUwbWithCountryCodeChangedCallback(); |
| verify(mNativeUwbManager).doInitialize(); |
| verify(mUwbCountryCode).setCountryCode(true); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| |
| clearInvocations(mNativeUwbManager, mUwbCountryCode, cb); |
| |
| // Enable again. should be ignored. |
| enableUwb(); |
| verifyNoMoreInteractions(mNativeUwbManager, mUwbCountryCode, cb); |
| } |
| |
| // Test the UWB stack enable when the NativeUwbManager.doInitialize() is delayed such that the |
| // watchdog timer expiry happens. |
| @Test |
| public void testEnableWhenInitializeDelayed() throws Exception { |
| IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); |
| when(cb.asBinder()).thenReturn(mock(IBinder.class)); |
| mUwbServiceCore.registerAdapterStateCallbacks(cb); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| |
| // Setup doInitialize() to take long time, such that the WatchDog thread times out. |
| when(mNativeUwbManager.doInitialize()).thenAnswer(new Answer<Boolean>() { |
| public Boolean answer(InvocationOnMock invocation) throws Throwable { |
| // Return success but too late, so this result shouldn't matter. |
| Thread.sleep(UwbServiceCore.WATCHDOG_MS + 1000); |
| return true; |
| } |
| }); |
| |
| doAnswer(new Answer() { |
| @Override |
| public Object answer(InvocationOnMock invocation) throws Throwable { |
| mUwbServiceCore.onCountryCodeChanged(VALID_COUNTRY_CODE); |
| return Pair.create(STATUS_CODE_OK, VALID_COUNTRY_CODE); |
| } |
| }).when(mUwbCountryCode).setCountryCode(anyBoolean()); |
| when(mUwbCountryCode.getCountryCode()).thenReturn(VALID_COUNTRY_CODE); |
| |
| // Setup the wakelock to be checked twice (once from the watchdog thread after expiry, and |
| // second time from handleEnable()). |
| when(mUwbWakeLock.isHeld()).thenReturn(true).thenReturn(false); |
| |
| mUwbServiceCore.setEnabled(true); |
| mTestLooper.dispatchAll(); |
| |
| verify(mNativeUwbManager).doInitialize(); |
| verify(mUwbWakeLock, times(1)).acquire(); |
| verify(mUwbWakeLock, times(2)).isHeld(); |
| verify(mUwbWakeLock, times(1)).release(); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| } |
| |
| @Test |
| public void testDisable() throws Exception { |
| IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); |
| when(cb.asBinder()).thenReturn(mock(IBinder.class)); |
| mUwbServiceCore.registerAdapterStateCallbacks(cb); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| clearInvocations(cb); |
| |
| // Enable first. |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| verify(mNativeUwbManager).doInitialize(); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| verifyNoMoreInteractions(cb); |
| clearInvocations(cb); |
| |
| // Disable UWB. |
| disableUwb(); |
| |
| verify(mNativeUwbManager).doDeinitialize(); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_POLICY); |
| } |
| |
| // Test the UWB stack disable when the NativeUwbManager.doDeinitialize() is delayed such that |
| // the watchdog timer expiry happens. |
| @Test |
| public void testDisableWhenDeinitializeDelayed() throws Exception { |
| IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); |
| when(cb.asBinder()).thenReturn(mock(IBinder.class)); |
| mUwbServiceCore.registerAdapterStateCallbacks(cb); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| |
| // Enable first |
| enableUwbWithCountryCodeChangedCallback(); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| |
| clearInvocations(mUwbWakeLock, cb); |
| |
| // Setup doDeinitialize() to take long time, such that the WatchDog thread times out. |
| when(mNativeUwbManager.doDeinitialize()).thenAnswer(new Answer<Boolean>() { |
| public Boolean answer(InvocationOnMock invocation) throws Throwable { |
| // Return success but too late, so this result shouldn't matter. |
| Thread.sleep(UwbServiceCore.WATCHDOG_MS + 1000); |
| return true; |
| } |
| }); |
| |
| // Setup the wakelock to be checked twice (once from the watchdog thread after expiry, and |
| // second time from handleDisable()). |
| when(mUwbWakeLock.isHeld()).thenReturn(true).thenReturn(false); |
| |
| // Disable UWB. |
| mUwbServiceCore.setEnabled(false); |
| mTestLooper.dispatchAll(); |
| |
| verify(mNativeUwbManager).doDeinitialize(); |
| verify(mUwbWakeLock, times(1)).acquire(); |
| verify(mUwbWakeLock, times(2)).isHeld(); |
| verify(mUwbWakeLock, times(1)).release(); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_POLICY); |
| } |
| |
| @Test |
| public void testDisableWhenAlreadyDisabled() throws Exception { |
| IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); |
| when(cb.asBinder()).thenReturn(mock(IBinder.class)); |
| mUwbServiceCore.registerAdapterStateCallbacks(cb); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| clearInvocations(cb); |
| |
| // Enable first |
| enableUwbWithCountryCodeChangedCallback(); |
| verify(mNativeUwbManager).doInitialize(); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| verifyNoMoreInteractions(cb); |
| clearInvocations(mNativeUwbManager, mUwbCountryCode, cb); |
| |
| // Disable UWB. |
| disableUwb(); |
| verify(mNativeUwbManager).doDeinitialize(); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_POLICY); |
| verifyNoMoreInteractions(mUwbCountryCode); |
| clearInvocations(mNativeUwbManager, mUwbCountryCode, cb); |
| |
| // Disable again. should be ignored. |
| disableUwb(); |
| verifyNoMoreInteractions(mNativeUwbManager, mUwbCountryCode, cb); |
| } |
| |
| @Test |
| public void testToggleMultipleEnableDisable() throws Exception { |
| when(mNativeUwbManager.doInitialize()).thenReturn(true); |
| when(mNativeUwbManager.doDeinitialize()).thenReturn(true); |
| |
| IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); |
| when(cb.asBinder()).thenReturn(mock(IBinder.class)); |
| mUwbServiceCore.registerAdapterStateCallbacks(cb); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| clearInvocations(cb); |
| |
| // Enable first (with country code initially unknown, like at boot time). |
| enableUwb(); |
| |
| verify(mNativeUwbManager).doInitialize(); |
| verify(mUwbCountryCode).setCountryCode(true); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_POLICY); |
| verifyNoMoreInteractions(cb); |
| |
| clearInvocations(mNativeUwbManager, mUwbCountryCode, cb); |
| |
| // Later on, the onCountryCodeChanged() callback is invoked, with a valid country code. |
| mUwbServiceCore.onCountryCodeChanged(VALID_COUNTRY_CODE); |
| |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| verifyNoMoreInteractions(cb); |
| |
| clearInvocations(mNativeUwbManager, mUwbCountryCode, cb); |
| |
| // Disable UWB. |
| disableUwb(); |
| |
| verify(mNativeUwbManager).doDeinitialize(); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_POLICY); |
| verifyNoMoreInteractions(cb); |
| |
| clearInvocations(mNativeUwbManager, mUwbCountryCode, cb); |
| |
| // Enable again (this time we get the onCountryCodeChanged() callback with a valid |
| // country code as it's known). |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| verify(mNativeUwbManager).doInitialize(); |
| verify(mUwbCountryCode).setCountryCode(anyBoolean()); |
| verify(cb).onAdapterStateChanged( |
| UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| verifyNoMoreInteractions(cb); |
| } |
| |
| @Test |
| public void testToggleMultipleEnableDisableQuickly() throws Exception { |
| when(mNativeUwbManager.doInitialize()).thenReturn(true); |
| when(mNativeUwbManager.doDeinitialize()).thenReturn(true); |
| |
| IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); |
| when(cb.asBinder()).thenReturn(mock(IBinder.class)); |
| mUwbServiceCore.registerAdapterStateCallbacks(cb); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| clearInvocations(mNativeUwbManager, mUwbCountryCode, cb); |
| |
| doAnswer(new Answer() { |
| @Override |
| public Object answer(InvocationOnMock invocation) throws Throwable { |
| mUwbServiceCore.onCountryCodeChanged(VALID_COUNTRY_CODE); |
| return Pair.create(STATUS_CODE_OK, VALID_COUNTRY_CODE); |
| } |
| }).when(mUwbCountryCode).setCountryCode(anyBoolean()); |
| when(mUwbCountryCode.getCountryCode()).thenReturn(VALID_COUNTRY_CODE); |
| |
| // Quickly enqueue a UWB enable followed by a UWB disable message. Both of these will be |
| // processed (in order). |
| mUwbServiceCore.setEnabled(true); |
| mUwbServiceCore.setEnabled(false); |
| |
| // Now process all the looper messages |
| mTestLooper.dispatchAll(); |
| |
| verify(mNativeUwbManager).doInitialize(); |
| verify(mUwbCountryCode).setCountryCode(anyBoolean()); |
| verify(mNativeUwbManager).doDeinitialize(); |
| |
| // We expect one onAdapterStateChanged() call each, for the "UWB Enabled" and final |
| // "UWB Disabled" state. |
| verify(cb, times(1)).onAdapterStateChanged( |
| UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_POLICY); |
| verify(cb, times(1)).onAdapterStateChanged( |
| UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| verifyNoMoreInteractions(cb, mNativeUwbManager); |
| } |
| |
| @Test |
| public void testOpenFiraRanging() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| GenericSpecificationParams genericSpecificationParams = |
| mock(GenericSpecificationParams.class); |
| FiraSpecificationParams firaSpecificationParams = |
| mock(FiraSpecificationParams.class); |
| SessionHandle sessionHandle = mock(SessionHandle.class); |
| IUwbRangingCallbacks cb = mock(IUwbRangingCallbacks.class); |
| AttributionSource attributionSource = TEST_ATTRIBUTION_SOURCE; |
| FiraOpenSessionParams params = TEST_FIRA_OPEN_SESSION_PARAMS.build(); |
| when(mUwbConfigurationManager |
| .getCapsInfo(eq(GenericParams.PROTOCOL_NAME), any(), anyString())) |
| .thenReturn(Pair.create( |
| UwbUciConstants.STATUS_CODE_OK, genericSpecificationParams)); |
| when(genericSpecificationParams.getFiraSpecificationParams()) |
| .thenReturn(firaSpecificationParams); |
| when(firaSpecificationParams.hasRssiReportingSupport()) |
| .thenReturn(true); |
| mUwbServiceCore.openRanging( |
| attributionSource, sessionHandle, cb, params.toBundle(), TEST_DEFAULT_CHIP_ID); |
| |
| verify(mUwbSessionManager).initSession( |
| eq(attributionSource), |
| eq(sessionHandle), eq(params.getSessionId()), eq((byte) params.getSessionType()), |
| eq(FiraParams.PROTOCOL_NAME), |
| argThat(p -> ((FiraOpenSessionParams) p).getSessionId() == params.getSessionId()), |
| eq(cb), eq(TEST_DEFAULT_CHIP_ID)); |
| |
| } |
| |
| @Test |
| public void testOpenCccRanging() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| SessionHandle sessionHandle = mock(SessionHandle.class); |
| IUwbRangingCallbacks cb = mock(IUwbRangingCallbacks.class); |
| CccOpenRangingParams params = TEST_CCC_OPEN_RANGING_PARAMS.build(); |
| AttributionSource attributionSource = TEST_ATTRIBUTION_SOURCE; |
| mUwbServiceCore.openRanging( |
| attributionSource, sessionHandle, cb, params.toBundle(), TEST_DEFAULT_CHIP_ID); |
| |
| verify(mUwbSessionManager).initSession( |
| eq(attributionSource), |
| eq(sessionHandle), eq(params.getSessionId()), eq((byte) params.getSessionType()), |
| eq(CccParams.PROTOCOL_NAME), |
| argThat(p -> ((CccOpenRangingParams) p).getSessionId() == params.getSessionId()), |
| eq(cb), eq(TEST_DEFAULT_CHIP_ID)); |
| } |
| |
| @Test |
| public void testOpenRangingWhenUwbDisabled() throws Exception { |
| SessionHandle sessionHandle = mock(SessionHandle.class); |
| IUwbRangingCallbacks cb = mock(IUwbRangingCallbacks.class); |
| CccOpenRangingParams params = TEST_CCC_OPEN_RANGING_PARAMS.build(); |
| AttributionSource attributionSource = TEST_ATTRIBUTION_SOURCE; |
| |
| try { |
| mUwbServiceCore.openRanging(attributionSource, |
| sessionHandle, |
| cb, |
| params.toBundle(), |
| TEST_DEFAULT_CHIP_ID); |
| fail(); |
| } catch (IllegalStateException e) { |
| // pass |
| } |
| |
| // Should be ignored. |
| verifyNoMoreInteractions(mUwbSessionManager); |
| } |
| |
| @Test |
| public void testStartCccRanging() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| SessionHandle sessionHandle = mock(SessionHandle.class); |
| CccStartRangingParams params = new CccStartRangingParams.Builder() |
| .setRanMultiplier(6) |
| .setSessionId(1) |
| .build(); |
| mUwbServiceCore.startRanging(sessionHandle, params.toBundle()); |
| |
| verify(mUwbSessionManager).startRanging(eq(sessionHandle), |
| argThat(p -> ((CccStartRangingParams) p).getSessionId() == params.getSessionId())); |
| } |
| |
| @Test |
| public void testStartCccRangingWithNoStartParams() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| SessionHandle sessionHandle = mock(SessionHandle.class); |
| mUwbServiceCore.startRanging(sessionHandle, new PersistableBundle()); |
| |
| verify(mUwbSessionManager).startRanging(eq(sessionHandle), argThat(p -> (p == null))); |
| } |
| |
| @Test |
| public void testReconfigureRanging() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| SessionHandle sessionHandle = mock(SessionHandle.class); |
| final FiraRangingReconfigureParams parameters = |
| new FiraRangingReconfigureParams.Builder() |
| .setBlockStrideLength(6) |
| .setRangeDataNtfConfig(RANGE_DATA_NTF_CONFIG_ENABLE_PROXIMITY_LEVEL_TRIG) |
| .setRangeDataProximityFar(6) |
| .setRangeDataProximityNear(4) |
| .build(); |
| mUwbServiceCore.reconfigureRanging(sessionHandle, parameters.toBundle()); |
| verify(mUwbSessionManager).reconfigure(eq(sessionHandle), |
| argThat((x) -> |
| ((FiraRangingReconfigureParams) x).getBlockStrideLength().equals(6))); |
| } |
| |
| @Test |
| public void testSendData_success() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| SessionHandle sessionHandle = mock(SessionHandle.class); |
| UwbAddress uwbAddress = UwbAddress.fromBytes(new byte[] {15, 27}); |
| PersistableBundle params = mock(PersistableBundle.class); |
| byte[] data = new byte[] {1, 3, 5, 7, 11, 13}; |
| |
| mUwbServiceCore.sendData(sessionHandle, uwbAddress, params, data); |
| verify(mUwbSessionManager).sendData( |
| eq(sessionHandle), eq(uwbAddress), eq(params), eq(data)); |
| } |
| |
| @Test |
| public void testSendData_whenUwbIsDisabled() throws Exception { |
| disableUwb(); |
| |
| SessionHandle sessionHandle = mock(SessionHandle.class); |
| UwbAddress uwbAddress = UwbAddress.fromBytes(new byte[] {15, 27}); |
| PersistableBundle params = mock(PersistableBundle.class); |
| byte[] data = new byte[] {1, 3, 5, 7, 11, 13}; |
| |
| try { |
| mUwbServiceCore.sendData(sessionHandle, uwbAddress, params, data); |
| fail(); |
| } catch (IllegalStateException e) { } |
| } |
| |
| @Test |
| public void testAddControlee() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| SessionHandle sessionHandle = mock(SessionHandle.class); |
| UwbAddress uwbAddress1 = UwbAddress.fromBytes(new byte[] {1, 2}); |
| UwbAddress uwbAddress2 = UwbAddress.fromBytes(new byte[] {4, 5}); |
| UwbAddress[] addressList = new UwbAddress[] {uwbAddress1, uwbAddress2}; |
| int[] subSessionIdList = new int[] {3, 4}; |
| FiraControleeParams params = |
| new FiraControleeParams.Builder() |
| .setAddressList(addressList) |
| .setSubSessionIdList(subSessionIdList) |
| .build(); |
| |
| mUwbServiceCore.addControlee(sessionHandle, params.toBundle()); |
| verify(mUwbSessionManager).reconfigure(eq(sessionHandle), |
| argThat((x) -> { |
| FiraRangingReconfigureParams reconfigureParams = |
| (FiraRangingReconfigureParams) x; |
| return reconfigureParams.getAction().equals(MULTICAST_LIST_UPDATE_ACTION_ADD) |
| && Arrays.equals( |
| reconfigureParams.getAddressList(), params.getAddressList()) |
| && Arrays.equals( |
| reconfigureParams.getSubSessionIdList(), |
| params.getSubSessionIdList()); |
| })); |
| } |
| |
| @Test |
| public void testRemoveControlee() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| SessionHandle sessionHandle = mock(SessionHandle.class); |
| UwbAddress uwbAddress1 = UwbAddress.fromBytes(new byte[] {1, 2}); |
| UwbAddress uwbAddress2 = UwbAddress.fromBytes(new byte[] {4, 5}); |
| UwbAddress[] addressList = new UwbAddress[] {uwbAddress1, uwbAddress2}; |
| int[] subSessionIdList = new int[] {3, 4}; |
| FiraControleeParams params = |
| new FiraControleeParams.Builder() |
| .setAddressList(addressList) |
| .setSubSessionIdList(subSessionIdList) |
| .build(); |
| |
| mUwbServiceCore.removeControlee(sessionHandle, params.toBundle()); |
| verify(mUwbSessionManager).reconfigure(eq(sessionHandle), |
| argThat((x) -> { |
| FiraRangingReconfigureParams reconfigureParams = |
| (FiraRangingReconfigureParams) x; |
| return reconfigureParams.getAction().equals(MULTICAST_LIST_UPDATE_ACTION_DELETE) |
| && Arrays.equals( |
| reconfigureParams.getAddressList(), params.getAddressList()) |
| && Arrays.equals( |
| reconfigureParams.getSubSessionIdList(), |
| params.getSubSessionIdList()); |
| })); |
| } |
| |
| @Test |
| public void testStopRanging() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| SessionHandle sessionHandle = mock(SessionHandle.class); |
| mUwbServiceCore.stopRanging(sessionHandle); |
| |
| verify(mUwbSessionManager).stopRanging(sessionHandle); |
| } |
| |
| |
| @Test |
| public void testCloseRanging() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| SessionHandle sessionHandle = mock(SessionHandle.class); |
| mUwbServiceCore.closeRanging(sessionHandle); |
| |
| verify(mUwbSessionManager).deInitSession(sessionHandle); |
| } |
| |
| @Test |
| public void testGetAdapterState() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| assertThat(mUwbServiceCore.getAdapterState()) |
| .isEqualTo(AdapterState.STATE_ENABLED_INACTIVE); |
| |
| disableUwb(); |
| assertThat(mUwbServiceCore.getAdapterState()) |
| .isEqualTo(AdapterState.STATE_DISABLED); |
| } |
| |
| @Test |
| public void testGetAdapterState_multichip() throws Exception { |
| UwbMultichipData multichipData = setUpMultichipDataForTwoChips(); |
| when(mUwbInjector.getMultichipData()).thenReturn(multichipData); |
| |
| mUwbServiceCore = new UwbServiceCore(mContext, mNativeUwbManager, mUwbMetrics, |
| mUwbCountryCode, mUwbSessionManager, mUwbConfigurationManager, |
| mUwbInjector, mTestLooper.getLooper()); |
| multichipData.initialize(); |
| |
| enableUwbWithCountryCodeChangedCallback(); |
| assertThat(mUwbServiceCore.getAdapterState()) |
| .isEqualTo(AdapterState.STATE_ENABLED_INACTIVE); |
| |
| // If one chip is active, then getAdapterState should return STATE_ENABLED_ACTIVE. |
| mUwbServiceCore.onDeviceStatusNotificationReceived(UwbUciConstants.DEVICE_STATE_ACTIVE, |
| TEST_CHIP_ONE_CHIP_ID); |
| mTestLooper.dispatchAll(); |
| assertThat(mUwbServiceCore.getAdapterState()).isEqualTo(AdapterState.STATE_ENABLED_ACTIVE); |
| |
| disableUwb(); |
| assertThat(mUwbServiceCore.getAdapterState()) |
| .isEqualTo(AdapterState.STATE_DISABLED); |
| |
| // If one chip is disabled, then getAdapter state should always return STATE_DISABLED. |
| // (Although in practice, there should never be one ACTIVE chip and one DISABLED chip.) |
| mUwbServiceCore.onDeviceStatusNotificationReceived(UwbUciConstants.DEVICE_STATE_ACTIVE, |
| TEST_CHIP_ONE_CHIP_ID); |
| mTestLooper.dispatchAll(); |
| assertThat(mUwbServiceCore.getAdapterState()).isEqualTo(AdapterState.STATE_DISABLED); |
| } |
| |
| @Test |
| public void testGetAdapterState_noChips() throws Exception { |
| // Create a new UwbServiceCore instance without initializing multichip data or enabling UWB |
| mUwbServiceCore = new UwbServiceCore(mContext, mNativeUwbManager, mUwbMetrics, |
| mUwbCountryCode, mUwbSessionManager, mUwbConfigurationManager, |
| mUwbInjector, mTestLooper.getLooper()); |
| |
| assertThat(mUwbServiceCore.getAdapterState()).isEqualTo(AdapterState.STATE_DISABLED); |
| } |
| |
| |
| @Test |
| public void testSendVendorUciCommand() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| int gid = 0; |
| int oid = 0; |
| byte[] payload = new byte[0]; |
| UwbVendorUciResponse rsp = new UwbVendorUciResponse( |
| (byte) UwbUciConstants.STATUS_CODE_OK, gid, oid, payload); |
| when(mNativeUwbManager.sendRawVendorCmd(anyInt(), anyInt(), anyInt(), any(), anyString())) |
| .thenReturn(rsp); |
| |
| IUwbVendorUciCallback vendorCb = mock(IUwbVendorUciCallback.class); |
| mUwbServiceCore.registerVendorExtensionCallback(vendorCb); |
| |
| assertThat(mUwbServiceCore.sendVendorUciMessage(1, 0, 0, new byte[0], |
| TEST_DEFAULT_CHIP_ID)) |
| .isEqualTo(UwbUciConstants.STATUS_CODE_OK); |
| |
| verify(vendorCb).onVendorResponseReceived(gid, oid, payload); |
| } |
| |
| @Test |
| public void testSendVendorUciCommandMessageTypeTest() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| int gid = 0; |
| int oid = 0; |
| byte[] payload = new byte[0]; |
| UwbVendorUciResponse rsp = new UwbVendorUciResponse( |
| (byte) UwbUciConstants.STATUS_CODE_OK, gid, oid, payload); |
| when(mNativeUwbManager.sendRawVendorCmd(anyInt(), anyInt(), anyInt(), any(), anyString())) |
| .thenReturn(rsp); |
| FiraProtocolVersion maxMacVersionSupported = new FiraProtocolVersion(2, 0); |
| List<Integer> supportedChannels = List.of(5, 6, 8, 9); |
| FiraSpecificationParams firaSpecificationParams = new FiraSpecificationParams.Builder() |
| .setMaxMacVersionSupported(maxMacVersionSupported) |
| .setSupportedChannels(supportedChannels) |
| .build(); |
| CccSpecificationParams cccSpecificationParams = getTestCccSpecificationParams(); |
| |
| GenericSpecificationParams genericSpecificationParams = |
| new GenericSpecificationParams.Builder() |
| .setFiraSpecificationParams(firaSpecificationParams) |
| .setCccSpecificationParams(cccSpecificationParams) |
| .build(); |
| when(mUwbConfigurationManager.getCapsInfo(any(), any(), anyString())) |
| .thenReturn(Pair.create( |
| UwbUciConstants.STATUS_CODE_OK, genericSpecificationParams)); |
| |
| IUwbVendorUciCallback vendorCb = mock(IUwbVendorUciCallback.class); |
| mUwbServiceCore.registerVendorExtensionCallback(vendorCb); |
| |
| assertThat(mUwbServiceCore.sendVendorUciMessage(MESSAGE_TYPE_TEST_1, 0, 0, |
| new byte[0], TEST_DEFAULT_CHIP_ID)) |
| .isEqualTo(UwbUciConstants.STATUS_CODE_OK); |
| |
| verify(vendorCb).onVendorResponseReceived(gid, oid, payload); |
| } |
| |
| @Test |
| public void testSendVendorUciCommandUnsupportedMessageType() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| List<Integer> supportedChannels = List.of(5, 6, 8, 9); |
| FiraSpecificationParams firaSpecificationParams = new FiraSpecificationParams.Builder() |
| .setSupportedChannels(supportedChannels) |
| .build(); |
| CccSpecificationParams cccSpecificationParams = getTestCccSpecificationParams(); |
| GenericSpecificationParams genericSpecificationParams = |
| new GenericSpecificationParams.Builder() |
| .setFiraSpecificationParams(firaSpecificationParams) |
| .setCccSpecificationParams(cccSpecificationParams) |
| .build(); |
| when(mUwbConfigurationManager.getCapsInfo(any(), any(), anyString())) |
| .thenReturn(Pair.create( |
| UwbUciConstants.STATUS_CODE_OK, genericSpecificationParams)); |
| IUwbVendorUciCallback vendorCb = mock(IUwbVendorUciCallback.class); |
| mUwbServiceCore.registerVendorExtensionCallback(vendorCb); |
| |
| assertThat(mUwbServiceCore.sendVendorUciMessage(MESSAGE_TYPE_TEST_1, 0, 0, |
| new byte[0], TEST_DEFAULT_CHIP_ID)) |
| .isEqualTo(STATUS_CODE_FAILED); |
| } |
| |
| @Test |
| public void testQueryDataSize() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| SessionHandle sessionHandle = mock(SessionHandle.class); |
| |
| when(mUwbSessionManager.queryMaxDataSizeBytes(any())).thenReturn(MAX_DATA_SIZE); |
| mUwbServiceCore.queryMaxDataSizeBytes(sessionHandle); |
| verify(mUwbSessionManager).queryMaxDataSizeBytes(eq(sessionHandle)); |
| } |
| |
| @Test |
| public void testDeviceStateCallback() throws Exception { |
| IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); |
| when(cb.asBinder()).thenReturn(mock(IBinder.class)); |
| mUwbServiceCore.registerAdapterStateCallbacks(cb); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| clearInvocations(cb); |
| |
| // Confirm a STATE_ENABLED_INACTIVE AdapterState notification is received when UWB is |
| // enabled with a valid country code. |
| enableUwbWithCountryCodeChangedCallback(); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| verifyNoMoreInteractions(cb); |
| clearInvocations(cb); |
| |
| // Confirm a STATE_ENABLED_ACTIVE AdapterState notification is received when a UWB ranging |
| // session is active. |
| when(mUwbCountryCode.getCountryCode()).thenReturn("US"); |
| mUwbServiceCore.onDeviceStatusNotificationReceived(UwbUciConstants.DEVICE_STATE_ACTIVE, |
| TEST_DEFAULT_CHIP_ID); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_ACTIVE, |
| StateChangeReason.SESSION_STARTED); |
| verifyNoMoreInteractions(cb); |
| } |
| |
| @Test |
| public void testMultipleDeviceStateCallbacks() throws Exception { |
| IUwbAdapterStateCallbacks cb1 = mock(IUwbAdapterStateCallbacks.class); |
| when(cb1.asBinder()).thenReturn(mock(IBinder.class)); |
| IUwbAdapterStateCallbacks cb2 = mock(IUwbAdapterStateCallbacks.class); |
| when(cb2.asBinder()).thenReturn(mock(IBinder.class)); |
| |
| mUwbServiceCore.registerAdapterStateCallbacks(cb1); |
| verify(cb1).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| |
| mUwbServiceCore.registerAdapterStateCallbacks(cb2); |
| verify(cb2).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| |
| enableUwbWithCountryCodeChangedCallback(); |
| verify(cb1).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| verify(cb2).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| |
| when(mUwbCountryCode.getCountryCode()).thenReturn("US"); |
| mUwbServiceCore.onDeviceStatusNotificationReceived(UwbUciConstants.DEVICE_STATE_ACTIVE, |
| TEST_DEFAULT_CHIP_ID); |
| mTestLooper.dispatchAll(); |
| verify(cb1).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_ACTIVE, |
| StateChangeReason.SESSION_STARTED); |
| verify(cb2).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_ACTIVE, |
| StateChangeReason.SESSION_STARTED); |
| |
| mUwbServiceCore.unregisterAdapterStateCallbacks(cb1); |
| mUwbServiceCore.unregisterAdapterStateCallbacks(cb2); |
| } |
| |
| @Test |
| public void testDeviceStateCallback_invalidChipId() throws Exception { |
| IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); |
| when(cb.asBinder()).thenReturn(mock(IBinder.class)); |
| mUwbServiceCore.registerAdapterStateCallbacks(cb); |
| |
| enableUwbWithCountryCodeChangedCallback(); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| |
| mUwbServiceCore.onDeviceStatusNotificationReceived(UwbUciConstants.DEVICE_STATE_ACTIVE, |
| "invalidChipId"); |
| verify(cb, never()) |
| .onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_ACTIVE, |
| StateChangeReason.SESSION_STARTED); |
| } |
| |
| @Test |
| public void testToggleOfOnDeviceStateErrorCallback_whenCountryCodeIsValid() throws Exception { |
| IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); |
| when(cb.asBinder()).thenReturn(mock(IBinder.class)); |
| mUwbServiceCore.registerAdapterStateCallbacks(cb); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| clearInvocations(cb); |
| |
| // Enable UWB, with a valid country code. |
| enableUwbWithCountryCodeChangedCallback(); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| |
| when(mNativeUwbManager.doDeinitialize()).thenReturn(true); |
| when(mNativeUwbManager.doInitialize()).thenReturn(true); |
| |
| clearInvocations(mNativeUwbManager, mUwbCountryCode, cb); |
| |
| // In the second toggle-on iteration, there should again be a valid country code known to |
| // UwbCountryCode class (from earlier notifications from the Wifi/Telephony stack). |
| mUwbServiceCore.onDeviceStatusNotificationReceived(UwbUciConstants.DEVICE_STATE_ERROR, |
| TEST_DEFAULT_CHIP_ID); |
| mTestLooper.dispatchAll(); |
| |
| // Verify UWB toggle off. |
| verify(mNativeUwbManager).doDeinitialize(); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_POLICY); |
| |
| // Verify UWB toggle on. |
| verify(mNativeUwbManager).doInitialize(); |
| verify(cb).onAdapterStateChanged( |
| UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| } |
| |
| @Test |
| public void testToggleOfOnDeviceStateErrorCallback_whenCountryCodeIsInvalid_setCountryFailure() |
| throws Exception { |
| IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); |
| when(cb.asBinder()).thenReturn(mock(IBinder.class)); |
| |
| mUwbServiceCore.registerAdapterStateCallbacks(cb); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| clearInvocations(mNativeUwbManager, mUwbCountryCode, cb); |
| |
| // UWB Adapter State will internally be Enabled, but we expect an AdapterState notification |
| // with State DISABLED to be sent (as the country code is not valid). |
| enableUwb(); |
| verify(cb).onAdapterStateChanged( |
| UwbManager.AdapterStateCallback.STATE_DISABLED, StateChangeReason.SYSTEM_POLICY); |
| verifyNoMoreInteractions(cb); |
| clearInvocations(mNativeUwbManager, mUwbCountryCode, cb); |
| |
| // UWB Enable is expected to result in setting the country code; but that may fail and the |
| // onCountryCode() callback is not invoked, as the country code is not valid. |
| when(mUwbCountryCode.setCountryCode(anyBoolean())).thenReturn( |
| Pair.create(STATUS_CODE_FAILED, null)); |
| when(mNativeUwbManager.doDeinitialize()).thenReturn(true); |
| when(mNativeUwbManager.doInitialize()).thenReturn(true); |
| |
| mUwbServiceCore.onDeviceStatusNotificationReceived(UwbUciConstants.DEVICE_STATE_ERROR, |
| TEST_DEFAULT_CHIP_ID); |
| mTestLooper.dispatchAll(); |
| |
| // Verify UWB is first toggled off and then on. There should be no AdapterStateCallback |
| // sent, as the country code is invalid and so the AdapterState for notification remains |
| // the same as before (STATE_DISABLED). |
| verify(mNativeUwbManager).doDeinitialize(); |
| verify(mNativeUwbManager).doInitialize(); |
| verifyNoMoreInteractions(cb); |
| } |
| |
| @Test |
| public void testToggleOfOnDeviceStateErrorCallback_invalidChipId() throws Exception { |
| IUwbAdapterStateCallbacks cb = mock(IUwbAdapterStateCallbacks.class); |
| when(cb.asBinder()).thenReturn(mock(IBinder.class)); |
| mUwbServiceCore.registerAdapterStateCallbacks(cb); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, |
| StateChangeReason.SYSTEM_BOOT); |
| |
| // Enable UWB to initialize state. |
| enableUwbWithCountryCodeChangedCallback(); |
| verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE, |
| StateChangeReason.SYSTEM_POLICY); |
| clearInvocations(mNativeUwbManager, mUwbCountryCode, cb); |
| |
| when(mNativeUwbManager.doDeinitialize()).thenReturn(true); |
| when(mNativeUwbManager.doInitialize()).thenReturn(true); |
| |
| mUwbServiceCore.onDeviceStatusNotificationReceived(UwbUciConstants.DEVICE_STATE_ERROR, |
| "invalidChipId"); |
| mTestLooper.dispatchAll(); |
| |
| // Verify there are no more UWB stack or state updates (since chipId is invalid). |
| verifyNoMoreInteractions(mNativeUwbManager, mUwbCountryCode, cb); |
| } |
| |
| @Test |
| public void testVendorUciNotificationCallback() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| IUwbVendorUciCallback vendorCb = mock(IUwbVendorUciCallback.class); |
| mUwbServiceCore.registerVendorExtensionCallback(vendorCb); |
| int gid = 0; |
| int oid = 0; |
| byte[] payload = new byte[0]; |
| mUwbServiceCore.onVendorUciNotificationReceived(gid, oid, payload); |
| verify(vendorCb).onVendorNotificationReceived(gid, oid, payload); |
| |
| mUwbServiceCore.unregisterVendorExtensionCallback(vendorCb); |
| mUwbServiceCore.onVendorUciNotificationReceived(gid, oid, payload); |
| verifyZeroInteractions(vendorCb); |
| } |
| |
| @Test |
| public void testReportUwbActivityEnergyInfo() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| IOnUwbActivityEnergyInfoListener listener = mock(IOnUwbActivityEnergyInfoListener.class); |
| mUwbServiceCore.reportUwbActivityEnergyInfo(listener); |
| mTestLooper.dispatchAll(); |
| verify(listener).onUwbActivityEnergyInfo(any()); |
| } |
| |
| @Test |
| public void testOemExtensionCallback_registerUnregister() throws RemoteException { |
| IUwbOemExtensionCallback oemExtensionCb = mock(IUwbOemExtensionCallback.class); |
| |
| assertFalse(mUwbServiceCore.isOemExtensionCbRegistered()); |
| assertThat(mUwbServiceCore.getOemExtensionCallback()).isNull(); |
| |
| mUwbServiceCore.registerOemExtensionCallback(oemExtensionCb); |
| assertTrue(mUwbServiceCore.isOemExtensionCbRegistered()); |
| assertThat(mUwbServiceCore.getOemExtensionCallback()).isNotNull(); |
| |
| mUwbServiceCore.updateDeviceState(0, ""); |
| verify(oemExtensionCb).onDeviceStatusNotificationReceived(any()); |
| |
| mUwbServiceCore.unregisterOemExtensionCallback(oemExtensionCb); |
| |
| assertFalse(mUwbServiceCore.isOemExtensionCbRegistered()); |
| assertThat(mUwbServiceCore.getOemExtensionCallback()).isNull(); |
| } |
| |
| @Test |
| public void testRangingRoundsUpdateDtTag() throws Exception { |
| enableUwbWithCountryCodeChangedCallback(); |
| |
| SessionHandle sessionHandle = mock(SessionHandle.class); |
| PersistableBundle bundle = new PersistableBundle(); |
| mUwbServiceCore.rangingRoundsUpdateDtTag(sessionHandle, bundle); |
| |
| verify(mUwbSessionManager).rangingRoundsUpdateDtTag(sessionHandle, bundle); |
| } |
| |
| public CccSpecificationParams getTestCccSpecificationParams() { |
| CccProtocolVersion[] protocolVersions = |
| new CccProtocolVersion[] { |
| new CccProtocolVersion(1, 0), |
| new CccProtocolVersion(2, 0), |
| new CccProtocolVersion(2, 1) |
| }; |
| |
| Integer[] uwbConfigs = new Integer[] {CccParams.UWB_CONFIG_0, CccParams.UWB_CONFIG_1}; |
| CccPulseShapeCombo[] pulseShapeCombos = |
| new CccPulseShapeCombo[] { |
| new CccPulseShapeCombo( |
| CccParams.PULSE_SHAPE_SYMMETRICAL_ROOT_RAISED_COSINE, |
| CccParams.PULSE_SHAPE_SYMMETRICAL_ROOT_RAISED_COSINE), |
| new CccPulseShapeCombo( |
| CccParams.PULSE_SHAPE_PRECURSOR_FREE, |
| CccParams.PULSE_SHAPE_PRECURSOR_FREE), |
| new CccPulseShapeCombo( |
| CccParams.PULSE_SHAPE_PRECURSOR_FREE_SPECIAL, |
| CccParams.PULSE_SHAPE_PRECURSOR_FREE_SPECIAL) |
| }; |
| int ranMultiplier = 200; |
| Integer[] chapsPerSlots = |
| new Integer[] {CccParams.CHAPS_PER_SLOT_4, CccParams.CHAPS_PER_SLOT_12}; |
| Integer[] syncCodes = |
| new Integer[] {10, 23}; |
| Integer[] channels = new Integer[] {CccParams.UWB_CHANNEL_5, CccParams.UWB_CHANNEL_9}; |
| Integer[] hoppingConfigModes = |
| new Integer[] { |
| CccParams.HOPPING_CONFIG_MODE_ADAPTIVE, |
| CccParams.HOPPING_CONFIG_MODE_CONTINUOUS }; |
| Integer[] hoppingSequences = |
| new Integer[] {CccParams.HOPPING_SEQUENCE_AES, CccParams.HOPPING_SEQUENCE_DEFAULT}; |
| |
| CccSpecificationParams.Builder paramsBuilder = new CccSpecificationParams.Builder(); |
| for (CccProtocolVersion p : protocolVersions) { |
| paramsBuilder.addProtocolVersion(p); |
| } |
| |
| for (int uwbConfig : uwbConfigs) { |
| paramsBuilder.addUwbConfig(uwbConfig); |
| } |
| |
| for (CccPulseShapeCombo pulseShapeCombo : pulseShapeCombos) { |
| paramsBuilder.addPulseShapeCombo(pulseShapeCombo); |
| } |
| |
| paramsBuilder.setRanMultiplier(ranMultiplier); |
| |
| for (int chapsPerSlot : chapsPerSlots) { |
| paramsBuilder.addChapsPerSlot(chapsPerSlot); |
| } |
| |
| for (int syncCode : syncCodes) { |
| paramsBuilder.addSyncCode(syncCode); |
| } |
| |
| for (int channel : channels) { |
| paramsBuilder.addChannel(channel); |
| } |
| |
| for (int hoppingConfigMode : hoppingConfigModes) { |
| paramsBuilder.addHoppingConfigMode(hoppingConfigMode); |
| } |
| |
| for (int hoppingSequence : hoppingSequences) { |
| paramsBuilder.addHoppingSequence(hoppingSequence); |
| } |
| return paramsBuilder.build(); |
| } |
| } |