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