1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.uwb; 18 19 import static android.uwb.RangingSession.Callback.REASON_LOCAL_REQUEST; 20 21 import static com.android.server.uwb.UwbShellCommand.DEFAULT_CCC_OPEN_RANGING_PARAMS; 22 import static com.android.server.uwb.UwbShellCommand.DEFAULT_FIRA_OPEN_SESSION_PARAMS; 23 24 import static com.google.common.truth.Truth.assertThat; 25 import static com.google.uwb.support.fira.FiraParams.RangeDataNtfConfigCapabilityFlag.HAS_RANGE_DATA_NTF_CONFIG_DISABLE; 26 import static com.google.uwb.support.fira.FiraParams.RangeDataNtfConfigCapabilityFlag.HAS_RANGE_DATA_NTF_CONFIG_ENABLE; 27 28 import static org.mockito.ArgumentMatchers.anyInt; 29 import static org.mockito.Mockito.any; 30 import static org.mockito.Mockito.clearInvocations; 31 import static org.mockito.Mockito.doAnswer; 32 import static org.mockito.Mockito.eq; 33 import static org.mockito.Mockito.mock; 34 import static org.mockito.Mockito.never; 35 import static org.mockito.Mockito.validateMockitoUsage; 36 import static org.mockito.Mockito.verify; 37 import static org.mockito.Mockito.when; 38 39 import android.annotation.NonNull; 40 import android.annotation.Nullable; 41 import android.content.AttributionSource; 42 import android.content.Context; 43 import android.os.Binder; 44 import android.os.PersistableBundle; 45 import android.os.Process; 46 import android.util.Pair; 47 import android.uwb.IUwbRangingCallbacks; 48 import android.uwb.RangingMeasurement; 49 import android.uwb.RangingReport; 50 import android.uwb.SessionHandle; 51 import android.uwb.UwbManager; 52 import android.uwb.UwbTestUtils; 53 54 import androidx.test.filters.SmallTest; 55 import androidx.test.runner.AndroidJUnit4; 56 57 import com.google.uwb.support.base.Params; 58 import com.google.uwb.support.ccc.CccOpenRangingParams; 59 import com.google.uwb.support.ccc.CccSpecificationParams; 60 import com.google.uwb.support.ccc.CccStartRangingParams; 61 import com.google.uwb.support.fira.FiraOpenSessionParams; 62 import com.google.uwb.support.fira.FiraSpecificationParams; 63 import com.google.uwb.support.generic.GenericSpecificationParams; 64 65 import org.junit.After; 66 import org.junit.Before; 67 import org.junit.Test; 68 import org.junit.runner.RunWith; 69 import org.mockito.ArgumentCaptor; 70 import org.mockito.Mock; 71 import org.mockito.MockitoAnnotations; 72 73 import java.io.FileDescriptor; 74 import java.util.EnumSet; 75 import java.util.List; 76 import java.util.concurrent.FutureTask; 77 78 /** 79 * Unit tests for {@link com.android.server.uwb.UwbShellCommand}. 80 */ 81 @RunWith(AndroidJUnit4.class) 82 @SmallTest 83 public class UwbShellCommandTest { 84 private static final String TEST_PACKAGE = "com.android.test"; 85 86 @Mock UwbInjector mUwbInjector; 87 @Mock UwbServiceImpl mUwbService; 88 @Mock UwbCountryCode mUwbCountryCode; 89 @Mock Context mContext; 90 @Mock UwbServiceCore mUwbServiceCore; 91 92 UwbShellCommand mUwbShellCommand; 93 94 @Before setUp()95 public void setUp() throws Exception { 96 MockitoAnnotations.initMocks(this); 97 98 when(mUwbInjector.getUwbCountryCode()).thenReturn(mUwbCountryCode); 99 when(mUwbInjector.getUwbServiceCore()).thenReturn(mUwbServiceCore); 100 doAnswer(invocation -> { 101 FutureTask t = invocation.getArgument(0); 102 t.run(); 103 return t.get(); 104 }).when(mUwbInjector).runTaskOnSingleThreadExecutor(any(FutureTask.class), anyInt()); 105 GenericSpecificationParams params = new GenericSpecificationParams.Builder() 106 .setCccSpecificationParams(mock(CccSpecificationParams.class)) 107 .setFiraSpecificationParams( 108 new FiraSpecificationParams.Builder() 109 .setSupportedChannels(List.of(9)) 110 .setRangeDataNtfConfigCapabilities( 111 EnumSet.of( 112 HAS_RANGE_DATA_NTF_CONFIG_DISABLE, 113 HAS_RANGE_DATA_NTF_CONFIG_ENABLE)) 114 .build()) 115 .build(); 116 when(mUwbServiceCore.getCachedSpecificationParams(any())).thenReturn(params); 117 118 mUwbShellCommand = new UwbShellCommand(mUwbInjector, mUwbService, mContext); 119 120 // by default emulate shell uid. 121 BinderUtil.setUid(Process.SHELL_UID); 122 } 123 124 @After tearDown()125 public void tearDown() throws Exception { 126 mUwbShellCommand.reset(); 127 validateMockitoUsage(); 128 } 129 130 @Test testStatus()131 public void testStatus() throws Exception { 132 when(mUwbService.getAdapterState()) 133 .thenReturn(UwbManager.AdapterStateCallback.STATE_ENABLED_ACTIVE); 134 135 // unrooted shell. 136 mUwbShellCommand.exec( 137 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(), 138 new String[]{"status"}); 139 verify(mUwbService).getAdapterState(); 140 } 141 142 @Test testForceSetCountryCode()143 public void testForceSetCountryCode() throws Exception { 144 // not allowed for unrooted shell. 145 mUwbShellCommand.exec( 146 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(), 147 new String[]{"force-country-code", "enabled", "US"}); 148 verify(mUwbCountryCode, never()).setOverrideCountryCode(any()); 149 assertThat(mUwbShellCommand.getErrPrintWriter().toString().isEmpty()).isFalse(); 150 151 BinderUtil.setUid(Process.ROOT_UID); 152 153 // rooted shell. 154 mUwbShellCommand.exec( 155 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(), 156 new String[]{"force-country-code", "enabled", "US"}); 157 verify(mUwbCountryCode).setOverrideCountryCode(any()); 158 159 } 160 161 @Test testForceClearCountryCode()162 public void testForceClearCountryCode() throws Exception { 163 // not allowed for unrooted shell. 164 mUwbShellCommand.exec( 165 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(), 166 new String[]{"force-country-code", "disabled"}); 167 verify(mUwbCountryCode, never()).setOverrideCountryCode(any()); 168 assertThat(mUwbShellCommand.getErrPrintWriter().toString().isEmpty()).isFalse(); 169 170 BinderUtil.setUid(Process.ROOT_UID); 171 172 // rooted shell. 173 mUwbShellCommand.exec( 174 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(), 175 new String[]{"force-country-code", "disabled"}); 176 verify(mUwbCountryCode).clearOverrideCountryCode(); 177 } 178 179 @Test testGetCountryCode()180 public void testGetCountryCode() throws Exception { 181 mUwbShellCommand.exec( 182 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(), 183 new String[]{"get-country-code"}); 184 verify(mUwbCountryCode).getCountryCode(); 185 } 186 187 @Test testEnableUwb()188 public void testEnableUwb() throws Exception { 189 mUwbShellCommand.exec( 190 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(), 191 new String[]{"enable-uwb"}); 192 verify(mUwbService).setEnabled(true); 193 } 194 195 @Test testDisableUwb()196 public void testDisableUwb() throws Exception { 197 mUwbShellCommand.exec( 198 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(), 199 new String[]{"disable-uwb"}); 200 verify(mUwbService).setEnabled(false); 201 } 202 203 private static class MutableCb { 204 @Nullable public IUwbRangingCallbacks cb; 205 } 206 triggerAndVerifyRangingStart( String[] rangingStartCmd, @NonNull Params openRangingParams)207 private Pair<IUwbRangingCallbacks, SessionHandle> triggerAndVerifyRangingStart( 208 String[] rangingStartCmd, @NonNull Params openRangingParams) throws Exception { 209 return triggerAndVerifyRangingStart(rangingStartCmd, openRangingParams, null); 210 } 211 triggerAndVerifyRangingStart( String[] rangingStartCmd, @NonNull Params openRangingParams, @Nullable Params startRangingParams)212 private Pair<IUwbRangingCallbacks, SessionHandle> triggerAndVerifyRangingStart( 213 String[] rangingStartCmd, @NonNull Params openRangingParams, @Nullable Params 214 startRangingParams) throws Exception { 215 final MutableCb cbCaptor = new MutableCb(); 216 doAnswer(invocation -> { 217 cbCaptor.cb = invocation.getArgument(2); 218 cbCaptor.cb.onRangingOpened(invocation.getArgument(1)); 219 return true; 220 }).when(mUwbService).openRanging(any(), any(), any(), any(), any()); 221 doAnswer(invocation -> { 222 cbCaptor.cb.onRangingStarted(invocation.getArgument(0), new PersistableBundle()); 223 return true; 224 }).when(mUwbService).startRanging(any(), any()); 225 226 mUwbShellCommand.exec( 227 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(), 228 rangingStartCmd); 229 230 ArgumentCaptor<SessionHandle> sessionHandleCaptor = 231 ArgumentCaptor.forClass(SessionHandle.class); 232 ArgumentCaptor<PersistableBundle> paramsCaptor = 233 ArgumentCaptor.forClass(PersistableBundle.class); 234 235 verify(mUwbService).openRanging( 236 eq(new AttributionSource.Builder(Process.SHELL_UID) 237 .setPackageName(UwbShellCommand.SHELL_PACKAGE_NAME) 238 .build()), 239 sessionHandleCaptor.capture(), any(), paramsCaptor.capture(), any()); 240 // PersistableBundle does not implement equals, so use toString equals. 241 assertThat(paramsCaptor.getValue().toString()) 242 .isEqualTo(openRangingParams.toBundle().toString()); 243 244 verify(mUwbService).startRanging( 245 eq(sessionHandleCaptor.getValue()), paramsCaptor.capture()); 246 assertThat(paramsCaptor.getValue().toString()) 247 .isEqualTo(startRangingParams != null 248 ? startRangingParams.toBundle().toString() 249 : new PersistableBundle().toString()); 250 251 return Pair.create(cbCaptor.cb, sessionHandleCaptor.getValue()); 252 } 253 triggerAndVerifyRangingStop( String[] rangingStopCmd, IUwbRangingCallbacks cb, SessionHandle sessionHandle)254 private void triggerAndVerifyRangingStop( 255 String[] rangingStopCmd, IUwbRangingCallbacks cb, SessionHandle sessionHandle) 256 throws Exception { 257 doAnswer(invocation -> { 258 cb.onRangingStopped(sessionHandle, REASON_LOCAL_REQUEST, new PersistableBundle()); 259 return true; 260 }).when(mUwbService).stopRanging(any()); 261 doAnswer(invocation -> { 262 cb.onRangingClosed( 263 sessionHandle, REASON_LOCAL_REQUEST, 264 new PersistableBundle()); 265 return true; 266 }).when(mUwbService).closeRanging(any()); 267 268 mUwbShellCommand.exec( 269 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(), 270 rangingStopCmd); 271 272 verify(mUwbService).stopRanging(sessionHandle); 273 verify(mUwbService).closeRanging(sessionHandle); 274 } 275 getCccStartRangingParamsFromOpenRangingParams( @onNull CccOpenRangingParams openRangingParams)276 private CccStartRangingParams getCccStartRangingParamsFromOpenRangingParams( 277 @NonNull CccOpenRangingParams openRangingParams) { 278 return new CccStartRangingParams.Builder() 279 .setSessionId(openRangingParams.getSessionId()) 280 .setRanMultiplier(openRangingParams.getRanMultiplier()) 281 .build(); 282 } 283 284 @Test testStartFiraRanging()285 public void testStartFiraRanging() throws Exception { 286 triggerAndVerifyRangingStart( 287 new String[]{"start-fira-ranging-session"}, 288 DEFAULT_FIRA_OPEN_SESSION_PARAMS.build()); 289 } 290 291 @Test testStartFiraRangingUsesUniqueSessionHandle()292 public void testStartFiraRangingUsesUniqueSessionHandle() throws Exception { 293 FiraOpenSessionParams.Builder openSessionParamsBuilder = 294 new FiraOpenSessionParams.Builder(DEFAULT_FIRA_OPEN_SESSION_PARAMS); 295 296 openSessionParamsBuilder.setSessionId(1); 297 Pair<IUwbRangingCallbacks, SessionHandle> cbAndSessionHandle1 = 298 triggerAndVerifyRangingStart( 299 new String[]{"start-fira-ranging-session", "-i", "1"}, 300 openSessionParamsBuilder.build()); 301 clearInvocations(mUwbService); 302 303 openSessionParamsBuilder.setSessionId(2); 304 Pair<IUwbRangingCallbacks, SessionHandle> cbAndSessionHandle2 = 305 triggerAndVerifyRangingStart( 306 new String[]{"start-fira-ranging-session", "-i", "2"}, 307 openSessionParamsBuilder.build()); 308 assertThat(cbAndSessionHandle1.second).isNotEqualTo(cbAndSessionHandle2.second); 309 } 310 311 @Test testStartFiraRangingWithNonDefaultParams()312 public void testStartFiraRangingWithNonDefaultParams() throws Exception { 313 FiraOpenSessionParams.Builder openSessionParamsBuilder = 314 new FiraOpenSessionParams.Builder(DEFAULT_FIRA_OPEN_SESSION_PARAMS); 315 openSessionParamsBuilder.setSessionId(5); 316 triggerAndVerifyRangingStart( 317 new String[]{"start-fira-ranging-session", "-i", "5"}, 318 openSessionParamsBuilder.build()); 319 } 320 321 @Test testStartFiraRangingWithBothInterleavingAndAoaResultReq()322 public void testStartFiraRangingWithBothInterleavingAndAoaResultReq() throws Exception { 323 // Both AOA result req and interleaving are not allowed in the same command. 324 assertThat(mUwbShellCommand.exec( 325 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(), 326 new String[]{"start-fira-ranging-session", "-i", "5", "-z", "4,5,6", "-e", 327 "enabled"})).isEqualTo(-1); 328 } 329 getRangingMeasurement()330 private RangingMeasurement getRangingMeasurement() { 331 return new RangingMeasurement.Builder() 332 .setStatus(RangingMeasurement.RANGING_STATUS_SUCCESS) 333 .setElapsedRealtimeNanos(67) 334 .setDistanceMeasurement(UwbTestUtils.getDistanceMeasurement()) 335 .setAngleOfArrivalMeasurement(UwbTestUtils.getAngleOfArrivalMeasurement()) 336 .setRemoteDeviceAddress(UwbTestUtils.getUwbAddress(true)) 337 .build(); 338 } 339 340 @Test testRangingReportFiraRanging()341 public void testRangingReportFiraRanging() throws Exception { 342 Pair<IUwbRangingCallbacks, SessionHandle> cbAndSessionHandle = 343 triggerAndVerifyRangingStart( 344 new String[]{"start-fira-ranging-session"}, 345 DEFAULT_FIRA_OPEN_SESSION_PARAMS.build()); 346 int sessionId = DEFAULT_FIRA_OPEN_SESSION_PARAMS.build().getSessionId(); 347 cbAndSessionHandle.first.onRangingResult( 348 cbAndSessionHandle.second, 349 new RangingReport.Builder().addMeasurement(getRangingMeasurement()).build()); 350 mUwbShellCommand.exec( 351 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(), 352 new String[]{"get-ranging-session-reports", String.valueOf(sessionId)}); 353 } 354 355 @Test testRangingReportAllFiraRanging()356 public void testRangingReportAllFiraRanging() throws Exception { 357 Pair<IUwbRangingCallbacks, SessionHandle> cbAndSessionHandle = 358 triggerAndVerifyRangingStart( 359 new String[]{"start-fira-ranging-session"}, 360 DEFAULT_FIRA_OPEN_SESSION_PARAMS.build()); 361 cbAndSessionHandle.first.onRangingResult( 362 cbAndSessionHandle.second, 363 new RangingReport.Builder().addMeasurement(getRangingMeasurement()).build()); 364 mUwbShellCommand.exec( 365 new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(), 366 new String[]{"get-all-ranging-session-reports"}); 367 } 368 369 @Test testStopFiraRanging()370 public void testStopFiraRanging() throws Exception { 371 Pair<IUwbRangingCallbacks, SessionHandle> cbAndSessionHandle = 372 triggerAndVerifyRangingStart( 373 new String[]{"start-fira-ranging-session"}, 374 DEFAULT_FIRA_OPEN_SESSION_PARAMS.build()); 375 int sessionId = DEFAULT_FIRA_OPEN_SESSION_PARAMS.build().getSessionId(); 376 triggerAndVerifyRangingStop( 377 new String[]{"stop-ranging-session", String.valueOf(sessionId)}, 378 cbAndSessionHandle.first, cbAndSessionHandle.second); 379 } 380 381 @Test testStartCccRanging()382 public void testStartCccRanging() throws Exception { 383 CccOpenRangingParams openSessionParams = DEFAULT_CCC_OPEN_RANGING_PARAMS.build(); 384 triggerAndVerifyRangingStart( 385 new String[]{"start-ccc-ranging-session"}, 386 openSessionParams, 387 getCccStartRangingParamsFromOpenRangingParams(openSessionParams)); 388 } 389 390 @Test testStartCccRangingWithNonDefaultParams()391 public void testStartCccRangingWithNonDefaultParams() throws Exception { 392 CccOpenRangingParams.Builder openSessionParamsBuilder = 393 new CccOpenRangingParams.Builder(DEFAULT_CCC_OPEN_RANGING_PARAMS); 394 openSessionParamsBuilder.setSessionId(5); 395 CccOpenRangingParams openSessionParams = openSessionParamsBuilder.build(); 396 triggerAndVerifyRangingStart( 397 new String[]{"start-ccc-ranging-session", "-i", "5"}, 398 openSessionParams, 399 getCccStartRangingParamsFromOpenRangingParams(openSessionParams)); 400 } 401 402 @Test testStopCccRanging()403 public void testStopCccRanging() throws Exception { 404 CccOpenRangingParams openSessionParams = DEFAULT_CCC_OPEN_RANGING_PARAMS.build(); 405 Pair<IUwbRangingCallbacks, SessionHandle> cbAndSessionHandle = 406 triggerAndVerifyRangingStart( 407 new String[]{"start-ccc-ranging-session"}, 408 openSessionParams, 409 getCccStartRangingParamsFromOpenRangingParams(openSessionParams)); 410 int sessionId = openSessionParams.getSessionId(); 411 triggerAndVerifyRangingStop( 412 new String[]{"stop-ranging-session", String.valueOf(sessionId)}, 413 cbAndSessionHandle.first, cbAndSessionHandle.second); 414 } 415 416 @Test testStopAllRanging()417 public void testStopAllRanging() throws Exception { 418 CccOpenRangingParams openSessionParams = DEFAULT_CCC_OPEN_RANGING_PARAMS.build(); 419 Pair<IUwbRangingCallbacks, SessionHandle> cbAndSessionHandle = 420 triggerAndVerifyRangingStart( 421 new String[]{"start-ccc-ranging-session"}, 422 openSessionParams, 423 getCccStartRangingParamsFromOpenRangingParams(openSessionParams)); 424 triggerAndVerifyRangingStop( 425 new String[]{"stop-all-ranging-sessions"}, 426 cbAndSessionHandle.first, cbAndSessionHandle.second); 427 } 428 } 429