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 import java.util.function.Supplier; 59 60 /** Snippet class exposing Android APIs for Uwb. */ 61 public class UwbManagerSnippet implements Snippet { 62 private static class UwbManagerSnippetException extends Exception { 63 UwbManagerSnippetException(String msg)64 UwbManagerSnippetException(String msg) { 65 super(msg); 66 } 67 UwbManagerSnippetException(String msg, Throwable err)68 UwbManagerSnippetException(String msg, Throwable err) { 69 super(msg, err); 70 } 71 } 72 73 private static final String TAG = "UwbManagerSnippet: "; 74 private final UwbManager mUwbManager; 75 private final ConnectivityManager mConnectivityManager; 76 private final Context mContext; 77 private final Executor mExecutor = Executors.newSingleThreadExecutor(); 78 private final EventCache mEventCache = EventCache.getInstance(); 79 private static HashMap<String, RangingSessionCallback> sRangingSessionCallbackMap = 80 new HashMap<String, RangingSessionCallback>(); 81 private static HashMap<String, UwbAdapterStateCallback> sUwbAdapterStateCallbackMap = 82 new HashMap<String, UwbAdapterStateCallback>(); 83 UwbManagerSnippet()84 public UwbManagerSnippet() { 85 mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 86 mUwbManager = mContext.getSystemService(UwbManager.class); 87 mConnectivityManager = mContext.getSystemService(ConnectivityManager.class); 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 handleEvent(Event e, int reason)205 private void handleEvent(Event e, int reason) { 206 Log.d(TAG + "RangingSessionCallback#handleEvent() for " + e.toString()); 207 SnippetEvent event = new SnippetEvent(mId, "RangingSessionCallback"); 208 event.getData().putString("rangingSessionEvent", e.toString()); 209 event.getData().putInt("reasonCode", reason); 210 mEventCache.postEvent(event); 211 } 212 213 @Override onOpened(RangingSession session)214 public void onOpened(RangingSession session) { 215 Log.d(TAG + "RangingSessionCallback#onOpened() called"); 216 rangingSession = session; 217 handleEvent(Event.Opened); 218 } 219 220 @Override onOpenFailed(int reason, PersistableBundle params)221 public void onOpenFailed(int reason, PersistableBundle params) { 222 Log.d(TAG + "RangingSessionCallback#onOpenedFailed() called"); 223 Log.d(TAG + "OpenFailed reason " + String.valueOf(reason)); 224 persistableBundle = params; 225 handleEvent(Event.OpenFailed, reason); 226 } 227 228 @Override onStarted(PersistableBundle info)229 public void onStarted(PersistableBundle info) { 230 Log.d(TAG + "RangingSessionCallback#onStarted() called"); 231 sessionInfo = info; 232 handleEvent(Event.Started); 233 } 234 235 @Override onStartFailed(int reason, PersistableBundle params)236 public void onStartFailed(int reason, PersistableBundle params) { 237 Log.d(TAG + "RangingSessionCallback#onStartFailed() called"); 238 Log.d(TAG + "StartFailed reason " + String.valueOf(reason)); 239 persistableBundle = params; 240 handleEvent(Event.StartFailed, reason); 241 } 242 243 @Override onReconfigured(PersistableBundle params)244 public void onReconfigured(PersistableBundle params) { 245 Log.d(TAG + "RangingSessionCallback#oniReconfigured() called"); 246 persistableBundle = params; 247 handleEvent(Event.Reconfigured); 248 } 249 250 @Override onReconfigureFailed(int reason, PersistableBundle params)251 public void onReconfigureFailed(int reason, PersistableBundle params) { 252 Log.d(TAG + "RangingSessionCallback#onReconfigureFailed() called"); 253 Log.d(TAG + "ReconfigureFailed reason " + String.valueOf(reason)); 254 persistableBundle = params; 255 handleEvent(Event.ReconfigureFailed, reason); 256 } 257 258 @Override onStopped(int reason, PersistableBundle params)259 public void onStopped(int reason, PersistableBundle params) { 260 Log.d(TAG + "RangingSessionCallback#onStopped() called"); 261 Log.d(TAG + "Stopped reason " + String.valueOf(reason)); 262 persistableBundle = params; 263 handleEvent(Event.Stopped, reason); 264 } 265 266 @Override onStopFailed(int reason, PersistableBundle params)267 public void onStopFailed(int reason, PersistableBundle params) { 268 Log.d(TAG + "RangingSessionCallback#onStopFailed() called"); 269 Log.d(TAG + "StopFailed reason " + String.valueOf(reason)); 270 persistableBundle = params; 271 handleEvent(Event.StopFailed, reason); 272 } 273 274 @Override onClosed(int reason, PersistableBundle params)275 public void onClosed(int reason, PersistableBundle params) { 276 Log.d(TAG + "RangingSessionCallback#onClosed() called"); 277 Log.d(TAG + "Closed reason " + String.valueOf(reason)); 278 persistableBundle = params; 279 handleEvent(Event.Closed, reason); 280 } 281 282 @Override onReportReceived(RangingReport report)283 public void onReportReceived(RangingReport report) { 284 Log.d(TAG + "RangingSessionCallback#onReportReceived() called"); 285 rangingReport = report; 286 handleEvent(Event.ReportReceived); 287 } 288 289 @Override onControleeAdded(PersistableBundle params)290 public void onControleeAdded(PersistableBundle params) { 291 Log.d(TAG + "RangingSessionCallback#onControleeAdded() called"); 292 persistableBundle = params; 293 handleEvent(Event.ControleeAdded); 294 295 } 296 297 @Override onControleeAddFailed( int reason, PersistableBundle params)298 public void onControleeAddFailed( 299 int reason, PersistableBundle params) { 300 Log.d(TAG + "RangingSessionCallback#onControleeAddFailed() called"); 301 Log.d(TAG + "ControleeAddFailed reason " + String.valueOf(reason)); 302 persistableBundle = params; 303 handleEvent(Event.ControleeAddFailed, reason); 304 305 } 306 307 @Override onControleeRemoved(PersistableBundle params)308 public void onControleeRemoved(PersistableBundle params) { 309 Log.d(TAG + "RangingSessionCallback#onControleeRemoved() called"); 310 persistableBundle = params; 311 handleEvent(Event.ControleeRemoved); 312 } 313 314 @Override onControleeRemoveFailed( int reason, PersistableBundle params)315 public void onControleeRemoveFailed( 316 int reason, PersistableBundle params) { 317 Log.d(TAG + "RangingSessionCallback#onControleeRemoveFailed() called"); 318 Log.d(TAG + "ControleeRemoveFailed reason " + String.valueOf(reason)); 319 persistableBundle = params; 320 handleEvent(Event.ControleeRemoveFailed, reason); 321 } 322 323 @Override onPaused(PersistableBundle params)324 public void onPaused(PersistableBundle params) { 325 Log.d(TAG + "RangingSessionCallback#onPaused() called"); 326 persistableBundle = params; 327 handleEvent(Event.Paused); 328 } 329 330 @Override onPauseFailed(int reason, PersistableBundle params)331 public void onPauseFailed(int reason, PersistableBundle params) { 332 Log.d(TAG + "RangingSessionCallback#onPauseFailed() called"); 333 Log.d(TAG + "PauseFailed reason " + String.valueOf(reason)); 334 persistableBundle = params; 335 handleEvent(Event.PauseFailed, reason); 336 } 337 338 @Override onResumed(PersistableBundle params)339 public void onResumed(PersistableBundle params) { 340 Log.d(TAG + "RangingSessionCallback#onResumed() called"); 341 persistableBundle = params; 342 handleEvent(Event.Resumed); 343 } 344 345 @Override onResumeFailed(int reason, PersistableBundle params)346 public void onResumeFailed(int reason, PersistableBundle params) { 347 Log.d(TAG + "RangingSessionCallback#onResumeFailed() called"); 348 Log.d(TAG + "ResumeFailed reason " + String.valueOf(reason)); 349 persistableBundle = params; 350 handleEvent(Event.ResumeFailed, reason); 351 } 352 353 @Override onDataSent(UwbAddress remoteDeviceAddress, PersistableBundle params)354 public void onDataSent(UwbAddress remoteDeviceAddress, 355 PersistableBundle params) { 356 Log.d(TAG + "RangingSessionCallback#onDataSent() called"); 357 uwbAddress = getComputedMacAddress(remoteDeviceAddress); 358 persistableBundle = params; 359 handleEvent(Event.DataSent); 360 } 361 362 @Override onDataSendFailed(UwbAddress remoteDeviceAddress, int reason, PersistableBundle params)363 public void onDataSendFailed(UwbAddress remoteDeviceAddress, 364 int reason, PersistableBundle params) { 365 Log.d(TAG + "RangingSessionCallback#onDataSendFailed() called"); 366 Log.d(TAG + "DataSendFailed reason " + String.valueOf(reason)); 367 uwbAddress = getComputedMacAddress(remoteDeviceAddress); 368 persistableBundle = params; 369 handleEvent(Event.DataSendFailed, reason); 370 } 371 372 @Override onDataReceived(UwbAddress remoteDeviceAddress, PersistableBundle params, byte[] data)373 public void onDataReceived(UwbAddress remoteDeviceAddress, 374 PersistableBundle params, byte[] data) { 375 Log.d(TAG + "RangingSessionCallback#onDataReceived() called"); 376 uwbAddress = getComputedMacAddress(remoteDeviceAddress); 377 dataReceived = data; 378 persistableBundle = params; 379 handleEvent(Event.DataReceived); 380 } 381 382 @Override onDataReceiveFailed(UwbAddress remoteDeviceAddress, int reason, PersistableBundle params)383 public void onDataReceiveFailed(UwbAddress remoteDeviceAddress, 384 int reason, PersistableBundle params) { 385 Log.d(TAG + "RangingSessionCallback#onDataReceiveFailed() called"); 386 Log.d(TAG + "DataReceiveFailed reason " + String.valueOf(reason)); 387 uwbAddress = getComputedMacAddress(remoteDeviceAddress); 388 persistableBundle = params; 389 handleEvent(Event.DataReceiveFailed, reason); 390 } 391 392 @Override onServiceDiscovered(PersistableBundle params)393 public void onServiceDiscovered(PersistableBundle params) { 394 Log.d(TAG + "RangingSessionCallback#onServiceDiscovered() called"); 395 persistableBundle = params; 396 handleEvent(Event.ServiceDiscovered); 397 } 398 399 @Override onServiceConnected(PersistableBundle params)400 public void onServiceConnected(PersistableBundle params) { 401 Log.d(TAG + "RangingSessionCallback#onServiceConnected() called"); 402 persistableBundle = params; 403 handleEvent(Event.ServiceConnected); 404 } 405 406 // TODO: This is only available in Android U SDK. So, expose it there only. onRangingRoundsUpdateDtTagStatus(PersistableBundle params)407 public void onRangingRoundsUpdateDtTagStatus(PersistableBundle params) { 408 Log.d(TAG + "RangingSessionCallback#onRangingRoundsUpdateDtTagStatus() called"); 409 persistableBundle = params; 410 handleEvent(Event.RangingRoundsUpdateDtTagStatus); 411 } 412 } 413 414 /** Register uwb adapter state callback. */ 415 @AsyncRpc(description = "Register uwb adapter state callback") registerUwbAdapterStateCallback(String callbackId, String key)416 public void registerUwbAdapterStateCallback(String callbackId, String key) throws Throwable { 417 UwbAdapterStateCallback uwbAdapterStateCallback = new UwbAdapterStateCallback(callbackId); 418 sUwbAdapterStateCallbackMap.put(key, uwbAdapterStateCallback); 419 runWithShellPermission(() -> 420 mUwbManager.registerAdapterStateCallback(mExecutor, uwbAdapterStateCallback)); 421 } 422 423 /** Unregister uwb adapter state callback. */ 424 @Rpc(description = "Unregister uwb adapter state callback.") unregisterUwbAdapterStateCallback(String key)425 public void unregisterUwbAdapterStateCallback(String key) throws Throwable { 426 UwbAdapterStateCallback uwbAdapterStateCallback = sUwbAdapterStateCallbackMap.get(key); 427 runWithShellPermission(() -> 428 mUwbManager.unregisterAdapterStateCallback(uwbAdapterStateCallback)); 429 sUwbAdapterStateCallbackMap.remove(key); 430 } 431 432 /** Get UWB adapter state. */ 433 @Rpc(description = "Get Uwb adapter state") getAdapterState()434 public int getAdapterState() throws Throwable { 435 return runWithShellPermission(() -> mUwbManager.getAdapterState()); 436 } 437 438 /** Get the UWB state. */ 439 @Rpc(description = "Get Uwb state") isUwbEnabled()440 public boolean isUwbEnabled() throws Throwable { 441 return runWithShellPermission(() -> mUwbManager.isUwbEnabled()); 442 } 443 444 /** Set the UWB state. */ 445 @Rpc(description = "Set Uwb state") setUwbEnabled(boolean enabled)446 public void setUwbEnabled(boolean enabled) throws Throwable { 447 runWithShellPermission(() -> mUwbManager.setUwbEnabled(enabled)); 448 } 449 450 /** Get the UWB hardware state. */ 451 @Rpc(description = "Get Uwb hardware state") isUwbHwEnableRequested()452 public boolean isUwbHwEnableRequested() throws Throwable { 453 return runWithShellPermission(() -> mUwbManager.isUwbHwEnableRequested()); 454 } 455 456 /** Set the UWB hardware state. */ 457 @Rpc(description = "Set Uwb hardware state") requestUwbHwEnabled(boolean enabled)458 public void requestUwbHwEnabled(boolean enabled) throws Throwable { 459 runWithShellPermission(() -> mUwbManager.requestUwbHwEnabled(enabled)); 460 } 461 462 /** Get UWB HW idle feature state. */ 463 @Rpc(description = "Get Uwb hardware idle feature state") isUwbHwIdleTurnOffEnabled()464 public boolean isUwbHwIdleTurnOffEnabled() throws Throwable { 465 return runWithShellPermission(() -> mUwbManager.isUwbHwIdleTurnOffEnabled()); 466 } 467 convertJSONArrayToByteArray(JSONArray jArray)468 private byte[] convertJSONArrayToByteArray(JSONArray jArray) throws JSONException { 469 if (jArray == null) { 470 return null; 471 } 472 byte[] bArray = new byte[jArray.length()]; 473 for (int i = 0; i < jArray.length(); i++) { 474 bArray[i] = (byte) jArray.getInt(i); 475 } 476 return bArray; 477 } 478 generateFiraRangingReconfigureParams(JSONObject j)479 private FiraRangingReconfigureParams generateFiraRangingReconfigureParams(JSONObject j) 480 throws JSONException { 481 if (j == null) { 482 return null; 483 } 484 FiraRangingReconfigureParams.Builder builder = new FiraRangingReconfigureParams.Builder(); 485 if (j.has("action")) { 486 builder.setAction(j.getInt("action")); 487 } 488 if (j.has("addressList")) { 489 JSONArray jArray = j.getJSONArray("addressList"); 490 UwbAddress[] addressList = new UwbAddress[jArray.length()]; 491 for (int i = 0; i < jArray.length(); i++) { 492 addressList[i] = getComputedMacAddress(UwbAddress.fromBytes( 493 convertJSONArrayToByteArray(jArray.getJSONArray(i)))); 494 } 495 builder.setAddressList(addressList); 496 } 497 if (j.has("subSessionIdList")) { 498 JSONArray jArray = j.getJSONArray("subSessionIdList"); 499 int[] subSessionIdList = new int[jArray.length()]; 500 for (int i = 0; i < jArray.length(); i++) { 501 subSessionIdList[i] = jArray.getInt(i); 502 } 503 builder.setSubSessionIdList(subSessionIdList); 504 } 505 if (j.has("subSessionKeyList")) { 506 JSONArray jSubSessionKeyListArray = j.getJSONArray("subSessionKeyList"); 507 builder.setSubSessionKeyList(convertJSONArrayToByteArray(jSubSessionKeyListArray)); 508 } 509 if (j.has("blockStrideLength")) { 510 builder.setBlockStrideLength(j.getInt("blockStrideLength")); 511 } 512 return builder.build(); 513 } 514 generateFiraControleeParams(JSONObject j)515 private FiraControleeParams generateFiraControleeParams(JSONObject j) throws JSONException { 516 if (j == null) { 517 return null; 518 } 519 FiraControleeParams.Builder builder = new FiraControleeParams.Builder(); 520 if (j.has("action")) { 521 builder.setAction(j.getInt("action")); 522 } 523 if (j.has("addressList")) { 524 JSONArray jArray = j.getJSONArray("addressList"); 525 UwbAddress[] addressList = new UwbAddress[jArray.length()]; 526 for (int i = 0; i < jArray.length(); i++) { 527 addressList[i] = getComputedMacAddress(UwbAddress.fromBytes( 528 convertJSONArrayToByteArray(jArray.getJSONArray(i)))); 529 } 530 builder.setAddressList(addressList); 531 } 532 if (j.has("subSessionIdList")) { 533 JSONArray jArray = j.getJSONArray("subSessionIdList"); 534 int[] subSessionIdList = new int[jArray.length()]; 535 for (int i = 0; i < jArray.length(); i++) { 536 subSessionIdList[i] = jArray.getInt(i); 537 } 538 builder.setSubSessionIdList(subSessionIdList); 539 } 540 if (j.has("subSessionKeyList")) { 541 JSONArray jSubSessionKeyListArray = j.getJSONArray("subSessionKeyList"); 542 builder.setSubSessionKeyList(convertJSONArrayToByteArray(jSubSessionKeyListArray)); 543 } 544 return builder.build(); 545 } 546 generateCccRangingStartedParams(JSONObject j)547 private CccRangingStartedParams generateCccRangingStartedParams(JSONObject j) 548 throws JSONException { 549 if (j == null) { 550 return null; 551 } 552 CccRangingStartedParams.Builder builder = new CccRangingStartedParams.Builder(); 553 if (j.has("stsIndex")) { 554 builder.setStartingStsIndex(j.getInt("stsIndex")); 555 } 556 if (j.has("uwbTime")) { 557 builder.setUwbTime0(j.getInt("uwbTime")); 558 } 559 if (j.has("hopModeKey")) { 560 builder.setHopModeKey(j.getInt("hopModeKey")); 561 } 562 if (j.has("syncCodeIndex")) { 563 builder.setSyncCodeIndex(j.getInt("syncCodeIndex")); 564 } 565 if (j.has("ranMultiplier")) { 566 builder.setRanMultiplier(j.getInt("ranMultiplier")); 567 } 568 569 return builder.build(); 570 } 571 generateCccOpenRangingParams(JSONObject j)572 private CccOpenRangingParams generateCccOpenRangingParams(JSONObject j) throws JSONException { 573 if (j == null) { 574 return null; 575 } 576 CccOpenRangingParams.Builder builder = new CccOpenRangingParams.Builder(); 577 builder.setProtocolVersion(CccParams.PROTOCOL_VERSION_1_0); 578 if (j.has("sessionId")) { 579 builder.setSessionId(j.getInt("sessionId")); 580 } 581 if (j.has("uwbConfig")) { 582 builder.setUwbConfig(j.getInt("uwbConfig")); 583 } 584 if (j.has("ranMultiplier")) { 585 builder.setRanMultiplier(j.getInt("ranMultiplier")); 586 } 587 if (j.has("channel")) { 588 builder.setChannel(j.getInt("channel")); 589 } 590 if (j.has("chapsPerSlot")) { 591 builder.setNumChapsPerSlot(j.getInt("chapsPerSlot")); 592 } 593 if (j.has("responderNodes")) { 594 builder.setNumResponderNodes(j.getInt("responderNodes")); 595 } 596 if (j.has("slotsPerRound")) { 597 builder.setNumSlotsPerRound(j.getInt("slotsPerRound")); 598 } 599 if (j.has("hoppingMode")) { 600 builder.setHoppingConfigMode(j.getInt("hoppingMode")); 601 } 602 if (j.has("hoppingSequence")) { 603 builder.setHoppingSequence(j.getInt("hoppingSequence")); 604 } 605 if (j.has("syncCodeIndex")) { 606 builder.setSyncCodeIndex(j.getInt("syncCodeIndex")); 607 } 608 if (j.has("pulseShapeCombo")) { 609 JSONObject pulseShapeCombo = j.getJSONObject("pulseShapeCombo"); 610 builder.setPulseShapeCombo(new CccPulseShapeCombo( 611 pulseShapeCombo.getInt("pulseShapeComboTx"), 612 pulseShapeCombo.getInt("pulseShapeComboRx"))); 613 } 614 615 return builder.build(); 616 } 617 generateFiraOpenSessionParams(JSONObject j)618 private FiraOpenSessionParams generateFiraOpenSessionParams(JSONObject j) throws JSONException { 619 if (j == null) { 620 return null; 621 } 622 FiraOpenSessionParams.Builder builder = new FiraOpenSessionParams.Builder(); 623 builder.setProtocolVersion(FiraParams.PROTOCOL_VERSION_1_1); 624 if (j.has("sessionId")) { 625 builder.setSessionId(j.getInt("sessionId")); 626 } 627 if (j.has("deviceType")) { 628 builder.setDeviceType(j.getInt("deviceType")); 629 } 630 if (j.has("deviceRole")) { 631 builder.setDeviceRole(j.getInt("deviceRole")); 632 } 633 if (j.has("rangingRoundUsage")) { 634 builder.setRangingRoundUsage(j.getInt("rangingRoundUsage")); 635 } 636 if (j.has("multiNodeMode")) { 637 builder.setMultiNodeMode(j.getInt("multiNodeMode")); 638 } 639 if (j.has("deviceAddress")) { 640 JSONArray jArray = j.getJSONArray("deviceAddress"); 641 byte[] bArray = convertJSONArrayToByteArray(jArray); 642 UwbAddress deviceAddress = getComputedMacAddress(UwbAddress.fromBytes(bArray)); 643 builder.setDeviceAddress(deviceAddress); 644 } 645 if (j.has("destinationAddresses")) { 646 JSONArray jArray = j.getJSONArray("destinationAddresses"); 647 UwbAddress[] destinationUwbAddresses = new UwbAddress[jArray.length()]; 648 for (int i = 0; i < jArray.length(); i++) { 649 destinationUwbAddresses[i] = getComputedMacAddress(UwbAddress.fromBytes( 650 convertJSONArrayToByteArray(jArray.getJSONArray(i)))); 651 } 652 builder.setDestAddressList(Arrays.asList(destinationUwbAddresses)); 653 } 654 if (j.has("initiationTimeMs")) { 655 builder.setInitiationTime(j.getInt("initiationTimeMs")); 656 } 657 if (j.has("slotDurationRstu")) { 658 builder.setSlotDurationRstu(j.getInt("slotDurationRstu")); 659 } 660 if (j.has("slotsPerRangingRound")) { 661 builder.setSlotsPerRangingRound(j.getInt("slotsPerRangingRound")); 662 } 663 if (j.has("rangingIntervalMs")) { 664 builder.setRangingIntervalMs(j.getInt("rangingIntervalMs")); 665 } 666 if (j.has("blockStrideLength")) { 667 builder.setBlockStrideLength(j.getInt("blockStrideLength")); 668 } 669 if (j.has("hoppingMode")) { 670 builder.setHoppingMode(j.getInt("hoppingMode")); 671 } 672 if (j.has("maxRangingRoundRetries")) { 673 builder.setMaxRangingRoundRetries(j.getInt("maxRangingRoundRetries")); 674 } 675 if (j.has("maxNumberOfMeasurements")) { 676 builder.setMaxNumberOfMeasurements(j.getInt("maxNumberOfMeasurements")); 677 } 678 if (j.has("sessionPriority")) { 679 builder.setSessionPriority(j.getInt("sessionPriority")); 680 } 681 if (j.has("macAddressMode")) { 682 builder.setMacAddressMode(j.getInt("macAddressMode")); 683 } 684 if (j.has("inBandTerminationAttemptCount")) { 685 builder.setInBandTerminationAttemptCount(j.getInt("inBandTerminationAttemptCount")); 686 } 687 if (j.has("channel")) { 688 builder.setChannelNumber(j.getInt("channel")); 689 } 690 if (j.has("preamble")) { 691 builder.setPreambleCodeIndex(j.getInt("preamble")); 692 } 693 if (j.getInt("stsConfig") == FiraParams.STS_CONFIG_STATIC) { 694 JSONArray jVendorIdArray = j.getJSONArray("vendorId"); 695 builder.setVendorId(getComputedVendorId(convertJSONArrayToByteArray(jVendorIdArray))); 696 JSONArray jStatisStsIVArray = j.getJSONArray("staticStsIV"); 697 builder.setStaticStsIV(convertJSONArrayToByteArray(jStatisStsIVArray)); 698 } else if (j.getInt("stsConfig") == FiraParams.STS_CONFIG_PROVISIONED) { 699 builder.setStsConfig(j.getInt("stsConfig")); 700 JSONArray jSessionKeyArray = j.getJSONArray("sessionKey"); 701 builder.setSessionKey(convertJSONArrayToByteArray(jSessionKeyArray)); 702 } else if (j.getInt( 703 "stsConfig") == FiraParams.STS_CONFIG_PROVISIONED_FOR_CONTROLEE_INDIVIDUAL_KEY) { 704 builder.setStsConfig(j.getInt("stsConfig")); 705 JSONArray jSessionKeyArray = j.getJSONArray("sessionKey"); 706 builder.setSessionKey(convertJSONArrayToByteArray(jSessionKeyArray)); 707 if (j.getInt("deviceType") == FiraParams.RANGING_DEVICE_TYPE_CONTROLEE) { 708 JSONArray jSubSessionKeyArray = j.getJSONArray("subSessionKey"); 709 builder.setSubsessionKey(convertJSONArrayToByteArray(jSubSessionKeyArray)); 710 builder.setSubSessionId(j.getInt("subSessionId")); 711 } 712 } 713 if (j.has("aoaResultRequest")) { 714 builder.setAoaResultRequest(j.getInt("aoaResultRequest")); 715 } 716 if (j.has("filterType")) { 717 builder.setFilterType(j.getInt("filterType")); 718 } 719 if (j.has("rangeDataNtfConfig")) { 720 builder.setRangeDataNtfConfig(j.getInt("rangeDataNtfConfig")); 721 } 722 if (j.has("errorStreakTimeoutInMs")) { 723 builder.setRangingErrorStreakTimeoutMs(j.getInt("errorStreakTimeoutInMs")); 724 } 725 if (j.has("hasRangingResultReportMessage")) { 726 builder.setHasRangingResultReportMessage(j.getBoolean("hasRangingResultReportMessage")); 727 } 728 if (j.has("rangingErrorStreakTimeoutMs")) { 729 builder.setRangingErrorStreakTimeoutMs(j.getLong("rangingErrorStreakTimeoutMs")); 730 } 731 if (j.has("isKeyRotationEnabled")) { 732 builder.setIsKeyRotationEnabled(j.getBoolean("isKeyRotationEnabled")); 733 } 734 if (j.has("keyRotationRate")) { 735 builder.setKeyRotationRate(j.getInt("keyRotationRate")); 736 } 737 if (j.has("hasRangingResultReportMessage")) { 738 builder.setHasRangingResultReportMessage(j.getBoolean("hasRangingResultReportMessage")); 739 } 740 if (j.has("prfMode")) { 741 builder.setPrfMode(j.getInt("prfMode")); 742 } 743 744 return builder.build(); 745 } 746 getRangingMeasurement(String key, JSONArray jArray)747 private RangingMeasurement getRangingMeasurement(String key, JSONArray jArray) 748 throws JSONException { 749 byte[] bArray = convertJSONArrayToByteArray(jArray); 750 UwbAddress peerAddress = getComputedMacAddress(UwbAddress.fromBytes(bArray)); 751 RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key); 752 List<RangingMeasurement> rangingMeasurements = 753 rangingSessionCallback.rangingReport.getMeasurements(); 754 for (RangingMeasurement r: rangingMeasurements) { 755 if (r.getStatus() == RangingMeasurement.RANGING_STATUS_SUCCESS 756 && r.getRemoteDeviceAddress().equals(peerAddress)) { 757 Log.d(TAG + "Found peer " + peerAddress.toString()); 758 return r; 759 } 760 } 761 Log.w(TAG + "Invalid ranging status or peer not found."); 762 return null; 763 } 764 765 /** Open FIRA UWB ranging session. */ 766 @AsyncRpc(description = "Open FIRA UWB ranging session") openFiraRangingSession(String callbackId, String key, JSONObject config)767 public void openFiraRangingSession(String callbackId, String key, JSONObject config) 768 throws Throwable { 769 RangingSessionCallback rangingSessionCallback = new RangingSessionCallback( 770 callbackId, Event.EventAll.getType()); 771 FiraOpenSessionParams params = generateFiraOpenSessionParams(config); 772 runWithShellPermission(() -> 773 mUwbManager.openRangingSession( 774 params.toBundle(), mExecutor, rangingSessionCallback)); 775 sRangingSessionCallbackMap.put(key, rangingSessionCallback); 776 } 777 778 /** Open CCC UWB ranging session. */ 779 @AsyncRpc(description = "Open CCC UWB ranging session") openCccRangingSession(String callbackId, String key, JSONObject config)780 public void openCccRangingSession(String callbackId, String key, JSONObject config) 781 throws Throwable { 782 RangingSessionCallback rangingSessionCallback = new RangingSessionCallback( 783 callbackId, Event.EventAll.getType()); 784 CccOpenRangingParams params = generateCccOpenRangingParams(config); 785 runWithShellPermission(() -> 786 mUwbManager.openRangingSession( 787 params.toBundle(), mExecutor, rangingSessionCallback)); 788 sRangingSessionCallbackMap.put(key, rangingSessionCallback); 789 } 790 791 /** Start FIRA UWB ranging. */ 792 @Rpc(description = "Start FIRA UWB ranging") startFiraRangingSession(String key)793 public void startFiraRangingSession(String key) throws Throwable { 794 RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key); 795 // Hold on to the shell permission until the session is stopped to get the ranging reports. 796 adoptShellPermission(); 797 rangingSessionCallback.rangingSession.start(new PersistableBundle()); 798 } 799 800 /** Start CCC UWB ranging. */ 801 @Rpc(description = "Start CCC UWB ranging") startCccRangingSession(String key, JSONObject config)802 public void startCccRangingSession(String key, JSONObject config) throws Throwable { 803 RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key); 804 CccRangingStartedParams params = generateCccRangingStartedParams(config); 805 // Hold on to the shell permission until the session is stopped to get the ranging reports. 806 adoptShellPermission(); 807 rangingSessionCallback.rangingSession.start(params.toBundle()); 808 } 809 810 /** Reconfigures FIRA UWB ranging session. */ 811 @Rpc(description = "Reconfigure FIRA UWB ranging session") reconfigureFiraRangingSession(String key, JSONObject config)812 public void reconfigureFiraRangingSession(String key, JSONObject config) throws Throwable { 813 RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key); 814 FiraRangingReconfigureParams params = generateFiraRangingReconfigureParams(config); 815 // Hold on to the shell permission until the session is stopped to get the ranging reports. 816 adoptShellPermission(); 817 rangingSessionCallback.rangingSession.reconfigure(params.toBundle()); 818 } 819 820 /** Reconfigures FIRA UWB ranging session to add controlee. */ 821 @Rpc(description = "Reconfigure FIRA UWB ranging session to add controlee") addControleeFiraRangingSession(String key, JSONObject config)822 public void addControleeFiraRangingSession(String key, JSONObject config) throws Throwable { 823 RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key); 824 FiraControleeParams params = generateFiraControleeParams(config); 825 // Hold on to the shell permission until the session is stopped to get the ranging reports. 826 adoptShellPermission(); 827 rangingSessionCallback.rangingSession.addControlee(params.toBundle()); 828 } 829 830 /** Reconfigures FIRA UWB ranging session to remove controlee. */ 831 @Rpc(description = "Reconfigure FIRA UWB ranging session to remove controlee") removeControleeFiraRangingSession(String key, JSONObject config)832 public void removeControleeFiraRangingSession(String key, JSONObject config) 833 throws Throwable { 834 RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key); 835 FiraControleeParams params = generateFiraControleeParams(config); 836 // Hold on to the shell permission until the session is stopped to get the ranging reports. 837 adoptShellPermission(); 838 rangingSessionCallback.rangingSession.removeControlee(params.toBundle()); 839 } 840 841 /** 842 * Find if UWB peer is found. 843 */ 844 @Rpc(description = "Find if UWB peer is found") isUwbPeerFound(String key, JSONArray jArray)845 public boolean isUwbPeerFound(String key, JSONArray jArray) throws JSONException { 846 return getRangingMeasurement(key, jArray) != null; 847 } 848 849 /** Get UWB distance measurement. */ 850 @Rpc(description = "Get UWB ranging distance measurement with peer.") getDistanceMeasurement(String key, JSONArray jArray)851 public double getDistanceMeasurement(String key, JSONArray jArray) throws JSONException { 852 RangingMeasurement rangingMeasurement = getRangingMeasurement(key, jArray); 853 if (rangingMeasurement == null || rangingMeasurement.getDistanceMeasurement() == null) { 854 throw new NullPointerException("Cannot get Distance Measurement on null object."); 855 } 856 return rangingMeasurement.getDistanceMeasurement().getMeters(); 857 } 858 859 /** Get angle of arrival azimuth measurement. */ 860 @Rpc(description = "Get UWB AoA Azimuth measurement.") getAoAAzimuthMeasurement(String key, JSONArray jArray)861 public double getAoAAzimuthMeasurement(String key, JSONArray jArray) throws JSONException { 862 RangingMeasurement rangingMeasurement = getRangingMeasurement(key, jArray); 863 if (rangingMeasurement == null 864 || rangingMeasurement.getAngleOfArrivalMeasurement() == null 865 || rangingMeasurement.getAngleOfArrivalMeasurement().getAzimuth() == null) { 866 throw new NullPointerException("Cannot get AoA azimuth measurement on null object."); 867 } 868 return rangingMeasurement.getAngleOfArrivalMeasurement().getAzimuth().getRadians(); 869 } 870 871 /** Get angle of arrival altitude measurement. */ 872 @Rpc(description = "Get UWB AoA Altitude measurement.") getAoAAltitudeMeasurement(String key, JSONArray jArray)873 public double getAoAAltitudeMeasurement(String key, JSONArray jArray) throws JSONException { 874 RangingMeasurement rangingMeasurement = getRangingMeasurement(key, jArray); 875 if (rangingMeasurement == null 876 || rangingMeasurement.getAngleOfArrivalMeasurement() == null 877 || rangingMeasurement.getAngleOfArrivalMeasurement().getAltitude() == null) { 878 throw new NullPointerException("Cannot get AoA altitude measurement on null object."); 879 } 880 return rangingMeasurement.getAngleOfArrivalMeasurement().getAltitude().getRadians(); 881 } 882 883 /** Get RSSI measurement. */ 884 @Rpc(description = "Get RSSI measurement.") getRssiDbmMeasurement(String key, JSONArray jArray)885 public int getRssiDbmMeasurement(String key, JSONArray jArray) throws JSONException { 886 RangingMeasurement rangingMeasurement = getRangingMeasurement(key, jArray); 887 if (rangingMeasurement == null) { 888 throw new NullPointerException("Cannot get RSSI dBm measurement on null object."); 889 } 890 return rangingMeasurement.getRssiDbm(); 891 } 892 893 /** Stop UWB ranging. */ 894 @Rpc(description = "Stop UWB ranging") stopRangingSession(String key)895 public void stopRangingSession(String key) throws Throwable { 896 RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key); 897 runWithShellPermission(() -> 898 rangingSessionCallback.rangingSession.stop()); 899 } 900 901 /** Close UWB ranging session. */ 902 @Rpc(description = "Close UWB ranging session") closeRangingSession(String key)903 public void closeRangingSession(String key) throws Throwable { 904 RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.remove(key); 905 if (rangingSessionCallback != null && rangingSessionCallback.rangingSession != null) { 906 runWithShellPermission(() -> 907 rangingSessionCallback.rangingSession.close()); 908 } 909 } 910 convertPersistableBundleToJson(PersistableBundle bundle)911 private JSONObject convertPersistableBundleToJson(PersistableBundle bundle) 912 throws JSONException { 913 JSONObject jsonObj = new JSONObject(); 914 Set<String> keys = bundle.keySet(); 915 for (String key: keys) { 916 if (bundle.get(key) instanceof PersistableBundle) { 917 jsonObj.put(key, convertPersistableBundleToJson( 918 (PersistableBundle) bundle.get(key))); 919 } else { 920 jsonObj.put(key, JSONObject.wrap(bundle.get(key))); 921 } 922 } 923 return jsonObj; 924 } 925 926 /** Get UWB specification info */ 927 @Rpc(description = "Get Uwb specification info") getSpecificationInfo()928 public JSONObject getSpecificationInfo() throws Throwable { 929 return runWithShellPermission(() -> 930 convertPersistableBundleToJson(mUwbManager.getSpecificationInfo())); 931 } 932 933 /** Set airplane mode to True or False */ 934 @Rpc(description = "Set airplane mode") setAirplaneMode(Boolean enabled)935 public void setAirplaneMode(Boolean enabled) throws Throwable { 936 runWithShellPermission(() -> mConnectivityManager.setAirplaneMode(enabled)); 937 } 938 939 @Rpc(description = "Log info level message to device logcat") logInfo(String message)940 public void logInfo(String message) throws JSONException { 941 Log.i(TAG + message); 942 } 943 944 @Override shutdown()945 public void shutdown() {} 946 adoptShellPermission()947 private void adoptShellPermission() { 948 UiAutomation uia = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 949 uia.adoptShellPermissionIdentity(); 950 try { 951 Class<?> cls = Class.forName("android.app.UiAutomation"); 952 Method destroyMethod = cls.getDeclaredMethod("destroy"); 953 destroyMethod.invoke(uia); 954 } catch (ReflectiveOperationException e) { 955 throw new IllegalStateException("Failed to cleaup Ui Automation", e); 956 } 957 } 958 dropShellPermission()959 private void dropShellPermission() { 960 UiAutomation uia = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 961 uia.dropShellPermissionIdentity(); 962 try { 963 Class<?> cls = Class.forName("android.app.UiAutomation"); 964 Method destroyMethod = cls.getDeclaredMethod("destroy"); 965 destroyMethod.invoke(uia); 966 } catch (ReflectiveOperationException e) { 967 throw new IllegalStateException("Failed to cleaup Ui Automation", e); 968 } 969 } 970 getReverseBytes(byte[] data)971 private static byte[] getReverseBytes(byte[] data) { 972 byte[] buffer = new byte[data.length]; 973 for (int i = 0; i < data.length; i++) { 974 buffer[i] = data[data.length - 1 - i]; 975 } 976 return buffer; 977 } getComputedMacAddress(UwbAddress address)978 private static UwbAddress getComputedMacAddress(UwbAddress address) { 979 if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) { 980 return UwbAddress.fromBytes(getReverseBytes(address.toBytes())); 981 } 982 return address; 983 } 984 getComputedVendorId(byte[] data)985 private static byte[] getComputedVendorId(byte[] data) { 986 if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) { 987 return getReverseBytes(data); 988 } 989 return data; 990 } 991 runWithShellPermission(Runnable action)992 public void runWithShellPermission(Runnable action) throws Throwable { 993 adoptShellPermission(); 994 try { 995 action.run(); 996 } finally { 997 dropShellPermission(); 998 } 999 } 1000 runWithShellPermission(ThrowingSupplier<T> action)1001 public <T> T runWithShellPermission(ThrowingSupplier<T> action) throws Throwable { 1002 adoptShellPermission(); 1003 try { 1004 return action.get(); 1005 } finally { 1006 dropShellPermission(); 1007 } 1008 } 1009 1010 /** 1011 * Similar to {@link Supplier} but has {@code throws Exception}. 1012 * 1013 * @param <T> type of the value produced 1014 */ 1015 public interface ThrowingSupplier<T> { 1016 /** 1017 * Similar to {@link Supplier#get} but has {@code throws Exception}. 1018 */ get()1019 T get() throws Exception; 1020 } 1021 } 1022