1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.googlecode.android_scripting.facade.uwb; 18 19 import android.app.Service; 20 import android.content.Context; 21 import android.os.CancellationSignal; 22 import android.os.PersistableBundle; 23 import android.uwb.RangingMeasurement; 24 import android.uwb.RangingReport; 25 import android.uwb.RangingSession; 26 import android.uwb.UwbAddress; 27 import android.uwb.UwbManager; 28 29 import com.google.uwb.support.fira.FiraOpenSessionParams; 30 import com.google.uwb.support.fira.FiraParams; 31 import com.google.uwb.support.fira.FiraRangingReconfigureParams; 32 import com.googlecode.android_scripting.Log; 33 import com.googlecode.android_scripting.facade.EventFacade; 34 import com.googlecode.android_scripting.facade.FacadeManager; 35 import com.googlecode.android_scripting.jsonrpc.RpcReceiver; 36 import com.googlecode.android_scripting.rpc.Rpc; 37 import com.googlecode.android_scripting.rpc.RpcParameter; 38 39 import org.json.JSONArray; 40 import org.json.JSONException; 41 import org.json.JSONObject; 42 43 import java.util.Arrays; 44 import java.util.HashMap; 45 import java.util.List; 46 import java.util.concurrent.Executor; 47 import java.util.concurrent.Executors; 48 49 /** 50 * SL4A for UwbManager and Ranging APIs. 51 */ 52 public class UwbManagerFacade extends RpcReceiver { 53 54 private static final String TAG = "UwbManagerFacade: "; 55 private final Service mService; 56 private final Context mContext; 57 private final UwbManager mUwbManager; 58 private final Executor mExecutor = Executors.newSingleThreadExecutor(); 59 private final EventFacade mEventFacade; 60 private static HashMap<String, RangingSessionCallback> sRangingSessionCallbackMap = 61 new HashMap<String, RangingSessionCallback>(); 62 private static HashMap<String, UwbAdapterStateCallback> sUwbAdapterStateCallbackMap = 63 new HashMap<String, UwbAdapterStateCallback>(); 64 65 private enum Event { 66 Invalid(0), 67 Opened(1 << 0), 68 Started(1 << 1), 69 Reconfigured(1 << 2), 70 Stopped(1 << 3), 71 Closed(1 << 4), 72 OpenFailed(1 << 5), 73 StartFailed(1 << 6), 74 ReconfigureFailed(1 << 7), 75 StopFailed(1 << 8), 76 CloseFailed(1 << 9), 77 ReportReceived(1 << 10), 78 EventAll( 79 1 << 0 80 | 1 << 1 81 | 1 << 2 82 | 1 << 3 83 | 1 << 4 84 | 1 << 5 85 | 1 << 6 86 | 1 << 7 87 | 1 << 8 88 | 1 << 9 89 | 1 << 10); 90 91 private int mType; Event(int type)92 Event(int type) { 93 mType = type; 94 } getType()95 private int getType() { 96 return mType; 97 } 98 } 99 100 private static class UwbAdapterStateCallback implements UwbManager.AdapterStateCallback { 101 102 private final String mId; 103 private final EventFacade mEventFacade; 104 UwbAdapterStateCallback(EventFacade eventFacade)105 UwbAdapterStateCallback(EventFacade eventFacade) { 106 mId = this.toString(); 107 mEventFacade = eventFacade; 108 } 109 toString(int state)110 public String toString(int state) { 111 switch (state) { 112 case 1: return "Inactive"; 113 case 2: return "Active"; 114 default: return "Disabled"; 115 } 116 } 117 118 @Override onStateChanged(int state, int reason)119 public void onStateChanged(int state, int reason) { 120 Log.d(TAG + "UwbAdapterStateCallback#onStateChanged() called"); 121 Log.d(TAG + "Adapter state changed reason " + String.valueOf(reason)); 122 mEventFacade.postEvent( 123 UwbConstants.EventUwbAdapterStateCallback, 124 new UwbEvents.UwbAdapterStateEvent(mId, toString(state))); 125 } 126 } 127 128 129 class RangingSessionCallback implements RangingSession.Callback { 130 131 public RangingSession rangingSession; 132 public PersistableBundle persistableBundle; 133 public PersistableBundle sessionInfo; 134 public RangingReport rangingReport; 135 public String mId; 136 RangingSessionCallback(int events)137 RangingSessionCallback(int events) { 138 mId = this.toString(); 139 } 140 handleEvent(Event e)141 private void handleEvent(Event e) { 142 Log.d(TAG + "RangingSessionCallback#handleEvent() for " + e.toString()); 143 mEventFacade.postEvent( 144 UwbConstants.EventRangingSessionCallback, 145 new UwbEvents.RangingSessionEvent(mId, e.toString())); 146 } 147 148 @Override onOpened(RangingSession session)149 public void onOpened(RangingSession session) { 150 Log.d(TAG + "RangingSessionCallback#onOpened() called"); 151 rangingSession = session; 152 handleEvent(Event.Opened); 153 } 154 155 @Override onOpenFailed(@eason int reason, PersistableBundle params)156 public void onOpenFailed(@Reason int reason, PersistableBundle params) { 157 Log.d(TAG + "RangingSessionCallback#onOpenedFailed() called"); 158 Log.d(TAG + "OpenFailed reason " + String.valueOf(reason)); 159 persistableBundle = params; 160 handleEvent(Event.OpenFailed); 161 } 162 163 @Override onStarted(PersistableBundle info)164 public void onStarted(PersistableBundle info) { 165 Log.d(TAG + "RangingSessionCallback#onStarted() called"); 166 sessionInfo = info; 167 handleEvent(Event.Started); 168 } 169 170 @Override onStartFailed(@eason int reason, PersistableBundle params)171 public void onStartFailed(@Reason int reason, PersistableBundle params) { 172 Log.d(TAG + "RangingSessionCallback#onStartFailed() called"); 173 Log.d(TAG + "StartFailed reason " + String.valueOf(reason)); 174 persistableBundle = params; 175 handleEvent(Event.StartFailed); 176 } 177 178 @Override onReconfigured(PersistableBundle params)179 public void onReconfigured(PersistableBundle params) { 180 Log.d(TAG + "RangingSessionCallback#oniReconfigured() called"); 181 persistableBundle = params; 182 handleEvent(Event.Reconfigured); 183 } 184 185 @Override onReconfigureFailed(@eason int reason, PersistableBundle params)186 public void onReconfigureFailed(@Reason int reason, PersistableBundle params) { 187 Log.d(TAG + "RangingSessionCallback#onReconfigureFailed() called"); 188 Log.d(TAG + "ReconfigureFailed reason " + String.valueOf(reason)); 189 persistableBundle = params; 190 handleEvent(Event.ReconfigureFailed); 191 } 192 193 @Override onStopped(@eason int reason, PersistableBundle params)194 public void onStopped(@Reason int reason, PersistableBundle params) { 195 Log.d(TAG + "RangingSessionCallback#onStopped() called"); 196 Log.d(TAG + "Stopped reason " + String.valueOf(reason)); 197 persistableBundle = params; 198 handleEvent(Event.Stopped); 199 } 200 201 @Override onStopFailed(@eason int reason, PersistableBundle params)202 public void onStopFailed(@Reason int reason, PersistableBundle params) { 203 Log.d(TAG + "RangingSessionCallback#onStopFailed() called"); 204 Log.d(TAG + "StopFailed reason " + String.valueOf(reason)); 205 persistableBundle = params; 206 handleEvent(Event.StopFailed); 207 } 208 209 @Override onClosed(@eason int reason, PersistableBundle params)210 public void onClosed(@Reason int reason, PersistableBundle params) { 211 Log.d(TAG + "RangingSessionCallback#onClosed() called"); 212 Log.d(TAG + "Closed reason " + String.valueOf(reason)); 213 persistableBundle = params; 214 handleEvent(Event.Closed); 215 } 216 217 @Override onReportReceived(RangingReport report)218 public void onReportReceived(RangingReport report) { 219 Log.d(TAG + "RangingSessionCallback#onReportReceived() called"); 220 rangingReport = report; 221 handleEvent(Event.ReportReceived); 222 } 223 } 224 UwbManagerFacade(FacadeManager manager)225 public UwbManagerFacade(FacadeManager manager) { 226 super(manager); 227 mService = manager.getService(); 228 mContext = mService.getBaseContext(); 229 mUwbManager = (UwbManager) mService.getSystemService(Context.UWB_SERVICE); 230 mEventFacade = manager.getReceiver(EventFacade.class); 231 } 232 233 /** 234 * Get Uwb adapter state. 235 */ 236 @Rpc(description = "Get Uwb adapter state") getAdapterState()237 public int getAdapterState() { 238 return mUwbManager.getAdapterState(); 239 } 240 241 /** 242 * Get the UWB state. 243 */ 244 @Rpc(description = "Get Uwb state") isUwbEnabled()245 public boolean isUwbEnabled() { 246 return mUwbManager.isUwbEnabled(); 247 } 248 249 /** 250 * Set Uwb state to enabled or disabled. 251 * @param enabled : boolean - true to enable, false to disable. 252 */ 253 @Rpc(description = "Change Uwb state to enabled or disabled") setUwbEnabled(@pcParametername = "enabled") Boolean enabled)254 public void setUwbEnabled(@RpcParameter(name = "enabled") Boolean enabled) { 255 Log.d(TAG + "Setting Uwb state to " + enabled); 256 mUwbManager.setUwbEnabled(enabled); 257 } 258 259 /** 260 * Register uwb adapter state callback. 261 */ 262 @Rpc(description = "Register uwb adapter state callback") registerUwbAdapterStateCallback()263 public String registerUwbAdapterStateCallback() { 264 UwbAdapterStateCallback uwbAdapterStateCallback = new UwbAdapterStateCallback(mEventFacade); 265 String key = uwbAdapterStateCallback.mId; 266 sUwbAdapterStateCallbackMap.put(key, uwbAdapterStateCallback); 267 mUwbManager.registerAdapterStateCallback(mExecutor, uwbAdapterStateCallback); 268 return key; 269 } 270 271 /** 272 * Unregister uwb adapter state callback. 273 */ 274 @Rpc(description = "Unregister uwb adapter state callback.") unregisterUwbAdapterStateCallback(String key)275 public void unregisterUwbAdapterStateCallback(String key) { 276 UwbAdapterStateCallback uwbAdapterStateCallback = sUwbAdapterStateCallbackMap.get(key); 277 mUwbManager.unregisterAdapterStateCallback(uwbAdapterStateCallback); 278 sUwbAdapterStateCallbackMap.remove(key); 279 } 280 281 /** 282 * Get UWB specification info. 283 */ 284 @Rpc(description = "Get Uwb specification info") getSpecificationInfo()285 public PersistableBundle getSpecificationInfo() { 286 return mUwbManager.getSpecificationInfo(); 287 } 288 convertJSONArrayToByteArray(JSONArray jArray)289 private byte[] convertJSONArrayToByteArray(JSONArray jArray) throws JSONException { 290 if (jArray == null) { 291 return null; 292 } 293 byte[] bArray = new byte[jArray.length()]; 294 for (int i = 0; i < jArray.length(); i++) { 295 bArray[i] = (byte) jArray.getInt(i); 296 } 297 return bArray; 298 } 299 generateFiraRangingReconfigureParams(JSONObject j)300 private FiraRangingReconfigureParams generateFiraRangingReconfigureParams(JSONObject j) 301 throws JSONException { 302 if (j == null) { 303 return null; 304 } 305 FiraRangingReconfigureParams.Builder builder = new FiraRangingReconfigureParams.Builder(); 306 if (j.has("action")) { 307 builder.setAction(j.getInt("action")); 308 } 309 if (j.has("addressList")) { 310 JSONArray jArray = j.getJSONArray("addressList"); 311 UwbAddress[] addressList = new UwbAddress[jArray.length()]; 312 for (int i = 0; i < jArray.length(); i++) { 313 addressList[i] = UwbAddress.fromBytes( 314 convertJSONArrayToByteArray(jArray.getJSONArray(i))); 315 } 316 builder.setAddressList(addressList); 317 } 318 return builder.build(); 319 } 320 generateFiraOpenSessionParams(JSONObject j)321 private FiraOpenSessionParams generateFiraOpenSessionParams(JSONObject j) throws JSONException { 322 if (j == null) { 323 return null; 324 } 325 FiraOpenSessionParams.Builder builder = new FiraOpenSessionParams.Builder(); 326 builder.setProtocolVersion(FiraParams.PROTOCOL_VERSION_1_1); 327 if (j.has("sessionId")) { 328 builder.setSessionId(j.getInt("sessionId")); 329 } 330 if (j.has("deviceType")) { 331 builder.setDeviceType(j.getInt("deviceType")); 332 } 333 if (j.has("deviceRole")) { 334 builder.setDeviceRole(j.getInt("deviceRole")); 335 } 336 if (j.has("rangingRoundUsage")) { 337 builder.setRangingRoundUsage(j.getInt("rangingRoundUsage")); 338 } 339 if (j.has("multiNodeMode")) { 340 builder.setMultiNodeMode(j.getInt("multiNodeMode")); 341 } 342 if (j.has("deviceAddress")) { 343 JSONArray jArray = j.getJSONArray("deviceAddress"); 344 byte[] bArray = convertJSONArrayToByteArray(jArray); 345 UwbAddress deviceAddress = UwbAddress.fromBytes(bArray); 346 builder.setDeviceAddress(deviceAddress); 347 } 348 if (j.has("destinationAddresses")) { 349 JSONArray jArray = j.getJSONArray("destinationAddresses"); 350 UwbAddress[] destinationUwbAddresses = new UwbAddress[jArray.length()]; 351 for (int i = 0; i < jArray.length(); i++) { 352 destinationUwbAddresses[i] = UwbAddress.fromBytes( 353 convertJSONArrayToByteArray(jArray.getJSONArray(i))); 354 } 355 builder.setDestAddressList(Arrays.asList(destinationUwbAddresses)); 356 } 357 if (j.has("initiationTimeMs")) { 358 builder.setInitiationTimeMs(j.getInt("initiationTimeMs")); 359 } 360 if (j.has("slotDurationRstu")) { 361 builder.setSlotDurationRstu(j.getInt("slotDurationRstu")); 362 } 363 if (j.has("slotsPerRangingRound")) { 364 builder.setSlotsPerRangingRound(j.getInt("slotsPerRangingRound")); 365 } 366 if (j.has("rangingIntervalMs")) { 367 builder.setRangingIntervalMs(j.getInt("rangingIntervalMs")); 368 } 369 if (j.has("blockStrideLength")) { 370 builder.setBlockStrideLength(j.getInt("blockStrideLength")); 371 } 372 if (j.has("hoppingMode")) { 373 builder.setHoppingMode(j.getInt("hoppingMode")); 374 } 375 if (j.has("maxRangingRoundRetries")) { 376 builder.setMaxRangingRoundRetries(j.getInt("maxRangingRoundRetries")); 377 } 378 if (j.has("sessionPriority")) { 379 builder.setSessionPriority(j.getInt("sessionPriority")); 380 } 381 if (j.has("macAddressMode")) { 382 builder.setMacAddressMode(j.getInt("macAddressMode")); 383 } 384 if (j.has("inBandTerminationAttemptCount")) { 385 builder.setInBandTerminationAttemptCount(j.getInt("inBandTerminationAttemptCount")); 386 } 387 if (j.has("channel")) { 388 builder.setChannelNumber(j.getInt("channel")); 389 } 390 if (j.has("preamble")) { 391 builder.setPreambleCodeIndex(j.getInt("preamble")); 392 } 393 if (j.has("vendorId")) { 394 JSONArray jArray = j.getJSONArray("vendorId"); 395 byte[] bArray = convertJSONArrayToByteArray(jArray); 396 builder.setVendorId(bArray); 397 } 398 if (j.has("staticStsIV")) { 399 JSONArray jArray = j.getJSONArray("staticStsIV"); 400 byte[] bArray = convertJSONArrayToByteArray(jArray); 401 builder.setStaticStsIV(bArray); 402 } 403 if (j.has("aoaResultRequest")) { 404 builder.setAoaResultRequest(j.getInt("aoaResultRequest")); 405 } 406 407 return builder.build(); 408 } 409 410 /** 411 * Open UWB ranging session. 412 */ 413 @Rpc(description = "Open UWB ranging session") openRangingSession(@pcParametername = "config") JSONObject config)414 public String openRangingSession(@RpcParameter(name = "config") JSONObject config) 415 throws JSONException { 416 RangingSessionCallback rangingSessionCallback = new RangingSessionCallback( 417 Event.EventAll.getType()); 418 FiraOpenSessionParams params = generateFiraOpenSessionParams(config); 419 CancellationSignal cancellationSignal = mUwbManager.openRangingSession( 420 params.toBundle(), mExecutor, rangingSessionCallback); 421 String key = rangingSessionCallback.mId; 422 sRangingSessionCallbackMap.put(key, rangingSessionCallback); 423 return key; 424 } 425 426 /** 427 * Start UWB ranging. 428 */ 429 @Rpc(description = "Start UWB ranging") startRangingSession(String key)430 public void startRangingSession(String key) { 431 RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key); 432 rangingSessionCallback.rangingSession.start(new PersistableBundle()); 433 } 434 435 /** 436 * Reconfigures UWB ranging session. 437 */ 438 @Rpc(description = "Reconfigure UWB ranging session") reconfigureRangingSession(String key, @RpcParameter(name = "config") JSONObject config)439 public void reconfigureRangingSession(String key, 440 @RpcParameter(name = "config") JSONObject config) throws JSONException { 441 RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key); 442 FiraRangingReconfigureParams params = generateFiraRangingReconfigureParams(config); 443 rangingSessionCallback.rangingSession.reconfigure(params.toBundle()); 444 } 445 getRangingMeasurement(String key, JSONArray jArray)446 private RangingMeasurement getRangingMeasurement(String key, JSONArray jArray) 447 throws JSONException { 448 byte[] bArray = convertJSONArrayToByteArray(jArray); 449 UwbAddress peerAddress = UwbAddress.fromBytes(bArray); 450 RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key); 451 List<RangingMeasurement> rangingMeasurements = 452 rangingSessionCallback.rangingReport.getMeasurements(); 453 for (RangingMeasurement r: rangingMeasurements) { 454 if (r.getStatus() == RangingMeasurement.RANGING_STATUS_SUCCESS 455 && r.getRemoteDeviceAddress().equals(peerAddress)) { 456 Log.d(TAG + "Found peer " + peerAddress.toString()); 457 return r; 458 } 459 } 460 Log.w(TAG + "Invalid ranging status or peer not found."); 461 return null; 462 } 463 464 /** 465 * Find if UWB peer is found. 466 */ 467 @Rpc(description = "Find if UWB peer is found") isUwbPeerFound(String key, JSONArray jArray)468 public boolean isUwbPeerFound(String key, JSONArray jArray) throws JSONException { 469 return getRangingMeasurement(key, jArray) != null; 470 } 471 472 /** 473 * Get UWB distance measurement. 474 */ 475 @Rpc(description = "Get UWB ranging distance measurement with peer.") getDistanceMeasurement(String key, JSONArray jArray)476 public double getDistanceMeasurement(String key, JSONArray jArray) throws JSONException { 477 RangingMeasurement rangingMeasurement = getRangingMeasurement(key, jArray); 478 if (rangingMeasurement == null || rangingMeasurement.getDistanceMeasurement() == null) { 479 throw new NullPointerException("Cannot get Distance Measurement on null object."); 480 } 481 return rangingMeasurement.getDistanceMeasurement().getMeters(); 482 } 483 484 /** 485 * Get angle of arrival azimuth measurement. 486 */ 487 @Rpc(description = "Get UWB AoA Azimuth measurement.") getAoAAzimuthMeasurement(String key, JSONArray jArray)488 public double getAoAAzimuthMeasurement(String key, JSONArray jArray) throws JSONException { 489 RangingMeasurement rangingMeasurement = getRangingMeasurement(key, jArray); 490 if (rangingMeasurement == null 491 || rangingMeasurement.getAngleOfArrivalMeasurement() == null 492 || rangingMeasurement.getAngleOfArrivalMeasurement().getAzimuth() == null) { 493 throw new NullPointerException("Cannot get AoA azimuth measurement on null object."); 494 } 495 return rangingMeasurement.getAngleOfArrivalMeasurement().getAzimuth().getRadians(); 496 } 497 498 /** 499 * Get angle of arrival altitude measurement. 500 */ 501 @Rpc(description = "Get UWB AoA Altitude measurement.") getAoAAltitudeMeasurement(String key, JSONArray jArray)502 public double getAoAAltitudeMeasurement(String key, JSONArray jArray) throws JSONException { 503 RangingMeasurement rangingMeasurement = getRangingMeasurement(key, jArray); 504 if (rangingMeasurement == null 505 || rangingMeasurement.getAngleOfArrivalMeasurement() == null 506 || rangingMeasurement.getAngleOfArrivalMeasurement().getAltitude() == null) { 507 throw new NullPointerException("Cannot get AoA altitude measurement on null object."); 508 } 509 return rangingMeasurement.getAngleOfArrivalMeasurement().getAltitude().getRadians(); 510 } 511 512 /** 513 * Stop UWB ranging. 514 */ 515 @Rpc(description = "Stop UWB ranging") stopRangingSession(String key)516 public void stopRangingSession(String key) { 517 RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key); 518 rangingSessionCallback.rangingSession.stop(); 519 } 520 521 /** 522 * Close UWB ranging session. 523 */ 524 @Rpc(description = "Close UWB ranging session") closeRangingSession(String key)525 public void closeRangingSession(String key) { 526 RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key); 527 rangingSessionCallback.rangingSession.close(); 528 sRangingSessionCallbackMap.remove(key); 529 } 530 531 @Override shutdown()532 public void shutdown() {} 533 } 534