1 /*
2  * Copyright 2022 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.google.snippet.uwb;
18 
19 import android.app.UiAutomation;
20 import android.content.Context;
21 import android.net.ConnectivityManager;
22 import android.os.Build;
23 import android.os.PersistableBundle;
24 import android.uwb.RangingMeasurement;
25 import android.uwb.RangingReport;
26 import android.uwb.RangingSession;
27 import android.uwb.UwbAddress;
28 import android.uwb.UwbManager;
29 
30 import androidx.test.platform.app.InstrumentationRegistry;
31 
32 import com.google.android.mobly.snippet.Snippet;
33 import com.google.android.mobly.snippet.event.EventCache;
34 import com.google.android.mobly.snippet.event.SnippetEvent;
35 import com.google.android.mobly.snippet.rpc.AsyncRpc;
36 import com.google.android.mobly.snippet.rpc.Rpc;
37 import com.google.android.mobly.snippet.util.Log;
38 import com.google.uwb.support.ccc.CccOpenRangingParams;
39 import com.google.uwb.support.ccc.CccParams;
40 import com.google.uwb.support.ccc.CccPulseShapeCombo;
41 import com.google.uwb.support.ccc.CccRangingStartedParams;
42 import com.google.uwb.support.fira.FiraControleeParams;
43 import com.google.uwb.support.fira.FiraOpenSessionParams;
44 import com.google.uwb.support.fira.FiraParams;
45 import com.google.uwb.support.fira.FiraRangingReconfigureParams;
46 
47 import org.json.JSONArray;
48 import org.json.JSONException;
49 import org.json.JSONObject;
50 
51 import java.lang.reflect.Method;
52 import java.util.Arrays;
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Set;
56 import java.util.concurrent.Executor;
57 import java.util.concurrent.Executors;
58 
59 /** Snippet class exposing Android APIs for Uwb. */
60 public class UwbManagerSnippet implements Snippet {
61     private static class UwbManagerSnippetException extends Exception {
62 
UwbManagerSnippetException(String msg)63         UwbManagerSnippetException(String msg) {
64             super(msg);
65         }
66 
UwbManagerSnippetException(String msg, Throwable err)67         UwbManagerSnippetException(String msg, Throwable err) {
68             super(msg, err);
69         }
70     }
71 
72     private static final String TAG = "UwbManagerSnippet: ";
73     private final UwbManager mUwbManager;
74     private final ConnectivityManager mConnectivityManager;
75     private final Context mContext;
76     private final Executor mExecutor = Executors.newSingleThreadExecutor();
77     private final EventCache mEventCache = EventCache.getInstance();
78     private static HashMap<String, RangingSessionCallback> sRangingSessionCallbackMap =
79             new HashMap<String, RangingSessionCallback>();
80     private static HashMap<String, UwbAdapterStateCallback> sUwbAdapterStateCallbackMap =
81             new HashMap<String, UwbAdapterStateCallback>();
82 
UwbManagerSnippet()83     public UwbManagerSnippet() throws Throwable {
84         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
85         mUwbManager = mContext.getSystemService(UwbManager.class);
86         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
87         adoptShellPermission();
88     }
89 
90     private enum Event {
91         Invalid(0),
92         Opened(1 << 0),
93         Started(1 << 1),
94         Reconfigured(1 << 2),
95         Stopped(1 << 3),
96         Closed(1 << 4),
97         OpenFailed(1 << 5),
98         StartFailed(1 << 6),
99         ReconfigureFailed(1 << 7),
100         StopFailed(1 << 8),
101         CloseFailed(1 << 9),
102         ReportReceived(1 << 10),
103         ControleeAdded(1 << 11),
104         ControleeAddFailed(1 << 12),
105         ControleeRemoved(1 << 13),
106         ControleeRemoveFailed(1 << 14),
107         Paused(1 << 15),
108         PauseFailed(1 << 16),
109         Resumed(1 << 17),
110         ResumeFailed(1 << 18),
111         DataSent(1 << 19),
112         DataSendFailed(1 << 20),
113         DataReceived(1 << 21),
114         DataReceiveFailed(1 << 22),
115         ServiceDiscovered(1 << 23),
116         ServiceConnected(1 << 24),
117         RangingRoundsUpdateDtTagStatus(1 << 25),
118         EventAll(
119                 1 << 0
120                 | 1 << 1
121                 | 1 << 2
122                 | 1 << 3
123                 | 1 << 4
124                 | 1 << 5
125                 | 1 << 6
126                 | 1 << 7
127                 | 1 << 8
128                 | 1 << 9
129                 | 1 << 10
130                 | 1 << 11
131                 | 1 << 12
132                 | 1 << 13
133                 | 1 << 14
134                 | 1 << 15
135                 | 1 << 16
136                 | 1 << 17
137                 | 1 << 18
138                 | 1 << 19
139                 | 1 << 20
140                 | 1 << 21
141                 | 1 << 22
142                 | 1 << 23
143                 | 1 << 24
144                 | 1 << 25
145         );
146 
147         private final int mType;
Event(int type)148         Event(int type) {
149             mType = type;
150         }
getType()151         private int getType() {
152             return mType;
153         }
154     }
155 
156     class UwbAdapterStateCallback implements UwbManager.AdapterStateCallback {
157 
158         public String mId;
159 
UwbAdapterStateCallback(String id)160         UwbAdapterStateCallback(String id) {
161             mId = id;
162         }
163 
toString(int state)164         public String toString(int state) {
165             switch (state) {
166                 case 1: return "Inactive";
167                 case 2: return "Active";
168                 case 3: return "HwIdle";
169                 default: return "Disabled";
170             }
171         }
172 
173         @Override
onStateChanged(int state, int reason)174         public void onStateChanged(int state, int reason) {
175             Log.d(TAG + "UwbAdapterStateCallback#onStateChanged() called");
176             Log.d(TAG + "Adapter state " + String.valueOf(state)
177                     + ", state changed reason " + String.valueOf(reason));
178             SnippetEvent event = new SnippetEvent(mId, "UwbAdapterStateCallback");
179             event.getData().putString("uwbAdapterStateEvent", toString(state));
180             mEventCache.postEvent(event);
181         }
182     }
183 
184     class RangingSessionCallback implements RangingSession.Callback {
185 
186         public RangingSession rangingSession;
187         public PersistableBundle persistableBundle;
188         public PersistableBundle sessionInfo;
189         public RangingReport rangingReport;
190         public String mId;
191         public UwbAddress uwbAddress;
192         public byte[] dataReceived;
193 
RangingSessionCallback(String id, int events)194         RangingSessionCallback(String id, int events) {
195             mId = id;
196         }
197 
handleEvent(Event e)198         private void handleEvent(Event e) {
199             Log.d(TAG + "RangingSessionCallback#handleEvent() for " + e.toString());
200             SnippetEvent event = new SnippetEvent(mId, "RangingSessionCallback");
201             event.getData().putString("rangingSessionEvent", e.toString());
202             mEventCache.postEvent(event);
203         }
204 
205         @Override
onOpened(RangingSession session)206         public void onOpened(RangingSession session) {
207             Log.d(TAG + "RangingSessionCallback#onOpened() called");
208             rangingSession = session;
209             handleEvent(Event.Opened);
210         }
211 
212         @Override
onOpenFailed(int reason, PersistableBundle params)213         public void onOpenFailed(int reason, PersistableBundle params) {
214             Log.d(TAG + "RangingSessionCallback#onOpenedFailed() called");
215             Log.d(TAG + "OpenFailed reason " + String.valueOf(reason));
216             persistableBundle = params;
217             handleEvent(Event.OpenFailed);
218         }
219 
220         @Override
onStarted(PersistableBundle info)221         public void onStarted(PersistableBundle info) {
222             Log.d(TAG + "RangingSessionCallback#onStarted() called");
223             sessionInfo = info;
224             handleEvent(Event.Started);
225         }
226 
227         @Override
onStartFailed(int reason, PersistableBundle params)228         public void onStartFailed(int reason, PersistableBundle params) {
229             Log.d(TAG + "RangingSessionCallback#onStartFailed() called");
230             Log.d(TAG + "StartFailed reason " + String.valueOf(reason));
231             persistableBundle = params;
232             handleEvent(Event.StartFailed);
233         }
234 
235         @Override
onReconfigured(PersistableBundle params)236         public void onReconfigured(PersistableBundle params) {
237             Log.d(TAG + "RangingSessionCallback#oniReconfigured() called");
238             persistableBundle = params;
239             handleEvent(Event.Reconfigured);
240         }
241 
242         @Override
onReconfigureFailed(int reason, PersistableBundle params)243         public void onReconfigureFailed(int reason, PersistableBundle params) {
244             Log.d(TAG + "RangingSessionCallback#onReconfigureFailed() called");
245             Log.d(TAG + "ReconfigureFailed reason " + String.valueOf(reason));
246             persistableBundle = params;
247             handleEvent(Event.ReconfigureFailed);
248         }
249 
250         @Override
onStopped(int reason, PersistableBundle params)251         public void onStopped(int reason, PersistableBundle params) {
252             Log.d(TAG + "RangingSessionCallback#onStopped() called");
253             Log.d(TAG + "Stopped reason " + String.valueOf(reason));
254             persistableBundle = params;
255             handleEvent(Event.Stopped);
256         }
257 
258         @Override
onStopFailed(int reason, PersistableBundle params)259         public void onStopFailed(int reason, PersistableBundle params) {
260             Log.d(TAG + "RangingSessionCallback#onStopFailed() called");
261             Log.d(TAG + "StopFailed reason " + String.valueOf(reason));
262             persistableBundle = params;
263             handleEvent(Event.StopFailed);
264         }
265 
266         @Override
onClosed(int reason, PersistableBundle params)267         public void onClosed(int reason, PersistableBundle params) {
268             Log.d(TAG + "RangingSessionCallback#onClosed() called");
269             Log.d(TAG + "Closed reason " + String.valueOf(reason));
270             persistableBundle = params;
271             handleEvent(Event.Closed);
272         }
273 
274         @Override
onReportReceived(RangingReport report)275         public void onReportReceived(RangingReport report) {
276             Log.d(TAG + "RangingSessionCallback#onReportReceived() called");
277             rangingReport = report;
278             handleEvent(Event.ReportReceived);
279         }
280 
281         @Override
onControleeAdded(PersistableBundle params)282         public void onControleeAdded(PersistableBundle params) {
283             Log.d(TAG + "RangingSessionCallback#onControleeAdded() called");
284             persistableBundle = params;
285             handleEvent(Event.ControleeAdded);
286 
287         }
288 
289         @Override
onControleeAddFailed( int reason, PersistableBundle params)290         public void onControleeAddFailed(
291                 int reason, PersistableBundle params) {
292             Log.d(TAG + "RangingSessionCallback#onControleeAddFailed() called");
293             Log.d(TAG + "ControleeAddFailed reason " + String.valueOf(reason));
294             persistableBundle = params;
295             handleEvent(Event.ControleeAddFailed);
296 
297         }
298 
299         @Override
onControleeRemoved(PersistableBundle params)300         public void onControleeRemoved(PersistableBundle params) {
301             Log.d(TAG + "RangingSessionCallback#onControleeRemoved() called");
302             persistableBundle = params;
303             handleEvent(Event.ControleeRemoved);
304         }
305 
306         @Override
onControleeRemoveFailed( int reason, PersistableBundle params)307         public void onControleeRemoveFailed(
308                 int reason, PersistableBundle params) {
309             Log.d(TAG + "RangingSessionCallback#onControleeRemoveFailed() called");
310             Log.d(TAG + "ControleeRemoveFailed reason " + String.valueOf(reason));
311             persistableBundle = params;
312             handleEvent(Event.ControleeRemoveFailed);
313         }
314 
315         @Override
onPaused(PersistableBundle params)316         public void onPaused(PersistableBundle params) {
317             Log.d(TAG + "RangingSessionCallback#onPaused() called");
318             persistableBundle = params;
319             handleEvent(Event.Paused);
320         }
321 
322         @Override
onPauseFailed(int reason, PersistableBundle params)323         public void onPauseFailed(int reason, PersistableBundle params) {
324             Log.d(TAG + "RangingSessionCallback#onPauseFailed() called");
325             Log.d(TAG + "PauseFailed reason " + String.valueOf(reason));
326             persistableBundle = params;
327             handleEvent(Event.PauseFailed);
328         }
329 
330         @Override
onResumed(PersistableBundle params)331         public void onResumed(PersistableBundle params) {
332             Log.d(TAG + "RangingSessionCallback#onResumed() called");
333             persistableBundle = params;
334             handleEvent(Event.Resumed);
335         }
336 
337         @Override
onResumeFailed(int reason, PersistableBundle params)338         public void onResumeFailed(int reason, PersistableBundle params) {
339             Log.d(TAG + "RangingSessionCallback#onResumeFailed() called");
340             Log.d(TAG + "ResumeFailed reason " + String.valueOf(reason));
341             persistableBundle = params;
342             handleEvent(Event.ResumeFailed);
343         }
344 
345         @Override
onDataSent(UwbAddress remoteDeviceAddress, PersistableBundle params)346         public void onDataSent(UwbAddress remoteDeviceAddress,
347                 PersistableBundle params) {
348             Log.d(TAG + "RangingSessionCallback#onDataSent() called");
349             uwbAddress = getComputedMacAddress(remoteDeviceAddress);
350             persistableBundle = params;
351             handleEvent(Event.DataSent);
352         }
353 
354         @Override
onDataSendFailed(UwbAddress remoteDeviceAddress, int reason, PersistableBundle params)355         public void onDataSendFailed(UwbAddress remoteDeviceAddress,
356                 int reason, PersistableBundle params) {
357             Log.d(TAG + "RangingSessionCallback#onDataSendFailed() called");
358             Log.d(TAG + "DataSendFailed reason " + String.valueOf(reason));
359             uwbAddress = getComputedMacAddress(remoteDeviceAddress);
360             persistableBundle = params;
361             handleEvent(Event.DataSendFailed);
362         }
363 
364         @Override
onDataReceived(UwbAddress remoteDeviceAddress, PersistableBundle params, byte[] data)365         public void onDataReceived(UwbAddress remoteDeviceAddress,
366                 PersistableBundle params, byte[] data) {
367             Log.d(TAG + "RangingSessionCallback#onDataReceived() called");
368             uwbAddress = getComputedMacAddress(remoteDeviceAddress);
369             dataReceived = data;
370             persistableBundle = params;
371             handleEvent(Event.DataReceived);
372         }
373 
374         @Override
onDataReceiveFailed(UwbAddress remoteDeviceAddress, int reason, PersistableBundle params)375         public void onDataReceiveFailed(UwbAddress remoteDeviceAddress,
376                 int reason, PersistableBundle params) {
377             Log.d(TAG + "RangingSessionCallback#onDataReceiveFailed() called");
378             Log.d(TAG + "DataReceiveFailed reason " + String.valueOf(reason));
379             uwbAddress = getComputedMacAddress(remoteDeviceAddress);
380             persistableBundle = params;
381             handleEvent(Event.DataReceiveFailed);
382         }
383 
384         @Override
onServiceDiscovered(PersistableBundle params)385         public void onServiceDiscovered(PersistableBundle params) {
386             Log.d(TAG + "RangingSessionCallback#onServiceDiscovered() called");
387             persistableBundle = params;
388             handleEvent(Event.ServiceDiscovered);
389         }
390 
391         @Override
onServiceConnected(PersistableBundle params)392         public void onServiceConnected(PersistableBundle params) {
393             Log.d(TAG + "RangingSessionCallback#onServiceConnected() called");
394             persistableBundle = params;
395             handleEvent(Event.ServiceConnected);
396         }
397 
398         // TODO: This is only available in Android U SDK. So, expose it there only.
onRangingRoundsUpdateDtTagStatus(PersistableBundle params)399         public void onRangingRoundsUpdateDtTagStatus(PersistableBundle params) {
400             Log.d(TAG + "RangingSessionCallback#onRangingRoundsUpdateDtTagStatus() called");
401             persistableBundle = params;
402             handleEvent(Event.RangingRoundsUpdateDtTagStatus);
403         }
404     }
405 
406     /** Register uwb adapter state callback. */
407     @AsyncRpc(description = "Register uwb adapter state callback")
registerUwbAdapterStateCallback(String callbackId, String key)408     public void registerUwbAdapterStateCallback(String callbackId, String key) {
409         UwbAdapterStateCallback uwbAdapterStateCallback = new UwbAdapterStateCallback(callbackId);
410         sUwbAdapterStateCallbackMap.put(key, uwbAdapterStateCallback);
411         mUwbManager.registerAdapterStateCallback(mExecutor, uwbAdapterStateCallback);
412     }
413 
414     /** Unregister uwb adapter state callback. */
415     @Rpc(description = "Unregister uwb adapter state callback.")
unregisterUwbAdapterStateCallback(String key)416     public void unregisterUwbAdapterStateCallback(String key) {
417         UwbAdapterStateCallback uwbAdapterStateCallback = sUwbAdapterStateCallbackMap.get(key);
418         mUwbManager.unregisterAdapterStateCallback(uwbAdapterStateCallback);
419         sUwbAdapterStateCallbackMap.remove(key);
420     }
421 
422     /** Get UWB adapter state. */
423     @Rpc(description = "Get Uwb adapter state")
getAdapterState()424     public int getAdapterState() {
425         return mUwbManager.getAdapterState();
426     }
427 
428     /** Get the UWB state. */
429     @Rpc(description = "Get Uwb state")
isUwbEnabled()430     public boolean isUwbEnabled() {
431         return mUwbManager.isUwbEnabled();
432     }
433 
434     /** Set the UWB state. */
435     @Rpc(description = "Set Uwb state")
setUwbEnabled(boolean enabled)436     public void setUwbEnabled(boolean enabled) {
437         mUwbManager.setUwbEnabled(enabled);
438     }
439 
440     /** Get the UWB hardware state. */
441     @Rpc(description = "Get Uwb hardware state")
isUwbHwEnableRequested()442     public boolean isUwbHwEnableRequested() {
443         return mUwbManager.isUwbHwEnableRequested();
444     }
445 
446     /** Set the UWB hardware state. */
447     @Rpc(description = "Set Uwb hardware state")
requestUwbHwEnabled(boolean enabled)448     public void requestUwbHwEnabled(boolean enabled) {
449         mUwbManager.requestUwbHwEnabled(enabled);
450     }
451 
452     /** Get UWB HW idle feature state. */
453     @Rpc(description = "Get Uwb hardware idle feature state")
isUwbHwIdleTurnOffEnabled()454     public boolean isUwbHwIdleTurnOffEnabled() {
455         return mUwbManager.isUwbHwIdleTurnOffEnabled();
456     }
457 
convertJSONArrayToByteArray(JSONArray jArray)458     private byte[] convertJSONArrayToByteArray(JSONArray jArray) throws JSONException {
459         if (jArray == null) {
460             return null;
461         }
462         byte[] bArray = new byte[jArray.length()];
463         for (int i = 0; i < jArray.length(); i++) {
464             bArray[i] = (byte) jArray.getInt(i);
465         }
466         return bArray;
467     }
468 
generateFiraRangingReconfigureParams(JSONObject j)469     private FiraRangingReconfigureParams generateFiraRangingReconfigureParams(JSONObject j)
470             throws JSONException {
471         if (j == null) {
472             return null;
473         }
474         FiraRangingReconfigureParams.Builder builder = new FiraRangingReconfigureParams.Builder();
475         if (j.has("action")) {
476             builder.setAction(j.getInt("action"));
477         }
478         if (j.has("addressList")) {
479             JSONArray jArray = j.getJSONArray("addressList");
480             UwbAddress[] addressList = new UwbAddress[jArray.length()];
481             for (int i = 0; i < jArray.length(); i++) {
482                 addressList[i] = getComputedMacAddress(UwbAddress.fromBytes(
483                         convertJSONArrayToByteArray(jArray.getJSONArray(i))));
484             }
485             builder.setAddressList(addressList);
486         }
487         if (j.has("subSessionIdList")) {
488             JSONArray jArray = j.getJSONArray("subSessionIdList");
489             int[] subSessionIdList = new int[jArray.length()];
490             for (int i = 0; i < jArray.length(); i++) {
491                 subSessionIdList[i] = jArray.getInt(i);
492             }
493             builder.setSubSessionIdList(subSessionIdList);
494         }
495         if (j.has("subSessionKeyList")) {
496             JSONArray jSubSessionKeyListArray = j.getJSONArray("subSessionKeyList");
497             builder.setSubSessionKeyList(convertJSONArrayToByteArray(jSubSessionKeyListArray));
498         }
499         if (j.has("blockStrideLength")) {
500             builder.setBlockStrideLength(j.getInt("blockStrideLength"));
501         }
502         return builder.build();
503     }
504 
generateFiraControleeParams(JSONObject j)505     private FiraControleeParams generateFiraControleeParams(JSONObject j) throws JSONException {
506         if (j == null) {
507             return null;
508         }
509         FiraControleeParams.Builder builder = new FiraControleeParams.Builder();
510         if (j.has("action")) {
511             builder.setAction(j.getInt("action"));
512         }
513         if (j.has("addressList")) {
514             JSONArray jArray = j.getJSONArray("addressList");
515             UwbAddress[] addressList = new UwbAddress[jArray.length()];
516             for (int i = 0; i < jArray.length(); i++) {
517                 addressList[i] = getComputedMacAddress(UwbAddress.fromBytes(
518                         convertJSONArrayToByteArray(jArray.getJSONArray(i))));
519             }
520             builder.setAddressList(addressList);
521         }
522         if (j.has("subSessionIdList")) {
523             JSONArray jArray = j.getJSONArray("subSessionIdList");
524             int[] subSessionIdList = new int[jArray.length()];
525             for (int i = 0; i < jArray.length(); i++) {
526                 subSessionIdList[i] = jArray.getInt(i);
527             }
528             builder.setSubSessionIdList(subSessionIdList);
529         }
530         if (j.has("subSessionKeyList")) {
531             JSONArray jSubSessionKeyListArray = j.getJSONArray("subSessionKeyList");
532             builder.setSubSessionKeyList(convertJSONArrayToByteArray(jSubSessionKeyListArray));
533         }
534         return builder.build();
535     }
536 
generateCccRangingStartedParams(JSONObject j)537     private CccRangingStartedParams generateCccRangingStartedParams(JSONObject j)
538             throws JSONException {
539         if (j == null) {
540             return null;
541         }
542         CccRangingStartedParams.Builder builder = new CccRangingStartedParams.Builder();
543         if (j.has("stsIndex")) {
544             builder.setStartingStsIndex(j.getInt("stsIndex"));
545         }
546         if (j.has("uwbTime")) {
547             builder.setUwbTime0(j.getInt("uwbTime"));
548         }
549         if (j.has("hopModeKey")) {
550             builder.setHopModeKey(j.getInt("hopModeKey"));
551         }
552         if (j.has("syncCodeIndex")) {
553             builder.setSyncCodeIndex(j.getInt("syncCodeIndex"));
554         }
555         if (j.has("ranMultiplier")) {
556             builder.setRanMultiplier(j.getInt("ranMultiplier"));
557         }
558 
559         return builder.build();
560     }
561 
generateCccOpenRangingParams(JSONObject j)562     private CccOpenRangingParams generateCccOpenRangingParams(JSONObject j) throws JSONException {
563         if (j == null) {
564             return null;
565         }
566         CccOpenRangingParams.Builder builder = new CccOpenRangingParams.Builder();
567         builder.setProtocolVersion(CccParams.PROTOCOL_VERSION_1_0);
568         if (j.has("sessionId")) {
569             builder.setSessionId(j.getInt("sessionId"));
570         }
571         if (j.has("uwbConfig")) {
572             builder.setUwbConfig(j.getInt("uwbConfig"));
573         }
574         if (j.has("ranMultiplier")) {
575             builder.setRanMultiplier(j.getInt("ranMultiplier"));
576         }
577         if (j.has("channel")) {
578             builder.setChannel(j.getInt("channel"));
579         }
580         if (j.has("chapsPerSlot")) {
581             builder.setNumChapsPerSlot(j.getInt("chapsPerSlot"));
582         }
583         if (j.has("responderNodes")) {
584             builder.setNumResponderNodes(j.getInt("responderNodes"));
585         }
586         if (j.has("slotsPerRound")) {
587             builder.setNumSlotsPerRound(j.getInt("slotsPerRound"));
588         }
589         if (j.has("hoppingMode")) {
590             builder.setHoppingConfigMode(j.getInt("hoppingMode"));
591         }
592         if (j.has("hoppingSequence")) {
593             builder.setHoppingSequence(j.getInt("hoppingSequence"));
594         }
595         if (j.has("syncCodeIndex")) {
596             builder.setSyncCodeIndex(j.getInt("syncCodeIndex"));
597         }
598         if (j.has("pulseShapeCombo")) {
599             JSONObject pulseShapeCombo = j.getJSONObject("pulseShapeCombo");
600             builder.setPulseShapeCombo(new CccPulseShapeCombo(
601                     pulseShapeCombo.getInt("pulseShapeComboTx"),
602                     pulseShapeCombo.getInt("pulseShapeComboRx")));
603         }
604 
605         return builder.build();
606     }
607 
generateFiraOpenSessionParams(JSONObject j)608     private FiraOpenSessionParams generateFiraOpenSessionParams(JSONObject j) throws JSONException {
609         if (j == null) {
610             return null;
611         }
612         FiraOpenSessionParams.Builder builder = new FiraOpenSessionParams.Builder();
613         builder.setProtocolVersion(FiraParams.PROTOCOL_VERSION_1_1);
614         if (j.has("sessionId")) {
615             builder.setSessionId(j.getInt("sessionId"));
616         }
617         if (j.has("deviceType")) {
618             builder.setDeviceType(j.getInt("deviceType"));
619         }
620         if (j.has("deviceRole")) {
621             builder.setDeviceRole(j.getInt("deviceRole"));
622         }
623         if (j.has("rangingRoundUsage")) {
624             builder.setRangingRoundUsage(j.getInt("rangingRoundUsage"));
625         }
626         if (j.has("multiNodeMode")) {
627             builder.setMultiNodeMode(j.getInt("multiNodeMode"));
628         }
629         if (j.has("deviceAddress")) {
630             JSONArray jArray = j.getJSONArray("deviceAddress");
631             byte[] bArray = convertJSONArrayToByteArray(jArray);
632             UwbAddress deviceAddress = getComputedMacAddress(UwbAddress.fromBytes(bArray));
633             builder.setDeviceAddress(deviceAddress);
634         }
635         if (j.has("destinationAddresses")) {
636             JSONArray jArray = j.getJSONArray("destinationAddresses");
637             UwbAddress[] destinationUwbAddresses = new UwbAddress[jArray.length()];
638             for (int i = 0; i < jArray.length(); i++) {
639                 destinationUwbAddresses[i] = getComputedMacAddress(UwbAddress.fromBytes(
640                         convertJSONArrayToByteArray(jArray.getJSONArray(i))));
641             }
642             builder.setDestAddressList(Arrays.asList(destinationUwbAddresses));
643         }
644         if (j.has("initiationTimeMs")) {
645             builder.setInitiationTime(j.getInt("initiationTimeMs"));
646         }
647         if (j.has("slotDurationRstu")) {
648             builder.setSlotDurationRstu(j.getInt("slotDurationRstu"));
649         }
650         if (j.has("slotsPerRangingRound")) {
651             builder.setSlotsPerRangingRound(j.getInt("slotsPerRangingRound"));
652         }
653         if (j.has("rangingIntervalMs")) {
654             builder.setRangingIntervalMs(j.getInt("rangingIntervalMs"));
655         }
656         if (j.has("blockStrideLength")) {
657             builder.setBlockStrideLength(j.getInt("blockStrideLength"));
658         }
659         if (j.has("hoppingMode")) {
660             builder.setHoppingMode(j.getInt("hoppingMode"));
661         }
662         if (j.has("maxRangingRoundRetries")) {
663             builder.setMaxRangingRoundRetries(j.getInt("maxRangingRoundRetries"));
664         }
665         if (j.has("sessionPriority")) {
666             builder.setSessionPriority(j.getInt("sessionPriority"));
667         }
668         if (j.has("macAddressMode")) {
669             builder.setMacAddressMode(j.getInt("macAddressMode"));
670         }
671         if (j.has("inBandTerminationAttemptCount")) {
672             builder.setInBandTerminationAttemptCount(j.getInt("inBandTerminationAttemptCount"));
673         }
674         if (j.has("channel")) {
675             builder.setChannelNumber(j.getInt("channel"));
676         }
677         if (j.has("preamble")) {
678             builder.setPreambleCodeIndex(j.getInt("preamble"));
679         }
680         if (j.getInt("stsConfig") == FiraParams.STS_CONFIG_STATIC) {
681             JSONArray jVendorIdArray = j.getJSONArray("vendorId");
682             builder.setVendorId(getComputedVendorId(convertJSONArrayToByteArray(jVendorIdArray)));
683             JSONArray jStatisStsIVArray = j.getJSONArray("staticStsIV");
684             builder.setStaticStsIV(convertJSONArrayToByteArray(jStatisStsIVArray));
685         } else if (j.getInt("stsConfig") == FiraParams.STS_CONFIG_PROVISIONED) {
686             builder.setStsConfig(j.getInt("stsConfig"));
687             JSONArray jSessionKeyArray = j.getJSONArray("sessionKey");
688             builder.setSessionKey(convertJSONArrayToByteArray(jSessionKeyArray));
689         } else if (j.getInt(
690                 "stsConfig") == FiraParams.STS_CONFIG_PROVISIONED_FOR_CONTROLEE_INDIVIDUAL_KEY) {
691             builder.setStsConfig(j.getInt("stsConfig"));
692             JSONArray jSessionKeyArray = j.getJSONArray("sessionKey");
693             builder.setSessionKey(convertJSONArrayToByteArray(jSessionKeyArray));
694             if (j.getInt("deviceType") == FiraParams.RANGING_DEVICE_TYPE_CONTROLEE) {
695                 JSONArray jSubSessionKeyArray = j.getJSONArray("subSessionKey");
696                 builder.setSubsessionKey(convertJSONArrayToByteArray(jSubSessionKeyArray));
697                 builder.setSubSessionId(j.getInt("subSessionId"));
698             }
699         }
700         if (j.has("aoaResultRequest")) {
701             builder.setAoaResultRequest(j.getInt("aoaResultRequest"));
702         }
703         if (j.has("filterType")) {
704             builder.setFilterType(j.getInt("filterType"));
705         }
706 
707         return builder.build();
708     }
709 
getRangingMeasurement(String key, JSONArray jArray)710     private RangingMeasurement getRangingMeasurement(String key, JSONArray jArray)
711             throws JSONException {
712         byte[] bArray = convertJSONArrayToByteArray(jArray);
713         UwbAddress peerAddress = getComputedMacAddress(UwbAddress.fromBytes(bArray));
714         RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
715         List<RangingMeasurement> rangingMeasurements =
716                 rangingSessionCallback.rangingReport.getMeasurements();
717         for (RangingMeasurement r: rangingMeasurements) {
718             if (r.getStatus() == RangingMeasurement.RANGING_STATUS_SUCCESS
719                     && r.getRemoteDeviceAddress().equals(peerAddress)) {
720                 Log.d(TAG + "Found peer " + peerAddress.toString());
721                 return r;
722             }
723         }
724         Log.w(TAG + "Invalid ranging status or peer not found.");
725         return null;
726     }
727 
728     /** Open FIRA UWB ranging session. */
729     @AsyncRpc(description = "Open FIRA UWB ranging session")
openFiraRangingSession(String callbackId, String key, JSONObject config)730     public void openFiraRangingSession(String callbackId, String key, JSONObject config)
731             throws JSONException {
732         RangingSessionCallback rangingSessionCallback = new RangingSessionCallback(
733                 callbackId, Event.EventAll.getType());
734         FiraOpenSessionParams params = generateFiraOpenSessionParams(config);
735         mUwbManager.openRangingSession(params.toBundle(), mExecutor, rangingSessionCallback);
736         sRangingSessionCallbackMap.put(key, rangingSessionCallback);
737     }
738 
739     /** Open CCC UWB ranging session. */
740     @AsyncRpc(description = "Open CCC UWB ranging session")
openCccRangingSession(String callbackId, String key, JSONObject config)741     public void openCccRangingSession(String callbackId, String key, JSONObject config)
742             throws JSONException {
743         RangingSessionCallback rangingSessionCallback = new RangingSessionCallback(
744                 callbackId, Event.EventAll.getType());
745         CccOpenRangingParams params = generateCccOpenRangingParams(config);
746         mUwbManager.openRangingSession(params.toBundle(), mExecutor, rangingSessionCallback);
747         sRangingSessionCallbackMap.put(key, rangingSessionCallback);
748     }
749 
750     /** Start FIRA UWB ranging. */
751     @Rpc(description = "Start FIRA UWB ranging")
startFiraRangingSession(String key)752     public void startFiraRangingSession(String key) {
753         RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
754         rangingSessionCallback.rangingSession.start(new PersistableBundle());
755     }
756 
757     /** Start CCC UWB ranging. */
758     @Rpc(description = "Start CCC UWB ranging")
startCccRangingSession(String key, JSONObject config)759     public void startCccRangingSession(String key, JSONObject config) throws JSONException {
760         RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
761         CccRangingStartedParams params = generateCccRangingStartedParams(config);
762         rangingSessionCallback.rangingSession.start(params.toBundle());
763     }
764 
765     /** Reconfigures FIRA UWB ranging session. */
766     @Rpc(description = "Reconfigure FIRA UWB ranging session")
reconfigureFiraRangingSession(String key, JSONObject config)767     public void reconfigureFiraRangingSession(String key, JSONObject config) throws JSONException {
768         RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
769         FiraRangingReconfigureParams params = generateFiraRangingReconfigureParams(config);
770         rangingSessionCallback.rangingSession.reconfigure(params.toBundle());
771     }
772 
773     /** Reconfigures FIRA UWB ranging session to add controlee. */
774     @Rpc(description = "Reconfigure FIRA UWB ranging session to add controlee")
addControleeFiraRangingSession(String key, JSONObject config)775     public void addControleeFiraRangingSession(String key, JSONObject config) throws JSONException {
776         RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
777         FiraControleeParams params = generateFiraControleeParams(config);
778         rangingSessionCallback.rangingSession.addControlee(params.toBundle());
779     }
780 
781     /** Reconfigures FIRA UWB ranging session to remove controlee. */
782     @Rpc(description = "Reconfigure FIRA UWB ranging session to remove controlee")
removeControleeFiraRangingSession(String key, JSONObject config)783     public void removeControleeFiraRangingSession(String key, JSONObject config)
784             throws JSONException {
785         RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
786         FiraControleeParams params = generateFiraControleeParams(config);
787         rangingSessionCallback.rangingSession.removeControlee(params.toBundle());
788     }
789 
790     /**
791      * Find if UWB peer is found.
792      */
793     @Rpc(description = "Find if UWB peer is found")
isUwbPeerFound(String key, JSONArray jArray)794     public boolean isUwbPeerFound(String key, JSONArray jArray) throws JSONException {
795         return getRangingMeasurement(key, jArray) != null;
796     }
797 
798     /** Get UWB distance measurement. */
799     @Rpc(description = "Get UWB ranging distance measurement with peer.")
getDistanceMeasurement(String key, JSONArray jArray)800     public double getDistanceMeasurement(String key, JSONArray jArray) throws JSONException {
801         RangingMeasurement rangingMeasurement = getRangingMeasurement(key, jArray);
802         if (rangingMeasurement == null || rangingMeasurement.getDistanceMeasurement() == null) {
803             throw new NullPointerException("Cannot get Distance Measurement on null object.");
804         }
805         return rangingMeasurement.getDistanceMeasurement().getMeters();
806     }
807 
808     /** Get angle of arrival azimuth measurement. */
809     @Rpc(description = "Get UWB AoA Azimuth measurement.")
getAoAAzimuthMeasurement(String key, JSONArray jArray)810     public double getAoAAzimuthMeasurement(String key, JSONArray jArray) throws JSONException {
811         RangingMeasurement rangingMeasurement = getRangingMeasurement(key, jArray);
812         if (rangingMeasurement == null
813                 || rangingMeasurement.getAngleOfArrivalMeasurement() == null
814                 || rangingMeasurement.getAngleOfArrivalMeasurement().getAzimuth() == null) {
815             throw new NullPointerException("Cannot get AoA azimuth measurement on null object.");
816         }
817         return rangingMeasurement.getAngleOfArrivalMeasurement().getAzimuth().getRadians();
818     }
819 
820     /** Get angle of arrival altitude measurement. */
821     @Rpc(description = "Get UWB AoA Altitude measurement.")
getAoAAltitudeMeasurement(String key, JSONArray jArray)822     public double getAoAAltitudeMeasurement(String key, JSONArray jArray) throws JSONException {
823         RangingMeasurement rangingMeasurement = getRangingMeasurement(key, jArray);
824         if (rangingMeasurement == null
825                 || rangingMeasurement.getAngleOfArrivalMeasurement() == null
826                 || rangingMeasurement.getAngleOfArrivalMeasurement().getAltitude() == null) {
827             throw new NullPointerException("Cannot get AoA altitude measurement on null object.");
828         }
829         return rangingMeasurement.getAngleOfArrivalMeasurement().getAltitude().getRadians();
830     }
831 
832     /** Get RSSI measurement. */
833     @Rpc(description = "Get RSSI measurement.")
getRssiDbmMeasurement(String key, JSONArray jArray)834     public int getRssiDbmMeasurement(String key, JSONArray jArray) throws JSONException {
835         RangingMeasurement rangingMeasurement = getRangingMeasurement(key, jArray);
836         if (rangingMeasurement == null) {
837             throw new NullPointerException("Cannot get RSSI dBm measurement on null object.");
838         }
839         return rangingMeasurement.getRssiDbm();
840     }
841 
842     /** Stop UWB ranging. */
843     @Rpc(description = "Stop UWB ranging")
stopRangingSession(String key)844     public void stopRangingSession(String key) {
845         RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
846         rangingSessionCallback.rangingSession.stop();
847     }
848 
849     /** Close UWB ranging session. */
850     @Rpc(description = "Close UWB ranging session")
closeRangingSession(String key)851     public void closeRangingSession(String key) {
852         RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.remove(key);
853         if (rangingSessionCallback != null && rangingSessionCallback.rangingSession != null) {
854             rangingSessionCallback.rangingSession.close();
855         }
856     }
857 
convertPersistableBundleToJson(PersistableBundle bundle)858     private JSONObject convertPersistableBundleToJson(PersistableBundle bundle)
859             throws JSONException {
860         JSONObject jsonObj = new JSONObject();
861         Set<String> keys = bundle.keySet();
862         for (String key: keys) {
863             if (bundle.get(key) instanceof PersistableBundle) {
864                 jsonObj.put(key, convertPersistableBundleToJson(
865                         (PersistableBundle) bundle.get(key)));
866             } else {
867                 jsonObj.put(key, JSONObject.wrap(bundle.get(key)));
868             }
869         }
870         return jsonObj;
871     }
872 
873     /** Get UWB specification info */
874     @Rpc(description = "Get Uwb specification info")
getSpecificationInfo()875     public JSONObject getSpecificationInfo() throws JSONException {
876         return convertPersistableBundleToJson(mUwbManager.getSpecificationInfo());
877     }
878 
879     /** Set airplane mode to True or False */
880     @Rpc(description = "Set airplane mode")
setAirplaneMode(Boolean enabled)881     public void setAirplaneMode(Boolean enabled) {
882         mConnectivityManager.setAirplaneMode(enabled);
883     }
884 
885     @Rpc(description = "Log info level message to device logcat")
logInfo(String message)886     public void logInfo(String message) throws JSONException {
887         Log.i(TAG + message);
888     }
889 
890     @Override
shutdown()891     public void shutdown() {}
892 
adoptShellPermission()893     private void adoptShellPermission() throws Throwable {
894         UiAutomation uia = InstrumentationRegistry.getInstrumentation().getUiAutomation();
895         uia.adoptShellPermissionIdentity();
896         try {
897             Class<?> cls = Class.forName("android.app.UiAutomation");
898             Method destroyMethod = cls.getDeclaredMethod("destroy");
899             destroyMethod.invoke(uia);
900         } catch (ReflectiveOperationException e) {
901             throw new UwbManagerSnippetException("Failed to cleaup Ui Automation", e);
902         }
903     }
904 
getReverseBytes(byte[] data)905     private static byte[] getReverseBytes(byte[] data) {
906         byte[] buffer = new byte[data.length];
907         for (int i = 0; i < data.length; i++) {
908             buffer[i] = data[data.length - 1 - i];
909         }
910         return buffer;
911     }
getComputedMacAddress(UwbAddress address)912     private static UwbAddress getComputedMacAddress(UwbAddress address) {
913         if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
914             return UwbAddress.fromBytes(getReverseBytes(address.toBytes()));
915         }
916         return address;
917     }
918 
getComputedVendorId(byte[] data)919     private static byte[] getComputedVendorId(byte[] data) {
920         if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
921             return getReverseBytes(data);
922         }
923         return data;
924     }
925 }
926