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