1 /*
2  * Copyright (C) 2021 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.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
20 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
21 import static android.uwb.UwbAddress.SHORT_ADDRESS_BYTE_LENGTH;
22 
23 import static com.google.uwb.support.ccc.CccParams.CHAPS_PER_SLOT_3;
24 import static com.google.uwb.support.ccc.CccParams.HOPPING_CONFIG_MODE_ADAPTIVE;
25 import static com.google.uwb.support.ccc.CccParams.HOPPING_CONFIG_MODE_CONTINUOUS;
26 import static com.google.uwb.support.ccc.CccParams.HOPPING_SEQUENCE_AES;
27 import static com.google.uwb.support.ccc.CccParams.HOPPING_SEQUENCE_DEFAULT;
28 import static com.google.uwb.support.ccc.CccParams.PULSE_SHAPE_SYMMETRICAL_ROOT_RAISED_COSINE;
29 import static com.google.uwb.support.ccc.CccParams.SLOTS_PER_ROUND_6;
30 import static com.google.uwb.support.ccc.CccParams.UWB_CHANNEL_9;
31 import static com.google.uwb.support.fira.FiraParams.AOA_RESULT_REQUEST_MODE_NO_AOA_REPORT;
32 import static com.google.uwb.support.fira.FiraParams.AOA_RESULT_REQUEST_MODE_REQ_AOA_RESULTS;
33 import static com.google.uwb.support.fira.FiraParams.AOA_RESULT_REQUEST_MODE_REQ_AOA_RESULTS_AZIMUTH_ONLY;
34 import static com.google.uwb.support.fira.FiraParams.AOA_RESULT_REQUEST_MODE_REQ_AOA_RESULTS_ELEVATION_ONLY;
35 import static com.google.uwb.support.fira.FiraParams.AOA_RESULT_REQUEST_MODE_REQ_AOA_RESULTS_INTERLEAVED;
36 import static com.google.uwb.support.fira.FiraParams.BPRF_PHR_DATA_RATE_6M81;
37 import static com.google.uwb.support.fira.FiraParams.BPRF_PHR_DATA_RATE_850K;
38 import static com.google.uwb.support.fira.FiraParams.HOPPING_MODE_DISABLE;
39 import static com.google.uwb.support.fira.FiraParams.MULTICAST_LIST_UPDATE_ACTION_ADD;
40 import static com.google.uwb.support.fira.FiraParams.MULTICAST_LIST_UPDATE_ACTION_DELETE;
41 import static com.google.uwb.support.fira.FiraParams.MULTI_NODE_MODE_MANY_TO_MANY;
42 import static com.google.uwb.support.fira.FiraParams.MULTI_NODE_MODE_ONE_TO_MANY;
43 import static com.google.uwb.support.fira.FiraParams.MULTI_NODE_MODE_UNICAST;
44 import static com.google.uwb.support.fira.FiraParams.PRF_MODE_BPRF;
45 import static com.google.uwb.support.fira.FiraParams.PRF_MODE_HPRF;
46 import static com.google.uwb.support.fira.FiraParams.PSDU_DATA_RATE_27M2;
47 import static com.google.uwb.support.fira.FiraParams.PSDU_DATA_RATE_31M2;
48 import static com.google.uwb.support.fira.FiraParams.PSDU_DATA_RATE_6M81;
49 import static com.google.uwb.support.fira.FiraParams.PSDU_DATA_RATE_7M80;
50 import static com.google.uwb.support.fira.FiraParams.RANGE_DATA_NTF_CONFIG_ENABLE_PROXIMITY_LEVEL_TRIG;
51 import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_DT_TAG;
52 import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_ROLE_INITIATOR;
53 import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_ROLE_RESPONDER;
54 import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_TYPE_CONTROLEE;
55 import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_TYPE_CONTROLLER;
56 import static com.google.uwb.support.fira.FiraParams.RANGING_DEVICE_TYPE_DT_TAG;
57 import static com.google.uwb.support.fira.FiraParams.RANGING_ROUND_USAGE_DL_TDOA;
58 import static com.google.uwb.support.fira.FiraParams.RANGING_ROUND_USAGE_DS_TWR_DEFERRED_MODE;
59 import static com.google.uwb.support.fira.FiraParams.RANGING_ROUND_USAGE_DS_TWR_NON_DEFERRED_MODE;
60 import static com.google.uwb.support.fira.FiraParams.RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE;
61 import static com.google.uwb.support.fira.FiraParams.RANGING_ROUND_USAGE_SS_TWR_NON_DEFERRED_MODE;
62 import static com.google.uwb.support.fira.FiraParams.RFRAME_CONFIG_SP0;
63 import static com.google.uwb.support.fira.FiraParams.RFRAME_CONFIG_SP1;
64 import static com.google.uwb.support.fira.FiraParams.SFD_ID_VALUE_2;
65 import static com.google.uwb.support.fira.FiraParams.STS_CONFIG_PROVISIONED;
66 import static com.google.uwb.support.fira.FiraParams.STS_CONFIG_STATIC;
67 import static com.google.uwb.support.radar.RadarParams.BITS_PER_SAMPLES_32;
68 import static com.google.uwb.support.radar.RadarParams.NUMBER_OF_BURSTS_DEFAULT;
69 import static com.google.uwb.support.radar.RadarParams.PREAMBLE_DURATION_T128_SYMBOLS;
70 import static com.google.uwb.support.radar.RadarParams.RADAR_DATA_TYPE_RADAR_SWEEP_SAMPLES;
71 import static com.google.uwb.support.radar.RadarParams.SAMPLES_PER_SWEEP_DEFAULT;
72 import static com.google.uwb.support.radar.RadarParams.SESSION_PRIORITY_DEFAULT;
73 import static com.google.uwb.support.radar.RadarParams.SWEEP_OFFSET_DEFAULT;
74 
75 import static java.util.concurrent.TimeUnit.MILLISECONDS;
76 
77 import android.annotation.NonNull;
78 import android.content.AttributionSource;
79 import android.content.Context;
80 import android.content.pm.PackageManager;
81 import android.os.Binder;
82 import android.os.Handler;
83 import android.os.Looper;
84 import android.os.PersistableBundle;
85 import android.os.Process;
86 import android.os.RemoteException;
87 import android.util.ArrayMap;
88 import android.util.Log;
89 import android.util.Pair;
90 import android.uwb.IUwbRangingCallbacks;
91 import android.uwb.RangingReport;
92 import android.uwb.SessionHandle;
93 import android.uwb.UwbAddress;
94 import android.uwb.UwbManager;
95 
96 import androidx.annotation.Nullable;
97 
98 import com.android.internal.annotations.VisibleForTesting;
99 import com.android.modules.utils.BasicShellCommandHandler;
100 import com.android.server.uwb.jni.NativeUwbManager;
101 import com.android.server.uwb.util.ArrayUtils;
102 
103 import com.google.common.io.BaseEncoding;
104 import com.google.uwb.support.base.Params;
105 import com.google.uwb.support.ccc.CccOpenRangingParams;
106 import com.google.uwb.support.ccc.CccParams;
107 import com.google.uwb.support.ccc.CccPulseShapeCombo;
108 import com.google.uwb.support.ccc.CccStartRangingParams;
109 import com.google.uwb.support.dltdoa.DlTDoARangingRoundsUpdate;
110 import com.google.uwb.support.fira.FiraOpenSessionParams;
111 import com.google.uwb.support.fira.FiraParams;
112 import com.google.uwb.support.fira.FiraRangingReconfigureParams;
113 import com.google.uwb.support.generic.GenericSpecificationParams;
114 import com.google.uwb.support.radar.RadarOpenSessionParams;
115 
116 import java.io.PrintWriter;
117 import java.nio.ByteBuffer;
118 import java.util.ArrayDeque;
119 import java.util.ArrayList;
120 import java.util.Arrays;
121 import java.util.List;
122 import java.util.Map;
123 import java.util.concurrent.CancellationException;
124 import java.util.concurrent.CompletableFuture;
125 import java.util.concurrent.ExecutionException;
126 import java.util.concurrent.FutureTask;
127 import java.util.concurrent.TimeoutException;
128 
129 /**
130  * Interprets and executes 'adb shell cmd uwb [args]'.
131  *
132  * To add new commands:
133  * - onCommand: Add a case "<command>" execute. Return a 0
134  *   if command executed successfully.
135  * - onHelp: add a description string.
136  *
137  * Permissions: currently root permission is required for some commands. Others will
138  * enforce the corresponding API permissions.
139  */
140 public class UwbShellCommand extends BasicShellCommandHandler {
141     @VisibleForTesting
142     public static String SHELL_PACKAGE_NAME = "com.android.shell";
143     private static final long RANGE_CTL_TIMEOUT_MILLIS = 10_000;
144     private static final int RSSI_FLAG = 1;
145     private static final int AOA_FLAG = 1 << 1;
146     private static final int CIR_FLAG = 1 << 2;
147     private static final int SEGMENT_METRICS_FLAG = 1 << 5;
148     private static final int CMD_TIMEOUT_MS = 10_000;
149 
150     // These don't require root access.
151     // However, these do perform permission checks in the corresponding UwbService methods.
152     private static final String[] NON_PRIVILEGED_COMMANDS = {
153             "help",
154             "status",
155             "get-country-code",
156             "get-log-mode",
157             "enable-uwb",
158             "disable-uwb",
159             "enable-uwb-hw",
160             "disable-uwb-hw",
161             "simulate-app-state-change",
162             "start-fira-ranging-session",
163             "start-ccc-ranging-session",
164             "start-radar-session",
165             "reconfigure-fira-ranging-session",
166             "get-ranging-session-reports",
167             "get-all-ranging-session-reports",
168             "stop-ranging-session",
169             "stop-radar-session",
170             "stop-all-ranging-sessions",
171             "stop-all-radar-sessions",
172             "get-specification-info",
173             "enable-diagnostics-notification",
174             "disable-diagnostics-notification",
175             "take-bugreport",
176     };
177 
178     @VisibleForTesting
179     public static final FiraOpenSessionParams.Builder DEFAULT_FIRA_OPEN_SESSION_PARAMS =
180             new FiraOpenSessionParams.Builder()
181                     .setProtocolVersion(FiraParams.PROTOCOL_VERSION_1_1)
182                     .setSessionId(1)
183                     .setSessionType(FiraParams.SESSION_TYPE_RANGING)
184                     .setChannelNumber(9)
185                     .setDeviceType(RANGING_DEVICE_TYPE_CONTROLLER)
186                     .setDeviceRole(RANGING_DEVICE_ROLE_INITIATOR)
187                     .setDeviceAddress(UwbAddress.fromBytes(new byte[] { 0x4, 0x6}))
188                     .setDestAddressList(Arrays.asList(UwbAddress.fromBytes(new byte[] { 0x4, 0x6})))
189                     .setMultiNodeMode(MULTI_NODE_MODE_UNICAST)
190                     .setRangingRoundUsage(RANGING_ROUND_USAGE_DS_TWR_DEFERRED_MODE)
191                     .setVendorId(new byte[]{0x8, 0x7})
192                     .setStaticStsIV(new byte[]{0x1, 0x2, 0x3, 0x4, 0x5, 0x6});
193 
194     @VisibleForTesting
195     public static final CccOpenRangingParams.Builder DEFAULT_CCC_OPEN_RANGING_PARAMS =
196             new CccOpenRangingParams.Builder()
197                     .setProtocolVersion(CccParams.PROTOCOL_VERSION_1_0)
198                     .setUwbConfig(CccParams.UWB_CONFIG_0)
199                     .setPulseShapeCombo(
200                             new CccPulseShapeCombo(
201                                     PULSE_SHAPE_SYMMETRICAL_ROOT_RAISED_COSINE,
202                                     PULSE_SHAPE_SYMMETRICAL_ROOT_RAISED_COSINE))
203                     .setSessionId(1)
204                     .setRanMultiplier(4)
205                     .setChannel(UWB_CHANNEL_9)
206                     .setNumChapsPerSlot(CHAPS_PER_SLOT_3)
207                     .setNumResponderNodes(1)
208                     .setNumSlotsPerRound(SLOTS_PER_ROUND_6)
209                     .setSyncCodeIndex(1)
210                     .setHoppingConfigMode(HOPPING_MODE_DISABLE)
211                     .setHoppingSequence(HOPPING_SEQUENCE_DEFAULT);
212     @VisibleForTesting
213     public static final RadarOpenSessionParams.Builder DEFAULT_RADAR_OPEN_SESSION_PARAMS =
214             new RadarOpenSessionParams.Builder()
215                     .setSessionId(1)
216                         .setBurstPeriod(100)
217                         .setSweepPeriod(3000)
218                         .setSweepsPerBurst(16)
219                         .setSamplesPerSweep(SAMPLES_PER_SWEEP_DEFAULT)
220                         .setChannelNumber(FiraParams.UWB_CHANNEL_9)
221                         .setSweepOffset(SWEEP_OFFSET_DEFAULT)
222                         .setRframeConfig(RFRAME_CONFIG_SP0)
223                         .setPreambleDuration(PREAMBLE_DURATION_T128_SYMBOLS)
224                         .setPreambleCodeIndex(25)
225                         .setSessionPriority(SESSION_PRIORITY_DEFAULT)
226                         .setBitsPerSample(BITS_PER_SAMPLES_32)
227                         .setPrfMode(PRF_MODE_HPRF)
228                         .setNumberOfBursts(NUMBER_OF_BURSTS_DEFAULT)
229                         .setRadarDataType(RADAR_DATA_TYPE_RADAR_SWEEP_SAMPLES);
230 
231     private static final Map<Integer, SessionInfo> sSessionIdToInfo = new ArrayMap<>();
232     private static int sSessionHandleIdNext = 0;
233 
234     private final UwbInjector mUwbInjector;
235     private final UwbServiceImpl mUwbService;
236     private final UwbServiceCore mUwbServiceCore;
237     private final UwbCountryCode mUwbCountryCode;
238     private final UciLogModeStore mUciLogModeStore;
239     private final NativeUwbManager mNativeUwbManager;
240     private final UwbDiagnostics mUwbDiagnostics;
241     private final DeviceConfigFacade mDeviceConfig;
242     private final Looper mLooper;
243     private final Context mContext;
244 
UwbShellCommand(UwbInjector uwbInjector, UwbServiceImpl uwbService, Context context)245     UwbShellCommand(UwbInjector uwbInjector, UwbServiceImpl uwbService, Context context) {
246         mUwbInjector = uwbInjector;
247         mUwbService = uwbService;
248         mContext = context;
249         mUwbCountryCode = uwbInjector.getUwbCountryCode();
250         mUciLogModeStore = uwbInjector.getUciLogModeStore();
251         mNativeUwbManager = uwbInjector.getNativeUwbManager();
252         mUwbServiceCore = uwbInjector.getUwbServiceCore();
253         mUwbDiagnostics = uwbInjector.getUwbDiagnostics();
254         mDeviceConfig = uwbInjector.getDeviceConfigFacade();
255         mLooper = uwbInjector.getUwbServiceLooper();
256     }
257 
bundleToString(@ullable PersistableBundle bundle)258     private static String bundleToString(@Nullable PersistableBundle bundle) {
259         if (bundle != null) {
260             // Need to defuse any local bundles before printing. Use isEmpty() triggers unparcel.
261             bundle.isEmpty();
262             return bundle.toString();
263         } else {
264             return "null";
265         }
266     }
267 
268     private static final class UwbRangingCallbacks extends IUwbRangingCallbacks.Stub {
269         private final SessionInfo mSessionInfo;
270         private final PrintWriter mPw;
271         private final CompletableFuture mRangingOpenedFuture;
272         private final CompletableFuture mRangingStartedFuture;
273         private final CompletableFuture mRangingStoppedFuture;
274         private final CompletableFuture mRangingClosedFuture;
275         private final CompletableFuture mRangingReconfiguredFuture;
276 
UwbRangingCallbacks(@onNull SessionInfo sessionInfo, @NonNull PrintWriter pw, @NonNull CompletableFuture rangingOpenedFuture, @NonNull CompletableFuture rangingStartedFuture, @NonNull CompletableFuture rangingStoppedFuture, @NonNull CompletableFuture rangingClosedFuture, @NonNull CompletableFuture rangingReconfiguredFuture)277         UwbRangingCallbacks(@NonNull SessionInfo sessionInfo, @NonNull PrintWriter pw,
278                 @NonNull CompletableFuture rangingOpenedFuture,
279                 @NonNull CompletableFuture rangingStartedFuture,
280                 @NonNull CompletableFuture rangingStoppedFuture,
281                 @NonNull CompletableFuture rangingClosedFuture,
282                 @NonNull CompletableFuture rangingReconfiguredFuture) {
283             mSessionInfo = sessionInfo;
284             mPw = pw;
285             mRangingOpenedFuture = rangingOpenedFuture;
286             mRangingStartedFuture = rangingStartedFuture;
287             mRangingStoppedFuture = rangingStoppedFuture;
288             mRangingClosedFuture = rangingClosedFuture;
289             mRangingReconfiguredFuture = rangingReconfiguredFuture;
290         }
291 
onRangingOpened(SessionHandle sessionHandle)292         public void onRangingOpened(SessionHandle sessionHandle) {
293             mPw.println("Ranging session opened");
294             mRangingOpenedFuture.complete(true);
295         }
296 
onRangingOpenFailed(SessionHandle sessionHandle, int reason, PersistableBundle params)297         public void onRangingOpenFailed(SessionHandle sessionHandle, int reason,
298                 PersistableBundle params) {
299             mPw.println("Ranging session open failed with reason: " + reason + " and params: "
300                     + bundleToString(params));
301             mRangingOpenedFuture.complete(false);
302         }
303 
onRangingStarted(SessionHandle sessionHandle, PersistableBundle params)304         public void onRangingStarted(SessionHandle sessionHandle, PersistableBundle params) {
305             mPw.println("Ranging session started with params: " + bundleToString(params));
306             mRangingStartedFuture.complete(true);
307         }
308 
onRangingStartFailed(SessionHandle sessionHandle, int reason, PersistableBundle params)309         public void onRangingStartFailed(SessionHandle sessionHandle, int reason,
310                 PersistableBundle params) {
311             mPw.println("Ranging session start failed with reason: " + reason + " and params: "
312                     + bundleToString(params));
313             mRangingStartedFuture.complete(false);
314         }
315 
onRangingReconfigured(SessionHandle sessionHandle, PersistableBundle params)316         public void onRangingReconfigured(SessionHandle sessionHandle, PersistableBundle params) {
317             mPw.println("Ranging reconfigured with params: " + bundleToString(params));
318             mRangingReconfiguredFuture.complete(true);
319         }
320 
onRangingReconfigureFailed(SessionHandle sessionHandle, int reason, PersistableBundle params)321         public void onRangingReconfigureFailed(SessionHandle sessionHandle, int reason,
322                 PersistableBundle params) {
323             mPw.println("Ranging reconfigure failed with reason: " + reason + " and params: "
324                     + bundleToString(params));
325             mRangingReconfiguredFuture.complete(true);
326 
327         }
328 
onRangingStopped(SessionHandle sessionHandle, int reason, PersistableBundle params)329         public void onRangingStopped(SessionHandle sessionHandle, int reason,
330                 PersistableBundle params) {
331             mPw.println("Ranging session stopped with reason: " + reason + " and params: "
332                     + bundleToString(params));
333             mRangingStoppedFuture.complete(true);
334         }
335 
onRangingStopFailed(SessionHandle sessionHandle, int reason, PersistableBundle params)336         public void onRangingStopFailed(SessionHandle sessionHandle, int reason,
337                 PersistableBundle params) {
338             mPw.println("Ranging session stop failed with reason: " + reason + " and params: "
339                     + bundleToString(params));
340             mRangingStoppedFuture.complete(false);
341         }
342 
onRangingClosed(SessionHandle sessionHandle, int reason, PersistableBundle params)343         public void onRangingClosed(SessionHandle sessionHandle, int reason,
344                 PersistableBundle params) {
345             mPw.println("Ranging session closed with reason: " + reason + " and params: "
346                     + bundleToString(params));
347             sSessionIdToInfo.remove(mSessionInfo.sessionId);
348             mRangingClosedFuture.complete(true);
349         }
350 
onRangingResult(SessionHandle sessionHandle, RangingReport rangingReport)351         public void onRangingResult(SessionHandle sessionHandle, RangingReport rangingReport) {
352             mPw.println("Ranging Result: " + rangingReport);
353             mSessionInfo.addRangingReport(rangingReport);
354         }
355 
onControleeAdded(SessionHandle sessionHandle, PersistableBundle params)356         public void onControleeAdded(SessionHandle sessionHandle, PersistableBundle params) {}
357 
onControleeAddFailed(SessionHandle sessionHandle, int reason, PersistableBundle params)358         public void onControleeAddFailed(SessionHandle sessionHandle, int reason,
359                 PersistableBundle params) {}
360 
onControleeRemoved(SessionHandle sessionHandle, PersistableBundle params)361         public void onControleeRemoved(SessionHandle sessionHandle, PersistableBundle params) {}
362 
onControleeRemoveFailed(SessionHandle sessionHandle, int reason, PersistableBundle params)363         public void onControleeRemoveFailed(SessionHandle sessionHandle, int reason,
364                 PersistableBundle params) {}
365 
onRangingPaused(SessionHandle sessionHandle, PersistableBundle params)366         public void onRangingPaused(SessionHandle sessionHandle, PersistableBundle params) {}
367 
onRangingPauseFailed(SessionHandle sessionHandle, int reason, PersistableBundle params)368         public void onRangingPauseFailed(SessionHandle sessionHandle, int reason,
369                 PersistableBundle params) {}
370 
onRangingResumed(SessionHandle sessionHandle, PersistableBundle params)371         public void onRangingResumed(SessionHandle sessionHandle, PersistableBundle params) {}
372 
onRangingResumeFailed(SessionHandle sessionHandle, int reason, PersistableBundle params)373         public void onRangingResumeFailed(SessionHandle sessionHandle, int reason,
374                 PersistableBundle params) {}
375 
onDataSent(SessionHandle sessionHandle, UwbAddress uwbAddress, PersistableBundle params)376         public void onDataSent(SessionHandle sessionHandle, UwbAddress uwbAddress,
377                 PersistableBundle params) {}
378 
onDataSendFailed(SessionHandle sessionHandle, UwbAddress uwbAddress, int reason, PersistableBundle params)379         public void onDataSendFailed(SessionHandle sessionHandle, UwbAddress uwbAddress, int reason,
380                 PersistableBundle params) {}
381 
onDataTransferPhaseConfigured(SessionHandle sessionHandle, PersistableBundle params)382         public void onDataTransferPhaseConfigured(SessionHandle sessionHandle,
383                   PersistableBundle params) {
384         }
385 
onDataTransferPhaseConfigFailed(SessionHandle sessionHandle, int reason, PersistableBundle params)386         public void onDataTransferPhaseConfigFailed(SessionHandle sessionHandle, int reason,
387                 PersistableBundle params) {}
388 
onDataReceived(SessionHandle sessionHandle, UwbAddress uwbAddress, PersistableBundle params, byte[] data)389         public void onDataReceived(SessionHandle sessionHandle, UwbAddress uwbAddress,
390                 PersistableBundle params, byte[] data) {}
391 
onDataReceiveFailed(SessionHandle sessionHandle, UwbAddress uwbAddress, int reason, PersistableBundle params)392         public void onDataReceiveFailed(SessionHandle sessionHandle, UwbAddress uwbAddress,
393                 int reason, PersistableBundle params) {}
394 
onServiceDiscovered(SessionHandle sessionHandle, PersistableBundle params)395         public void onServiceDiscovered(SessionHandle sessionHandle, PersistableBundle params) {}
396 
onServiceConnected(SessionHandle sessionHandle, PersistableBundle params)397         public void onServiceConnected(SessionHandle sessionHandle, PersistableBundle params) {}
398 
onRangingRoundsUpdateDtTagStatus(SessionHandle sessionHandle, PersistableBundle params)399         public void onRangingRoundsUpdateDtTagStatus(SessionHandle sessionHandle,
400                 PersistableBundle params) {}
401 
onHybridSessionControllerConfigured(SessionHandle sessionHandle, PersistableBundle parameters)402         public void onHybridSessionControllerConfigured(SessionHandle sessionHandle,
403                 PersistableBundle parameters) {}
404 
onHybridSessionControllerConfigurationFailed(SessionHandle sessionHandle, int reason, PersistableBundle parameters)405         public void onHybridSessionControllerConfigurationFailed(SessionHandle sessionHandle,
406                 int reason, PersistableBundle parameters) {}
407 
onHybridSessionControleeConfigured(SessionHandle sessionHandle, PersistableBundle parameters)408         public void onHybridSessionControleeConfigured(SessionHandle sessionHandle,
409                 PersistableBundle parameters) {}
410 
onHybridSessionControleeConfigurationFailed(SessionHandle sessionHandle, int reason, PersistableBundle parameters)411         public void onHybridSessionControleeConfigurationFailed(SessionHandle sessionHandle,
412                 int reason, PersistableBundle parameters) {}
413     }
414 
415 
416     private class SessionInfo {
417         private static final int LAST_NUM_RANGING_REPORTS = 20;
418 
419         public final SessionHandle sessionHandle;
420         public final int sessionId;
421         public final Params openRangingParams;
422         public final UwbRangingCallbacks uwbRangingCbs;
423         public final boolean isRadarSession;
424         public final ArrayDeque<RangingReport> lastRangingReports =
425                 new ArrayDeque<>(LAST_NUM_RANGING_REPORTS);
426 
427         public final CompletableFuture<Boolean> rangingOpenedFuture = new CompletableFuture<>();
428         public final CompletableFuture<Boolean> rangingStartedFuture = new CompletableFuture<>();
429         public final CompletableFuture<Boolean> rangingStoppedFuture = new CompletableFuture<>();
430         public final CompletableFuture<Boolean> rangingClosedFuture = new CompletableFuture<>();
431         public final CompletableFuture<Boolean> rangingReconfiguredFuture =
432                 new CompletableFuture<>();
433 
SessionInfo(int sessionId, SessionHandle sessionHandle, @NonNull Params openRangingParams, @NonNull PrintWriter pw, boolean isRadarSession)434         SessionInfo(int sessionId, SessionHandle sessionHandle, @NonNull Params openRangingParams,
435                 @NonNull PrintWriter pw, boolean isRadarSession) {
436             this.sessionId = sessionId;
437             this.sessionHandle = sessionHandle;
438             this.openRangingParams = openRangingParams;
439             this.isRadarSession = isRadarSession;
440             uwbRangingCbs = new UwbRangingCallbacks(this, pw, rangingOpenedFuture,
441                     rangingStartedFuture, rangingStoppedFuture, rangingClosedFuture,
442                     rangingReconfiguredFuture);
443         }
444 
addRangingReport(@onNull RangingReport rangingReport)445         public void addRangingReport(@NonNull RangingReport rangingReport) {
446             if (lastRangingReports.size() == LAST_NUM_RANGING_REPORTS) {
447                 lastRangingReports.remove();
448             }
449             lastRangingReports.add(rangingReport);
450         }
451     }
452 
buildFiraOpenSessionParams( GenericSpecificationParams specificationParams)453     private Pair<FiraOpenSessionParams, Boolean> buildFiraOpenSessionParams(
454             GenericSpecificationParams specificationParams) {
455         FiraOpenSessionParams.Builder builder =
456                 new FiraOpenSessionParams.Builder(DEFAULT_FIRA_OPEN_SESSION_PARAMS);
457         boolean shouldBlockCall = false;
458         boolean interleavingEnabled = false;
459         boolean aoaResultReqEnabled = false;
460         String option = getNextOption();
461         while (option != null) {
462             if (option.equals("-b")) {
463                 shouldBlockCall = true;
464             }
465             if (option.equals("-i")) {
466                 builder.setSessionId(Integer.parseInt(getNextArgRequired()));
467             }
468             if (option.equals("-c")) {
469                 builder.setChannelNumber(Integer.parseInt(getNextArgRequired()));
470             }
471             if (option.equals("-t")) {
472                 String type = getNextArgRequired();
473                 if (type.equals("controller")) {
474                     builder.setDeviceType(RANGING_DEVICE_TYPE_CONTROLLER);
475                 } else if (type.equals("controlee")) {
476                     builder.setDeviceType(RANGING_DEVICE_TYPE_CONTROLEE);
477                 } else {
478                     throw new IllegalArgumentException("Unknown device type: " + type);
479                 }
480             }
481             if (option.equals("-r")) {
482                 String role = getNextArgRequired();
483                 if (role.equals("initiator")) {
484                     builder.setDeviceRole(RANGING_DEVICE_ROLE_INITIATOR);
485                 } else if (role.equals("responder")) {
486                     builder.setDeviceRole(RANGING_DEVICE_ROLE_RESPONDER);
487                 } else {
488                     throw new IllegalArgumentException("Unknown device role: " + role);
489                 }
490             }
491             if (option.equals("-a")) {
492                 builder.setDeviceAddress(
493                         UwbAddress.fromBytes(
494                                 ByteBuffer.allocate(SHORT_ADDRESS_BYTE_LENGTH)
495                                         .putShort(Short.parseShort(getNextArgRequired()))
496                                         .array()));
497             }
498             if (option.equals("-d")) {
499                 String[] destAddressesString = getNextArgRequired().split(",");
500                 List<UwbAddress> destAddresses = new ArrayList<>();
501                 for (String destAddressString : destAddressesString) {
502                     destAddresses.add(UwbAddress.fromBytes(
503                             ByteBuffer.allocate(SHORT_ADDRESS_BYTE_LENGTH)
504                                     .putShort(Short.parseShort(destAddressString))
505                                     .array()));
506                 }
507                 builder.setDestAddressList(destAddresses);
508                 builder.setMultiNodeMode(destAddresses.size() > 1
509                         ? MULTI_NODE_MODE_ONE_TO_MANY
510                         : MULTI_NODE_MODE_UNICAST);
511             }
512             if (option.equals("-m")) {
513                 String mode = getNextArgRequired();
514                 if (mode.equals("unicast")) {
515                     builder.setMultiNodeMode(MULTI_NODE_MODE_UNICAST);
516                 } else if (mode.equals("one-to-many")) {
517                     builder.setMultiNodeMode(MULTI_NODE_MODE_ONE_TO_MANY);
518                 } else if (mode.equals("many-to-many")) {
519                     builder.setMultiNodeMode(MULTI_NODE_MODE_MANY_TO_MANY);
520                 } else {
521                     throw new IllegalArgumentException("Unknown multi-node mode: " + mode);
522                 }
523             }
524             if (option.equals("-u")) {
525                 String usage = getNextArgRequired();
526                 if (usage.equals("ds-twr")) {
527                     builder.setRangingRoundUsage(RANGING_ROUND_USAGE_DS_TWR_DEFERRED_MODE);
528                 } else if (usage.equals("ss-twr")) {
529                     builder.setRangingRoundUsage(RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE);
530                 } else if (usage.equals("ds-twr-non-deferred")) {
531                     builder.setRangingRoundUsage(RANGING_ROUND_USAGE_DS_TWR_NON_DEFERRED_MODE);
532                 } else if (usage.equals("ss-twr-non-deferred")) {
533                     builder.setRangingRoundUsage(RANGING_ROUND_USAGE_SS_TWR_NON_DEFERRED_MODE);
534                 } else {
535                     throw new IllegalArgumentException("Unknown round usage: " + usage);
536                 }
537             }
538             if (option.equals("-l")) {
539                 builder.setRangingIntervalMs(Integer.parseInt(getNextArgRequired()));
540             }
541             if (option.equals("-s")) {
542                 builder.setSlotsPerRangingRound(Integer.parseInt(getNextArgRequired()));
543             }
544             if (option.equals("-x")) {
545                 String[] rangeDataNtfProximityString = getNextArgRequired().split(",");
546                 if (rangeDataNtfProximityString.length != 2) {
547                     throw new IllegalArgumentException("Unexpected range data ntf proximity range:"
548                             + Arrays.toString(rangeDataNtfProximityString)
549                             + " expected to be <proximity-near-cm, proximity-far-cm>");
550                 }
551                 int rangeDataNtfProximityNearCm = Integer.parseInt(rangeDataNtfProximityString[0]);
552                 int rangeDataNtfProximityFarCm = Integer.parseInt(rangeDataNtfProximityString[1]);
553                 // Enable range data ntf while inside proximity range
554                 builder.setRangeDataNtfConfig(RANGE_DATA_NTF_CONFIG_ENABLE_PROXIMITY_LEVEL_TRIG);
555                 builder.setRangeDataNtfProximityNear(rangeDataNtfProximityNearCm);
556                 builder.setRangeDataNtfProximityFar(rangeDataNtfProximityFarCm);
557             }
558             if (option.equals("-R")) {
559                 // enable / disable range data NTFs
560                 // range-data-notification
561                 String range_data_ntf = getNextArgRequired();
562                 if (range_data_ntf.equals("disabled")) {
563                     builder.setRangeDataNtfConfig(FiraParams.RANGE_DATA_NTF_CONFIG_DISABLE);
564                 } else if (range_data_ntf.equals("enabled")) {
565                     builder.setRangeDataNtfConfig(FiraParams.RANGE_DATA_NTF_CONFIG_ENABLE);
566                 } else {
567                     throw new IllegalArgumentException("Unknown range data ntf setting: "
568                         + range_data_ntf);
569                 }
570             }
571             if (option.equals("-z")) {
572                 String[] interleaveRatioString = getNextArgRequired().split(",");
573                 if (interleaveRatioString.length != 3) {
574                     throw new IllegalArgumentException("Unexpected interleaving ratio: "
575                             +  Arrays.toString(interleaveRatioString)
576                             + " expected to be <numRange, numAoaAzimuth, numAoaElevation>");
577                 }
578                 int numOfRangeMsrmts = Integer.parseInt(interleaveRatioString[0]);
579                 int numOfAoaAzimuthMrmts = Integer.parseInt(interleaveRatioString[1]);
580                 int numOfAoaElevationMrmts = Integer.parseInt(interleaveRatioString[2]);
581                 // Set to interleaving mode
582                 builder.setAoaResultRequest(AOA_RESULT_REQUEST_MODE_REQ_AOA_RESULTS_INTERLEAVED);
583                 builder.setMeasurementFocusRatio(
584                         numOfRangeMsrmts,
585                         numOfAoaAzimuthMrmts,
586                         numOfAoaElevationMrmts);
587                 interleavingEnabled = true;
588             }
589             if (option.equals("-e")) {
590                 String aoaType = getNextArgRequired();
591                 if (aoaType.equals("none")) {
592                     builder.setAoaResultRequest(AOA_RESULT_REQUEST_MODE_NO_AOA_REPORT);
593                 } else if (aoaType.equals("enabled")) {
594                     builder.setAoaResultRequest(AOA_RESULT_REQUEST_MODE_REQ_AOA_RESULTS);
595                 } else if (aoaType.equals("azimuth-only")) {
596                     builder.setAoaResultRequest(
597                         AOA_RESULT_REQUEST_MODE_REQ_AOA_RESULTS_AZIMUTH_ONLY);
598                 } else if (aoaType.equals("elevation-only")) {
599                     builder.setAoaResultRequest(
600                         AOA_RESULT_REQUEST_MODE_REQ_AOA_RESULTS_ELEVATION_ONLY);
601                 } else {
602                     throw new IllegalArgumentException("Unknown aoa type: " + aoaType);
603                 }
604                 aoaResultReqEnabled = true;
605             }
606             if (option.equals("-f")) {
607                 String[] resultReportConfigs = getNextArgRequired().split(",");
608                 for (String resultReportConfig : resultReportConfigs) {
609                     if (resultReportConfig.equals("tof")) {
610                         builder.setHasTimeOfFlightReport(true);
611                     } else if (resultReportConfig.equals("azimuth")) {
612                         builder.setHasAngleOfArrivalAzimuthReport(true);
613                     } else if (resultReportConfig.equals("elevation")) {
614                         builder.setHasAngleOfArrivalElevationReport(true);
615                     } else if (resultReportConfig.equals("aoa-fom")) {
616                         builder.setHasAngleOfArrivalFigureOfMeritReport(true);
617                     } else {
618                         throw new IllegalArgumentException("Unknown result report config: "
619                                 + resultReportConfig);
620                     }
621                 }
622             }
623             if (option.equals("-g")) {
624                 String staticSTSIV = getNextArgRequired();
625                 if (staticSTSIV.length() == 12) {
626                     builder.setStaticStsIV(BaseEncoding.base16().decode(staticSTSIV.toUpperCase()));
627                 } else {
628                     throw new IllegalArgumentException("staticSTSIV expecting 6 bytes");
629                 }
630             }
631             if (option.equals("-v")) {
632                 String vendorId = getNextArgRequired();
633                 if (vendorId.length() == 4) {
634                     builder.setVendorId(BaseEncoding.base16().decode(vendorId.toUpperCase()));
635                 } else {
636                     throw new IllegalArgumentException("vendorId expecting 2 bytes");
637                 }
638             }
639             if (option.equals("-h")) {
640                 int slotDurationRstu = Integer.parseInt(getNextArgRequired());
641                 builder.setSlotDurationRstu(slotDurationRstu);
642             }
643             if (option.equals("-w")) {
644                 boolean hasRangingResultReportMessage =
645                         getNextArgRequiredTrueOrFalse("enabled", "disabled");
646                 builder.setHasRangingResultReportMessage(hasRangingResultReportMessage);
647             }
648             if (option.equals("-y")) {
649                 boolean hoppingEnabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
650                 builder.setHoppingMode(hoppingEnabled ? 1 : 0);
651             }
652             if (option.equals("-p")) {
653                 int preambleCodeIndex = Integer.parseInt(getNextArgRequired());
654                 builder.setPreambleCodeIndex(preambleCodeIndex);
655             }
656             if (option.equals("-o")) {
657                 String stsConfigType = getNextArgRequired();
658                 if (stsConfigType.equals("static")) {
659                     builder.setStsConfig(STS_CONFIG_STATIC);
660                 } else if (stsConfigType.equals("provisioned")) {
661                     builder.setStsConfig(STS_CONFIG_PROVISIONED);
662                 } else {
663                     throw new IllegalArgumentException("unknown sts config type");
664                 }
665             }
666             if (option.equals("-n")) {
667                 String sessionKey = getNextArgRequired();
668                 if (sessionKey.length() == 32 || sessionKey.length() == 64) {
669                     builder.setSessionKey(BaseEncoding.base16().decode(sessionKey));
670                 } else {
671                     throw new IllegalArgumentException("sessionKey expecting 16 or 32 bytes");
672                 }
673             }
674             if (option.equals("-k")) {
675                 String subSessionKey = getNextArgRequired();
676                 if (subSessionKey.length() == 32 || subSessionKey.length() == 64) {
677                     builder.setSubsessionKey(BaseEncoding.base16().decode(subSessionKey));
678                 } else {
679                     throw new IllegalArgumentException(("subSessionKey expecting 16 or 32 bytes"));
680                 }
681             }
682             if (option.equals("-j")) {
683                 int errorStreakTimeoutMs = Integer.parseInt(getNextArgRequired());
684                 builder.setRangingErrorStreakTimeoutMs(errorStreakTimeoutMs);
685             }
686             if (option.equals("-q")) {
687                 int sessionPriority = Integer.parseInt(getNextArgRequired());
688                 if (sessionPriority < 1 || sessionPriority > 100 || sessionPriority == 50) {
689                     throw new IllegalArgumentException(
690                             "sessionPriority expecting value between 1-49 or 51-100. 50 is "
691                                     + "reserved for default and has no effect.");
692                 }
693                 builder.setSessionPriority(sessionPriority);
694             }
695             if (option.equals("-P")) {
696                 String prfMode = getNextArgRequired();
697                 if (prfMode.equals("bprf")) {
698                     builder.setPrfMode(PRF_MODE_BPRF);
699                 } else if (prfMode.equals("hprf")) {
700                     builder.setPrfMode(PRF_MODE_HPRF);
701                 } else {
702                     throw new IllegalArgumentException("Wrong arguments for prmMode");
703                 }
704             }
705             if (option.equals("-D")) {
706                 String psduDataRate = getNextArgRequired();
707                 if (psduDataRate.equals("6m81")) {
708                     builder.setPsduDataRate(PSDU_DATA_RATE_6M81);
709                 } else if (psduDataRate.equals("7m80")) {
710                     builder.setPsduDataRate(PSDU_DATA_RATE_7M80);
711                 } else if (psduDataRate.equals("27m2")) {
712                     builder.setPsduDataRate(PSDU_DATA_RATE_27M2);
713                 } else if (psduDataRate.equals("31m2")) {
714                     builder.setPsduDataRate(PSDU_DATA_RATE_31M2);
715                 } else {
716                     throw new IllegalArgumentException("Wrong arguments for psduDataRate");
717                 }
718             }
719             if (option.equals("-B")) {
720                 String bprfPhrDataRate = getNextArgRequired();
721                 if (bprfPhrDataRate.equals("850k")) {
722                     builder.setBprfPhrDataRate(BPRF_PHR_DATA_RATE_850K);
723                 } else if (bprfPhrDataRate.equals("6m81")) {
724                     builder.setBprfPhrDataRate(BPRF_PHR_DATA_RATE_6M81);
725                 } else {
726                     throw new IllegalArgumentException("Wrong arguments for bprfPhrDataRate");
727                 }
728             }
729             if (option.equals("-A")) {
730                 builder.setIsTxAdaptivePayloadPowerEnabled(
731                         getNextArgRequiredTrueOrFalse("enabled", "disabled"));
732             }
733             if (option.equals("-S")) {
734                 int sfd_id = Integer.parseInt(getNextArgRequired());
735                 if (sfd_id < 0 || sfd_id > 4) {
736                     throw new IllegalArgumentException("SFD_ID should be in range 0-4");
737                 }
738                 builder.setSfdId(sfd_id);
739             }
740             option = getNextOption();
741         }
742         if (aoaResultReqEnabled && interleavingEnabled) {
743             throw new IllegalArgumentException(
744                     "Both interleaving (-z) and aoa result req (-e) cannot be specified");
745         }
746         // Enable rssi reporting if device supports it.
747         if (specificationParams.getFiraSpecificationParams().hasRssiReportingSupport()) {
748             builder.setIsRssiReportingEnabled(true);
749         }
750         // TODO: Add remaining params if needed.
751         return Pair.create(builder.build(), shouldBlockCall);
752     }
753 
startFiraRangingSession(PrintWriter pw)754     private void startFiraRangingSession(PrintWriter pw) throws Exception {
755         GenericSpecificationParams specificationParams =
756                 mUwbServiceCore.getCachedSpecificationParams(mUwbService.getDefaultChipId());
757         Pair<FiraOpenSessionParams, Boolean> firaOpenSessionParams =
758                 buildFiraOpenSessionParams(specificationParams);
759         startRangingSession(
760                 firaOpenSessionParams.first, null, firaOpenSessionParams.first.getSessionId(),
761                 firaOpenSessionParams.second, pw);
762     }
763 
startDlTDoaRangingSession(PrintWriter pw)764     private void startDlTDoaRangingSession(PrintWriter pw) throws Exception {
765         FiraOpenSessionParams.Builder builder = new FiraOpenSessionParams.Builder()
766                 .setProtocolVersion(FiraParams.PROTOCOL_VERSION_1_1)
767                 .setSessionId(1)
768                 .setSessionType(FiraParams.SESSION_TYPE_RANGING)
769                 .setSfdId(SFD_ID_VALUE_2)
770                 .setDeviceType(RANGING_DEVICE_TYPE_DT_TAG)
771                 .setDeviceRole(RANGING_DEVICE_DT_TAG)
772                 .setDeviceAddress(UwbAddress.fromBytes(new byte[] { 0x4, 0x6}))
773                 .setMultiNodeMode(MULTI_NODE_MODE_ONE_TO_MANY)
774                 .setRangingRoundUsage(RANGING_ROUND_USAGE_DL_TDOA)
775                 .setVendorId(new byte[]{0x8, 0x7})
776                 .setRframeConfig(RFRAME_CONFIG_SP1)
777                 .setStaticStsIV(new byte[]{0x1, 0x2, 0x3, 0x4, 0x5, 0x6});
778 
779         String option = getNextOption();
780         while (option != null) {
781             if (option.equals("-i")) {
782                 builder.setSessionId(Integer.parseInt(getNextArgRequired()));
783             }
784             option = getNextOption();
785         }
786         FiraOpenSessionParams firaOpenSessionParams = builder.build();
787         startRangingSession(
788                 firaOpenSessionParams, null, firaOpenSessionParams.getSessionId(),
789                 true, pw);
790     }
791 
buildCccOpenRangingParams()792     private Pair<CccOpenRangingParams, Boolean> buildCccOpenRangingParams() {
793         CccOpenRangingParams.Builder builder =
794                 new CccOpenRangingParams.Builder(DEFAULT_CCC_OPEN_RANGING_PARAMS);
795         boolean shouldBlockCall = false;
796         String option = getNextOption();
797         while (option != null) {
798             if (option.equals("-b")) {
799                 shouldBlockCall = true;
800             }
801             if (option.equals("-u")) {
802                 builder.setUwbConfig(Integer.parseInt(getNextArgRequired()));
803             }
804             if (option.equals("-p")) {
805                 String[] pulseComboString = getNextArgRequired().split(",");
806                 if (pulseComboString.length != 2) {
807                     throw new IllegalArgumentException("Erroneous pulse combo: "
808                             + Arrays.toString(pulseComboString));
809                 }
810                 builder.setPulseShapeCombo(new CccPulseShapeCombo(
811                         Integer.parseInt(pulseComboString[0]),
812                         Integer.parseInt(pulseComboString[1])));
813             }
814             if (option.equals("-i")) {
815                 builder.setSessionId(Integer.parseInt(getNextArgRequired()));
816             }
817             if (option.equals("-r")) {
818                 builder.setRanMultiplier(Integer.parseInt(getNextArgRequired()));
819             }
820             if (option.equals("-c")) {
821                 builder.setChannel(Integer.parseInt(getNextArgRequired()));
822             }
823             if (option.equals("-m")) {
824                 builder.setNumChapsPerSlot(Integer.parseInt(getNextArgRequired()));
825             }
826             if (option.equals("-n")) {
827                 builder.setNumResponderNodes(Integer.parseInt(getNextArgRequired()));
828             }
829             if (option.equals("-o")) {
830                 builder.setNumSlotsPerRound(Integer.parseInt(getNextArgRequired()));
831             }
832             if (option.equals("-s")) {
833                 builder.setSyncCodeIndex(Integer.parseInt(getNextArgRequired()));
834             }
835             if (option.equals("-h")) {
836                 String hoppingConfigMode = getNextArgRequired();
837                 if (hoppingConfigMode.equals("none")) {
838                     builder.setHoppingConfigMode(HOPPING_MODE_DISABLE);
839                 } else if (hoppingConfigMode.equals("continuous")) {
840                     builder.setHoppingConfigMode(HOPPING_CONFIG_MODE_CONTINUOUS);
841                 } else if (hoppingConfigMode.equals("adaptive")) {
842                     builder.setHoppingConfigMode(HOPPING_CONFIG_MODE_ADAPTIVE);
843                 } else {
844                     throw new IllegalArgumentException("Unknown hopping config mode: "
845                             + hoppingConfigMode);
846                 }
847             }
848             if (option.equals("-a")) {
849                 String hoppingSequence = getNextArgRequired();
850                 if (hoppingSequence.equals("default")) {
851                     builder.setHoppingSequence(HOPPING_SEQUENCE_DEFAULT);
852                 } else if (hoppingSequence.equals("aes")) {
853                     builder.setHoppingSequence(HOPPING_SEQUENCE_AES);
854                 } else {
855                     throw new IllegalArgumentException("Unknown hopping sequence: "
856                             + hoppingSequence);
857                 }
858             }
859             option = getNextOption();
860         }
861         // TODO: Add remaining params if needed.
862         return Pair.create(builder.build(), shouldBlockCall);
863     }
864 
startCccRangingSession(PrintWriter pw)865     private void startCccRangingSession(PrintWriter pw) throws Exception {
866         Pair<CccOpenRangingParams, Boolean> cccOpenRangingParamsAndBlocking =
867                 buildCccOpenRangingParams();
868         CccOpenRangingParams cccOpenRangingParams = cccOpenRangingParamsAndBlocking.first;
869         CccStartRangingParams cccStartRangingParams = new CccStartRangingParams.Builder()
870                 .setSessionId(cccOpenRangingParams.getSessionId())
871                 .setRanMultiplier(cccOpenRangingParams.getRanMultiplier())
872                 .setInitiationTimeMs(cccOpenRangingParams.getInitiationTimeMs())
873                 .build();
874         startRangingSession(
875                 cccOpenRangingParams, cccStartRangingParams, cccOpenRangingParams.getSessionId(),
876                 cccOpenRangingParamsAndBlocking.second, pw);
877     }
878 
startRangingSession(@onNull Params openRangingSessionParams, @Nullable Params startRangingSessionParams, int sessionId, boolean shouldBlockCall, @NonNull PrintWriter pw)879     private void startRangingSession(@NonNull Params openRangingSessionParams,
880             @Nullable Params startRangingSessionParams, int sessionId,
881             boolean shouldBlockCall, @NonNull PrintWriter pw) throws Exception {
882         if (sSessionIdToInfo.containsKey(sessionId)) {
883             pw.println("Session with session ID: " + sessionId
884                     + " already ongoing. Stop that session before you start a new session");
885             return;
886         }
887         AttributionSource attributionSource = new AttributionSource.Builder(Process.SHELL_UID)
888                 .setPackageName(SHELL_PACKAGE_NAME)
889                 .build();
890         SessionHandle sessionHandle =
891                 new SessionHandle(sSessionHandleIdNext++, attributionSource, Process.myPid());
892         SessionInfo sessionInfo =
893                 new SessionInfo(sessionId, sessionHandle, openRangingSessionParams, pw, false);
894         mUwbService.openRanging(
895                 attributionSource,
896                 sessionInfo.sessionHandle,
897                 sessionInfo.uwbRangingCbs,
898                 openRangingSessionParams.toBundle(),
899                 null);
900         boolean openCompleted = false;
901         try {
902             openCompleted = sessionInfo.rangingOpenedFuture.get(
903                     RANGE_CTL_TIMEOUT_MILLIS, MILLISECONDS);
904         } catch (InterruptedException | CancellationException | TimeoutException
905                 | ExecutionException e) {
906         }
907         if (!openCompleted) {
908             pw.println("Failed to open ranging session. Aborting!");
909             return;
910         }
911         pw.println("Ranging session opened with params: "
912                 + bundleToString(openRangingSessionParams.toBundle()));
913         sSessionIdToInfo.put(sessionId, sessionInfo);
914 
915         if (openRangingSessionParams instanceof  FiraOpenSessionParams
916                 && ((FiraOpenSessionParams) openRangingSessionParams).getDeviceRole()
917                 == RANGING_DEVICE_DT_TAG) {
918             DlTDoARangingRoundsUpdate rangingRounds = new DlTDoARangingRoundsUpdate.Builder()
919                     .setSessionId(sessionId)
920                     .setNoOfRangingRounds(1)
921                     .setRangingRoundIndexes(new byte[]{0})
922                     .build();
923             mUwbService.updateRangingRoundsDtTag(sessionInfo.sessionHandle,
924                     rangingRounds.toBundle());
925             boolean setRangingRounds = false;
926             try {
927                 setRangingRounds = sessionInfo.rangingOpenedFuture.get(
928                         RANGE_CTL_TIMEOUT_MILLIS, MILLISECONDS);
929             } catch (InterruptedException | CancellationException | TimeoutException
930                      | ExecutionException e) {
931             }
932             if (!setRangingRounds) {
933                 pw.println("Failed to set ranging rounds for DT tag");
934                 return;
935             }
936         }
937         mUwbService.startRanging(
938                 sessionInfo.sessionHandle,
939                 startRangingSessionParams != null
940                         ? startRangingSessionParams.toBundle()
941                         : new PersistableBundle());
942         boolean startCompleted = false;
943         try {
944             startCompleted = sessionInfo.rangingStartedFuture.get(
945                     RANGE_CTL_TIMEOUT_MILLIS, MILLISECONDS);
946         } catch (InterruptedException | CancellationException | TimeoutException
947                 | ExecutionException e) {
948         }
949         if (!startCompleted) {
950             pw.println("Failed to start ranging session. Aborting!");
951             return;
952         }
953         pw.println("Ranging session started for sessionId: " + sessionId);
954         while (shouldBlockCall) {
955             Thread.sleep(RANGE_CTL_TIMEOUT_MILLIS);
956         }
957     }
958 
stopRangingSession(PrintWriter pw)959     private void stopRangingSession(PrintWriter pw) throws RemoteException {
960         int sessionId = Integer.parseInt(getNextArgRequired());
961         stopRangingSession(pw, sessionId);
962     }
963 
stopRangingSession(PrintWriter pw, int sessionId)964     private void stopRangingSession(PrintWriter pw, int sessionId) throws RemoteException {
965         SessionInfo sessionInfo = sSessionIdToInfo.get(sessionId);
966         if (sessionInfo == null) {
967             pw.println("No active session with session ID: " + sessionId + " found");
968             return;
969         }
970         mUwbService.stopRanging(sessionInfo.sessionHandle);
971         boolean stopCompleted = false;
972         try {
973             stopCompleted = sessionInfo.rangingStoppedFuture.get(
974                     RANGE_CTL_TIMEOUT_MILLIS, MILLISECONDS);
975         } catch (InterruptedException | CancellationException | TimeoutException
976                 | ExecutionException e) {
977         }
978         if (!stopCompleted) {
979             pw.println("Failed to stop ranging session. Aborting!");
980             return;
981         }
982         pw.println("Ranging session stopped");
983 
984         mUwbService.closeRanging(sessionInfo.sessionHandle);
985         boolean closeCompleted = false;
986         try {
987             closeCompleted = sessionInfo.rangingClosedFuture.get(
988                     RANGE_CTL_TIMEOUT_MILLIS, MILLISECONDS);
989         } catch (InterruptedException | CancellationException | TimeoutException
990                 | ExecutionException e) {
991         }
992         if (!closeCompleted) {
993             pw.println("Failed to close ranging session. Aborting!");
994             return;
995         }
996         pw.println("Ranging session closed");
997     }
998 
buildFiraReconfigureParams()999     private FiraRangingReconfigureParams buildFiraReconfigureParams() {
1000         FiraRangingReconfigureParams.Builder builder =
1001                 new FiraRangingReconfigureParams.Builder();
1002         String option = getNextOption();
1003         while (option != null) {
1004             if (option.equals("-a")) {
1005                 String action = getNextArgRequired();
1006                 if (action.equals("add")) {
1007                     builder.setAction(MULTICAST_LIST_UPDATE_ACTION_ADD);
1008                 } else if (action.equals("delete")) {
1009                     builder.setAction(MULTICAST_LIST_UPDATE_ACTION_DELETE);
1010                 } else {
1011                     throw new IllegalArgumentException("Unexpected action " + action);
1012                 }
1013             }
1014             if (option.equals("-d")) {
1015                 String[] destAddressesString = getNextArgRequired().split(",");
1016                 List<UwbAddress> destAddresses = new ArrayList<>();
1017                 for (String destAddressString : destAddressesString) {
1018                     destAddresses.add(UwbAddress.fromBytes(
1019                             ByteBuffer.allocate(SHORT_ADDRESS_BYTE_LENGTH)
1020                                     .putShort(Short.parseShort(destAddressString))
1021                                     .array()));
1022                 }
1023                 builder.setAddressList(destAddresses.toArray(new UwbAddress[0]));
1024             }
1025             if (option.equals("-s")) {
1026                 String[] subSessionIdsString = getNextArgRequired().split(",");
1027                 List<Integer> subSessionIds = new ArrayList<>();
1028                 for (String subSessionIdString : subSessionIdsString) {
1029                     subSessionIds.add(Integer.parseInt(subSessionIdString));
1030                 }
1031                 builder.setSubSessionIdList(subSessionIds.stream().mapToInt(s -> s).toArray());
1032             }
1033             if (option.equals("-b")) {
1034                 int blockStrideLength = Integer.parseInt(getNextArgRequired());
1035                 builder.setBlockStrideLength(blockStrideLength);
1036             }
1037             if (option.equals("-c")) {
1038                 int rangeDataNtfConfig = Integer.parseInt(getNextArgRequired());
1039                 builder.setRangeDataNtfConfig(rangeDataNtfConfig);
1040             }
1041             if (option.equals("-n")) {
1042                 int proximityNear = Integer.parseInt(getNextArgRequired());
1043                 builder.setRangeDataProximityNear(proximityNear);
1044             }
1045             if (option.equals("-f")) {
1046                 int proximityFar = Integer.parseInt(getNextArgRequired());
1047                 builder.setRangeDataProximityFar(proximityFar);
1048             }
1049             option = getNextOption();
1050         }
1051         // TODO: Add remaining params if needed.
1052         return builder.build();
1053     }
1054 
reconfigureFiraRangingSession(PrintWriter pw)1055     private void reconfigureFiraRangingSession(PrintWriter pw) throws RemoteException {
1056         int sessionId = Integer.parseInt(getNextArgRequired());
1057         SessionInfo sessionInfo = sSessionIdToInfo.get(sessionId);
1058         if (sessionInfo == null) {
1059             pw.println("No active session with session ID: " + sessionId + " found");
1060             return;
1061         }
1062         FiraRangingReconfigureParams params = buildFiraReconfigureParams();
1063 
1064         mUwbService.reconfigureRanging(sessionInfo.sessionHandle, params.toBundle());
1065         boolean reconfigureCompleted = false;
1066         try {
1067             reconfigureCompleted = sessionInfo.rangingReconfiguredFuture.get(
1068                     RANGE_CTL_TIMEOUT_MILLIS, MILLISECONDS);
1069         } catch (InterruptedException | CancellationException | TimeoutException
1070                 | ExecutionException e) {
1071         }
1072         if (!reconfigureCompleted) {
1073             pw.println("Failed to reconfigure ranging session. Aborting!");
1074             return;
1075         }
1076         pw.println("Ranging session reconfigured");
1077     }
1078 
runTaskOnSingleThreadExecutor(FutureTask<Integer> task)1079     private int runTaskOnSingleThreadExecutor(FutureTask<Integer> task) {
1080         try {
1081             return mUwbInjector.runTaskOnSingleThreadExecutor(task, CMD_TIMEOUT_MS);
1082         } catch (TimeoutException | InterruptedException | ExecutionException e) {
1083             Log.e(TAG, "Failed to send command", e);
1084         }
1085         return -1;
1086     }
1087 
buildRadarOpenSessionParams()1088     private Pair<RadarOpenSessionParams, Boolean> buildRadarOpenSessionParams() {
1089         RadarOpenSessionParams.Builder builder =
1090                 new RadarOpenSessionParams.Builder(DEFAULT_RADAR_OPEN_SESSION_PARAMS);
1091         boolean shouldBlockCall = false;
1092 
1093         for (String option = getNextOption(); option != null; option = getNextOption()) {
1094             switch (option) {
1095                 case "-b":
1096                     shouldBlockCall = true;
1097                     break;
1098                 case "-i":
1099                     builder.setSessionId(Integer.parseInt(getNextArgRequired()));
1100                     break;
1101                 case "-c":
1102                     builder.setChannelNumber(Integer.parseInt(getNextArgRequired()));
1103                     break;
1104                 case "-s":
1105                     builder.setSweepPeriod(Integer.parseInt(getNextArgRequired()));
1106                     break;
1107                 case "-u":
1108                     builder.setSweepsPerBurst(Integer.parseInt(getNextArgRequired()));
1109                     break;
1110                 case "-e":
1111                     builder.setSamplesPerSweep(Integer.parseInt(getNextArgRequired()));
1112                     break;
1113                 case "-o":
1114                     builder.setSweepOffset(Integer.parseInt(getNextArgRequired()));
1115                     break;
1116                 case "-r":
1117                     builder.setRframeConfig(Integer.parseInt(getNextArgRequired()));
1118                     break;
1119                 case "-t":
1120                     builder.setPreambleDuration(Integer.parseInt(getNextArgRequired()));
1121                     break;
1122                 case "-d":
1123                     builder.setPreambleCodeIndex(Integer.parseInt(getNextArgRequired()));
1124                     break;
1125                 case "-x":
1126                     builder.setSessionPriority(Integer.parseInt(getNextArgRequired()));
1127                     break;
1128                 case "-p":
1129                     builder.setBitsPerSample(Integer.parseInt(getNextArgRequired()));
1130                     break;
1131                 case "-m":
1132                     builder.setPrfMode(Integer.parseInt(getNextArgRequired()));
1133                     break;
1134                 case "-n":
1135                     builder.setNumberOfBursts(Integer.parseInt(getNextArgRequired()));
1136                     break;
1137             }
1138         }
1139         return Pair.create(builder.build(), shouldBlockCall);
1140     }
1141 
startRadarSession(PrintWriter pw)1142     private void startRadarSession(PrintWriter pw) throws Exception {
1143         Pair<RadarOpenSessionParams, Boolean> radarOpenSessionParamsAndBlocking =
1144                 buildRadarOpenSessionParams();
1145         RadarOpenSessionParams radarOpenSessionParams = radarOpenSessionParamsAndBlocking.first;
1146         int sessionId = radarOpenSessionParams.getSessionId();
1147 
1148         if (sSessionIdToInfo.containsKey(sessionId)) {
1149             pw.println("Session with session ID: " + sessionId
1150                     + " already ongoing. Stop that session before you start a new session");
1151             return;
1152         }
1153         AttributionSource attributionSource = new AttributionSource.Builder(Process.SHELL_UID)
1154                 .setPackageName(SHELL_PACKAGE_NAME)
1155                 .build();
1156         SessionHandle sessionHandle =
1157                 new SessionHandle(sSessionHandleIdNext++, attributionSource, Process.myPid());
1158         SessionInfo sessionInfo =
1159                 new SessionInfo(sessionId, sessionHandle, radarOpenSessionParams, pw, true);
1160         mUwbService.openRanging(
1161                 attributionSource,
1162                 sessionInfo.sessionHandle,
1163                 sessionInfo.uwbRangingCbs,
1164                 radarOpenSessionParams.toBundle(),
1165                 null);
1166         boolean openCompleted = false;
1167         try {
1168             openCompleted = sessionInfo.rangingOpenedFuture.get(
1169                     RANGE_CTL_TIMEOUT_MILLIS, MILLISECONDS);
1170         } catch (InterruptedException | CancellationException | TimeoutException
1171                 | ExecutionException e) {
1172         }
1173         if (!openCompleted) {
1174             pw.println("Failed to open radar session. Aborting!");
1175             return;
1176         }
1177         pw.println("Radar session opened with params: "
1178                 + bundleToString(radarOpenSessionParams.toBundle()));
1179 
1180         mUwbService.startRanging(sessionInfo.sessionHandle, new PersistableBundle());
1181         boolean startCompleted = false;
1182         try {
1183             startCompleted = sessionInfo.rangingStartedFuture.get(
1184                     RANGE_CTL_TIMEOUT_MILLIS, MILLISECONDS);
1185         } catch (InterruptedException | CancellationException | TimeoutException
1186                 | ExecutionException e) {
1187         }
1188         if (!startCompleted) {
1189             pw.println("Failed to start radar session. Aborting!");
1190             return;
1191         }
1192         pw.println("Radar session started for sessionId: " + sessionId);
1193         sSessionIdToInfo.put(sessionId, sessionInfo);
1194         while (radarOpenSessionParamsAndBlocking.second) {
1195             Thread.sleep(RANGE_CTL_TIMEOUT_MILLIS);
1196         }
1197     }
1198 
1199     @Override
onCommand(String cmd)1200     public int onCommand(String cmd) {
1201         // Treat no command as help command.
1202         if (cmd == null || cmd.equals("")) {
1203             cmd = "help";
1204         }
1205         // Explicit exclusion from root permission
1206         if (ArrayUtils.indexOf(NON_PRIVILEGED_COMMANDS, cmd) == -1) {
1207             final int uid = Binder.getCallingUid();
1208             if (uid != Process.ROOT_UID) {
1209                 throw new SecurityException(
1210                         "Uid " + uid + " does not have access to " + cmd + " uwb command "
1211                                 + "(or such command doesn't exist)");
1212             }
1213         }
1214 
1215         final PrintWriter pw = getOutPrintWriter();
1216         try {
1217             switch (cmd) {
1218                 case "force-country-code": {
1219                     boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
1220                     FutureTask<Integer> task;
1221                     if (enabled) {
1222                         String countryCode = getNextArgRequired();
1223                         if (!UwbCountryCode.isValid(countryCode)) {
1224                             pw.println("Invalid argument: Country code must be a 2-Character"
1225                                     + " alphanumeric code. But got countryCode " + countryCode
1226                                     + " instead");
1227                             return -1;
1228                         }
1229                         task = new FutureTask<>(() -> {
1230                             mUwbCountryCode.setOverrideCountryCode(countryCode);
1231                             return 0;
1232                         });
1233                     } else {
1234                         task = new FutureTask<>(() -> {
1235                             mUwbCountryCode.clearOverrideCountryCode();
1236                             return 0;
1237                         });
1238                     }
1239                     return runTaskOnSingleThreadExecutor(task);
1240                 }
1241                 case "get-country-code":
1242                     pw.println("Uwb Country Code = " + mUwbCountryCode.getCountryCode());
1243                     return 0;
1244                 case "simulate-app-state-change": {
1245                     String appPackageName = getNextArgRequired();
1246                     String nextArg = getNextArg();
1247                     if (nextArg != null) {
1248                         boolean isFg = argTrueOrFalse(nextArg, "foreground", "background");
1249                         int importance = isFg ? IMPORTANCE_FOREGROUND : IMPORTANCE_BACKGROUND;
1250                         int uid = 0;
1251                         try {
1252                             uid = mContext.getPackageManager().getApplicationInfo(
1253                                     appPackageName, 0).uid;
1254                         } catch (PackageManager.NameNotFoundException e) {
1255                             pw.println("Unable to find package name: " + appPackageName);
1256                             return -1;
1257                         }
1258                         mUwbInjector.setOverridePackageImportance(appPackageName, importance);
1259                         mUwbInjector.getUwbSessionManager().onUidImportance(uid, importance);
1260                     } else {
1261                         mUwbInjector.resetOverridePackageImportance(appPackageName);
1262                     }
1263                     return 0;
1264                 }
1265                 case "set-log-mode": {
1266                     String logMode = getNextArgRequired();
1267                     if (!UciLogModeStore.isValid(logMode)) {
1268                         pw.println("Invalid argument: Log mode must be one of the following:"
1269                                 + " Disabled, Filtered, or Unfiltered. But got log mode " + logMode
1270                                 + " instead");
1271                         return -1;
1272                     }
1273                     mUciLogModeStore.storeMode(logMode);
1274                     if (!mNativeUwbManager.setLogMode(logMode)) {
1275                         pw.println("Failed to set log mode. " + logMode
1276                                 + " log mode will be set on next UWB restart");
1277                         return -1;
1278                     }
1279                     return 0;
1280                 }
1281                 case "get-log-mode":
1282                     pw.println("UWB Log Mode = " + mUciLogModeStore.getMode());
1283                     return 0;
1284                 case "status":
1285                     printStatus(pw);
1286                     return 0;
1287                 case "enable-uwb":
1288                     mUwbService.setEnabled(true);
1289                     return 0;
1290                 case "disable-uwb":
1291                     mUwbService.setEnabled(false);
1292                     return 0;
1293                 case "enable-uwb-hw": {
1294                     AttributionSource attributionSource = new AttributionSource.Builder(
1295                             Process.SHELL_UID)
1296                             .setPackageName(SHELL_PACKAGE_NAME)
1297                             .build();
1298                     mUwbService.requestHwEnabled(true, attributionSource, new Binder());
1299                     return 0;
1300                 }
1301                 case "disable-uwb-hw": {
1302                     AttributionSource attributionSource = new AttributionSource.Builder(
1303                             Process.SHELL_UID)
1304                             .setPackageName(SHELL_PACKAGE_NAME)
1305                             .build();
1306                     mUwbService.requestHwEnabled(false, attributionSource, new Binder());
1307                     return 0;
1308                 }
1309                 case "start-dl-tdoa-ranging-session":
1310                     startDlTDoaRangingSession(pw);
1311                     return 0;
1312                 case "start-fira-ranging-session":
1313                     startFiraRangingSession(pw);
1314                     return 0;
1315                 case "start-ccc-ranging-session":
1316                     startCccRangingSession(pw);
1317                     return 0;
1318                 case "start-radar-session":
1319                     startRadarSession(pw);
1320                     return 0;
1321                 case "reconfigure-fira-ranging-session":
1322                     reconfigureFiraRangingSession(pw);
1323                     return 0;
1324                 case "get-ranging-session-reports": {
1325                     int sessionId = Integer.parseInt(getNextArgRequired());
1326                     SessionInfo sessionInfo = sSessionIdToInfo.get(sessionId);
1327                     if (sessionInfo == null) {
1328                         pw.println("No active session with session ID: " + sessionId + " found");
1329                         return -1;
1330                     }
1331                     pw.println("Last Ranging results:");
1332                     for (RangingReport rangingReport : sessionInfo.lastRangingReports) {
1333                         pw.println(rangingReport);
1334                     }
1335                     return 0;
1336                 }
1337                 case "get-all-ranging-session-reports": {
1338                     for (SessionInfo sessionInfo: sSessionIdToInfo.values()) {
1339                         pw.println("Last Ranging results for sessionId " + sessionInfo.sessionId
1340                                 + ":");
1341                         for (RangingReport rangingReport : sessionInfo.lastRangingReports) {
1342                             pw.println(rangingReport);
1343                         }
1344                     }
1345                     return 0;
1346                 }
1347                 case "stop-ranging-session":
1348                 case "stop-radar-session":
1349                     stopRangingSession(pw);
1350                     return 0;
1351                 case "stop-all-ranging-sessions": {
1352                     for (int sessionId : new ArrayList<>(sSessionIdToInfo.keySet())) {
1353                         if (!sSessionIdToInfo.get(sessionId).isRadarSession) {
1354                             stopRangingSession(pw, sessionId);
1355                         }
1356                     }
1357                     return 0;
1358                 }
1359                 case "stop-all-radar-sessions": {
1360                     for (int sessionId : new ArrayList<>(sSessionIdToInfo.keySet())) {
1361                         if (sSessionIdToInfo.get(sessionId).isRadarSession) {
1362                             stopRangingSession(pw, sessionId);
1363                         }
1364                     }
1365                     return 0;
1366                 }
1367                 case "get-specification-info": {
1368                     PersistableBundle bundle = mUwbService.getSpecificationInfo(null);
1369                     pw.println("Specification info: " + bundleToString(bundle));
1370                     return 0;
1371                 }
1372                 case "get-power-stats": {
1373                     PersistableBundle bundle = mUwbService.getSpecificationInfo(null);
1374                     GenericSpecificationParams params =
1375                             GenericSpecificationParams.fromBundle(bundle);
1376                     if (params == null) {
1377                         pw.println("Spec info is empty");
1378                         return -1;
1379                     }
1380                     if (params.hasPowerStatsSupport()) {
1381                         pw.println(mNativeUwbManager.getPowerStats(mUwbService.getDefaultChipId()));
1382                     } else {
1383                         pw.println("power stats query is not supported");
1384                     }
1385                     return 0;
1386                 }
1387                 case "enable-diagnostics-notification": {
1388                     byte diagramFrameReportsFlags = 0;
1389                     String option = getNextOption();
1390                     while (option != null) {
1391                         if (option.equals("-r")) {
1392                             diagramFrameReportsFlags |= RSSI_FLAG;
1393                         }
1394                         if (option.equals("-a")) {
1395                             diagramFrameReportsFlags |= AOA_FLAG;
1396                         }
1397                         if (option.equals("-c")) {
1398                             diagramFrameReportsFlags |= CIR_FLAG;
1399                         }
1400                         if (option.equals("-s")) {
1401                             diagramFrameReportsFlags |= SEGMENT_METRICS_FLAG;
1402                         }
1403                         option = getNextOption();
1404                     }
1405                     mUwbServiceCore.enableDiagnostics(true, diagramFrameReportsFlags);
1406                     return 0;
1407                 }
1408                 case "disable-diagnostics-notification": {
1409                     mUwbServiceCore.enableDiagnostics(false, (byte) 0);
1410                     return 0;
1411                 }
1412                 case "take-bugreport": {
1413                     new Handler(mLooper).post(() -> {
1414                         if (mDeviceConfig.isDeviceErrorBugreportEnabled()) {
1415                             mUwbDiagnostics.takeBugReport("Uwb bugreport test");
1416                         }
1417                     });
1418                     return 0;
1419                 }
1420                 default:
1421                     return handleDefaultCommands(cmd);
1422             }
1423         } catch (IllegalArgumentException e) {
1424             pw.println("Invalid args for " + cmd + ": ");
1425             e.printStackTrace(pw);
1426             return -1;
1427         } catch (Exception e) {
1428             pw.println("Exception while executing UwbShellCommand" + cmd + ": ");
1429             e.printStackTrace(pw);
1430             return -1;
1431         }
1432     }
1433 
argTrueOrFalse(String arg, String trueString, String falseString)1434     private static boolean argTrueOrFalse(String arg, String trueString, String falseString) {
1435         if (trueString.equals(arg)) {
1436             return true;
1437         } else if (falseString.equals(arg)) {
1438             return false;
1439         } else {
1440             throw new IllegalArgumentException("Expected '" + trueString + "' or '" + falseString
1441                     + "' as next arg but got '" + arg + "'");
1442         }
1443 
1444     }
1445 
getNextArgRequiredTrueOrFalse(String trueString, String falseString)1446     private boolean getNextArgRequiredTrueOrFalse(String trueString, String falseString)
1447             throws IllegalArgumentException {
1448         String nextArg = getNextArgRequired();
1449         return argTrueOrFalse(nextArg, trueString, falseString);
1450     }
1451 
printStatus(PrintWriter pw)1452     private void printStatus(PrintWriter pw) throws RemoteException {
1453         int adapterState = mUwbService.getAdapterState();
1454         boolean uwbEnabled = adapterState != UwbManager.AdapterStateCallback.STATE_DISABLED;
1455         boolean uwbHwIdle = adapterState == UwbManager.AdapterStateCallback.STATE_ENABLED_HW_IDLE;
1456         String status;
1457         if (uwbHwIdle) {
1458             status = "enabled by user, but no clients have voted to enable hw";
1459         } else if (uwbEnabled) {
1460             status = "enabled";
1461         } else {
1462             status = "disabled";
1463         }
1464         pw.println("Uwb is " + status);
1465     }
1466 
onHelpNonPrivileged(PrintWriter pw)1467     private void onHelpNonPrivileged(PrintWriter pw) {
1468         pw.println("  status");
1469         pw.println("    Gets status of UWB stack");
1470         pw.println("  get-country-code");
1471         pw.println("    Gets country code as a two-letter string");
1472         pw.println("  get-log-mode");
1473         pw.println("    Get the log mode for UCI packet capturing");
1474         pw.println("  enable-uwb");
1475         pw.println("    Toggle UWB on");
1476         pw.println("  disable-uwb");
1477         pw.println("    Toggle UWB off");
1478         pw.println("  enable-uwb-hw");
1479         pw.println("    If 'hw_idle_turn_off_enabled' feature is enabled, vote for UWB on");
1480         pw.println("  disable-uwb-hw");
1481         pw.println("    If 'hw_idle_turn_off_enabled' feature is enabled, vote for UWB off");
1482         pw.println("  start-fira-ranging-session"
1483                 + " [-b](blocking call)"
1484                 + " [-i <sessionId>](session-id)"
1485                 + " [-c <channel>](channel)"
1486                 + " [-t controller|controlee](device-type)"
1487                 + " [-r initiator|responder](device-role)"
1488                 + " [-a <deviceAddress>](device-address)"
1489                 + " [-d <destAddress-1, destAddress-2,...>](dest-addresses)"
1490                 + " [-m <unicast|one-to-many|many-to-many>](multi-node mode)"
1491                 + " [-u ds-twr|ss-twr|ds-twr-non-deferred|ss-twr-non-deferred](round-usage)"
1492                 + " [-l <ranging-interval-ms>](ranging-interval-ms)"
1493                 + " [-s <slots-per-ranging-round>](slots-per-ranging-round)"
1494                 + " [-x <proximity-near-cm, proximity-far-cm>](range-data-ntf-proximity)"
1495                 + " [-z <numRangeMrmts, numAoaAzimuthMrmts, numAoaElevationMrmts>"
1496                 + "(interleaving-ratio)"
1497                 + " [-e none|enabled|azimuth-only|elevation-only](aoa type)"
1498                 + " [-f <tof,azimuth,elevation,aoa-fom>(result-report-config)"
1499                 + " [-g <staticStsIV>(staticStsIV 6-bytes)"
1500                 + " [-v <staticStsVendorId>(staticStsVendorId 2-bytes)"
1501                 + " [-w enabled|disabled](has-result-report-phase)"
1502                 + " [-y enabled|disabled](hopping-mode, default = disabled)"
1503                 + " [-p <preamble-code-index>](preamble-code-index, default = 10)"
1504                 + " [-h <slot-duration-rstu>(slot-duration-rstu, default=2400)"
1505                 + " [-o static|provisioned](sts-config-type)"
1506                 + " [-n <sessionKey>](sessionKey 16 or 32 bytes)"
1507                 + " [-k <subSessionKey>](subSessionKey 16 or 32 bytes)"
1508                 + " [-j <errorStreakTimeoutMs>](error streak timeout in millis, default=30000)"
1509                 + " [-q <sessionPriority>](sessionPriority 1-49 or 51-100)"
1510                 + " [-P bprf|hprf](prfMode)"
1511                 + " [-D 6m81|7m80|27m2|31m2](psduDataRate)"
1512                 + " [-B 850k|6m81](bprfPhrDataRate)"
1513                 + " [-A enabled|disabled](TX adaptive power, default = disabled)"
1514                 + " [-S <sfd_id>](sfd_id 0-4, default = 2)"
1515                 + " [-R enabled|disabled](range-data-notification)");
1516         pw.println("    Starts a FIRA ranging session with the provided params."
1517                 + " Note: default behavior is to cache the latest ranging reports which can be"
1518                 + " retrieved using |get-ranging-session-reports|");
1519         pw.println("  start-dl-tdoa-ranging-session"
1520                         + " [-i <sessionId>](session-id)");
1521         pw.println("    Starts a FIRA Dl-TDoA ranging session for DT-Tag");
1522         pw.println("  start-ccc-ranging-session"
1523                 + " [-b](blocking call)"
1524                 + " Ranging reports will be displayed on screen)"
1525                 + " [-u 0|1](uwb-config)"
1526                 + " [-p <tx>,<rx>](pulse-shape-combo)"
1527                 + " [-i <sessionId>](session-id)"
1528                 + " [-r <ran_multiplier>](ran-multiplier)"
1529                 + " [-c <channel>](channel)"
1530                 + " [-m <num-chaps-per-slot>](num-chaps-per-slot)"
1531                 + " [-n <num-responder-nodes>](num-responder-nodes)"
1532                 + " [-o <num-slots-per-round>](num-slots-per-round)"
1533                 + " [-s <sync-code-index>](sync-code-index)"
1534                 + " [-h none|continuous|adaptive](hopping-config-mode)"
1535                 + " [-a default|aes](hopping-sequence)");
1536         pw.println("    Starts a CCC ranging session with the provided params."
1537                 + " Note: default behavior is to cache the latest ranging reports which can be"
1538                 + " retrieved using |get-ranging-session-reports|");
1539         pw.println("  start-radar-session"
1540                 + " [-b](blocking call)"
1541                 + " Radar data will be displayed on screen)"
1542                 + " [-i <sessionId>](session-id)"
1543                 + " [-c <channel>](channel)"
1544                 + " [-s <sweepPeriod>](sweep-period)"
1545                 + " [-u <sweepsPerBurst>](sweeps-per-burst)"
1546                 + " [-e <samplesPerSweep>](samples-per-sweep)"
1547                 + " [-p <bitsPerSample>](bits-per-sample)"
1548                 + " [-o <sweepOffset>](sweep-offset)"
1549                 + " [-r <rframeConfig>](rframe-config)"
1550                 + " [-t <preambleDuration>](preamble-duration)"
1551                 + " [-d <preambleCodeIndex>](preamble-code-index)"
1552                 + " [-x  <sessionPriority>](session-priority)"
1553                 + " [-m <prfMode>](prf-mode)"
1554                 + " [-n <numberOfBursts>](number-of-bursts)");
1555         pw.println("    Starts a Radar session with the provided params defined in the radar UCI"
1556                 + "    spec.");
1557         pw.println("  reconfigure-fira-ranging-session"
1558                 + " <sessionId>"
1559                 + " [-a add|delete](action)"
1560                 + " [-d <destAddress-1, destAddress-2,...>](dest-addresses)"
1561                 + " [-s <subSessionId-1, subSessionId-2,...>](sub-sessionIds)"
1562                 + " [-b <block-striding>](block-striding)"
1563                 + " [-c <range-data-ntf-cfg>](range-data-ntf-cfg)"
1564                 + " [-n <proximity-near>(proximity-near)"
1565                 + " [-f <proximity-far>](proximity-far)");
1566         pw.println("  get-ranging-session-reports <sessionId>");
1567         pw.println("    Displays latest cached ranging reports for an ongoing ranging session");
1568         pw.println("  get-all-ranging-session-reports");
1569         pw.println("    Displays latest cached ranging reports for all ongoing ranging session");
1570         pw.println("  stop-ranging-session <sessionId>");
1571         pw.println("    Stops an ongoing ranging session");
1572         pw.println("  stop-radar-session <sessionId>");
1573         pw.println("    Stops an ongoing radar session");
1574         pw.println("  stop-all-ranging-sessions");
1575         pw.println("    Stops all ongoing ranging sessions");
1576         pw.println("  stop-all-radar-sessions");
1577         pw.println("    Stops all ongoing radar sessions");
1578         pw.println("  get-specification-info");
1579         pw.println("    Gets specification info from uwb chip");
1580         pw.println("  enable-diagnostics-notification"
1581                 + " [-r](enable rssi)"
1582                 + " [-a](enable aoa)"
1583                 + " [-c](enable cir)"
1584                 + " [-s](enable segment metrics)");
1585         pw.println("    Enable vendor diagnostics notification");
1586         pw.println("  disable-diagnostics-notification");
1587         pw.println("    Disable vendor diagnostics notification");
1588         pw.println("  take-bugreport");
1589         pw.println("    take bugreport through betterBug or alternatively bugreport manager");
1590         pw.println("  simulate-app-state-change <package-name> foreground|background");
1591         pw.println("    Simulate app moving to foreground/background to test stack handling");
1592     }
1593 
onHelpPrivileged(PrintWriter pw)1594     private void onHelpPrivileged(PrintWriter pw) {
1595         pw.println("  force-country-code enabled <two-letter code> | disabled ");
1596         pw.println("    Sets country code to <two-letter code> or left for normal value");
1597         pw.println("  get-power-stats");
1598         pw.println("    Get power stats");
1599         pw.println("  set-log-mode disabled|filtered|unfiltered");
1600         pw.println("    Sets the log mode for UCI packet capturing");
1601     }
1602 
1603     @Override
onHelp()1604     public void onHelp() {
1605         final PrintWriter pw = getOutPrintWriter();
1606         pw.println("UWB (ultra wide-band) commands:");
1607         pw.println("  help or -h");
1608         pw.println("    Print this help text.");
1609         onHelpNonPrivileged(pw);
1610         if (Binder.getCallingUid() == Process.ROOT_UID) {
1611             onHelpPrivileged(pw);
1612         }
1613         pw.println();
1614     }
1615 
1616     @VisibleForTesting
reset()1617     public void reset() {
1618         sSessionHandleIdNext = 0;
1619         sSessionIdToInfo.clear();
1620     }
1621 }
1622