blob: 5ca7ff83b0aba5213e1306c427f7d9dc0f258fa9 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.googlecode.android_scripting.facade.uwb;
import android.app.Service;
import android.content.Context;
import android.os.CancellationSignal;
import android.os.PersistableBundle;
import android.uwb.RangingMeasurement;
import android.uwb.RangingReport;
import android.uwb.RangingSession;
import android.uwb.UwbAddress;
import android.uwb.UwbManager;
import com.google.uwb.support.fira.FiraOpenSessionParams;
import com.google.uwb.support.fira.FiraParams;
import com.google.uwb.support.fira.FiraRangingReconfigureParams;
import com.googlecode.android_scripting.Log;
import com.googlecode.android_scripting.facade.EventFacade;
import com.googlecode.android_scripting.facade.FacadeManager;
import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
import com.googlecode.android_scripting.rpc.Rpc;
import com.googlecode.android_scripting.rpc.RpcParameter;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* SL4A for UwbManager and Ranging APIs.
*/
public class UwbManagerFacade extends RpcReceiver {
private static final String TAG = "UwbManagerFacade: ";
private final Service mService;
private final Context mContext;
private final UwbManager mUwbManager;
private final Executor mExecutor = Executors.newSingleThreadExecutor();
private final EventFacade mEventFacade;
private static HashMap<String, RangingSessionCallback> sRangingSessionCallbackMap =
new HashMap<String, RangingSessionCallback>();
private static HashMap<String, UwbAdapterStateCallback> sUwbAdapterStateCallbackMap =
new HashMap<String, UwbAdapterStateCallback>();
private enum Event {
Invalid(0),
Opened(1 << 0),
Started(1 << 1),
Reconfigured(1 << 2),
Stopped(1 << 3),
Closed(1 << 4),
OpenFailed(1 << 5),
StartFailed(1 << 6),
ReconfigureFailed(1 << 7),
StopFailed(1 << 8),
CloseFailed(1 << 9),
ReportReceived(1 << 10),
EventAll(
1 << 0
| 1 << 1
| 1 << 2
| 1 << 3
| 1 << 4
| 1 << 5
| 1 << 6
| 1 << 7
| 1 << 8
| 1 << 9
| 1 << 10);
private int mType;
Event(int type) {
mType = type;
}
private int getType() {
return mType;
}
}
private static class UwbAdapterStateCallback implements UwbManager.AdapterStateCallback {
private final String mId;
private final EventFacade mEventFacade;
UwbAdapterStateCallback(EventFacade eventFacade) {
mId = this.toString();
mEventFacade = eventFacade;
}
public String toString(int state) {
switch (state) {
case 1: return "Inactive";
case 2: return "Active";
default: return "Disabled";
}
}
@Override
public void onStateChanged(int state, int reason) {
Log.d(TAG + "UwbAdapterStateCallback#onStateChanged() called");
Log.d(TAG + "Adapter state changed reason " + String.valueOf(reason));
mEventFacade.postEvent(
UwbConstants.EventUwbAdapterStateCallback,
new UwbEvents.UwbAdapterStateEvent(mId, toString(state)));
}
}
class RangingSessionCallback implements RangingSession.Callback {
public RangingSession rangingSession;
public PersistableBundle persistableBundle;
public PersistableBundle sessionInfo;
public RangingReport rangingReport;
public String mId;
RangingSessionCallback(int events) {
mId = this.toString();
}
private void handleEvent(Event e) {
Log.d(TAG + "RangingSessionCallback#handleEvent() for " + e.toString());
mEventFacade.postEvent(
UwbConstants.EventRangingSessionCallback,
new UwbEvents.RangingSessionEvent(mId, e.toString()));
}
@Override
public void onOpened(RangingSession session) {
Log.d(TAG + "RangingSessionCallback#onOpened() called");
rangingSession = session;
handleEvent(Event.Opened);
}
@Override
public void onOpenFailed(@Reason int reason, PersistableBundle params) {
Log.d(TAG + "RangingSessionCallback#onOpenedFailed() called");
Log.d(TAG + "OpenFailed reason " + String.valueOf(reason));
persistableBundle = params;
handleEvent(Event.OpenFailed);
}
@Override
public void onStarted(PersistableBundle info) {
Log.d(TAG + "RangingSessionCallback#onStarted() called");
sessionInfo = info;
handleEvent(Event.Started);
}
@Override
public void onStartFailed(@Reason int reason, PersistableBundle params) {
Log.d(TAG + "RangingSessionCallback#onStartFailed() called");
Log.d(TAG + "StartFailed reason " + String.valueOf(reason));
persistableBundle = params;
handleEvent(Event.StartFailed);
}
@Override
public void onReconfigured(PersistableBundle params) {
Log.d(TAG + "RangingSessionCallback#oniReconfigured() called");
persistableBundle = params;
handleEvent(Event.Reconfigured);
}
@Override
public void onReconfigureFailed(@Reason int reason, PersistableBundle params) {
Log.d(TAG + "RangingSessionCallback#onReconfigureFailed() called");
Log.d(TAG + "ReconfigureFailed reason " + String.valueOf(reason));
persistableBundle = params;
handleEvent(Event.ReconfigureFailed);
}
@Override
public void onStopped(@Reason int reason, PersistableBundle params) {
Log.d(TAG + "RangingSessionCallback#onStopped() called");
Log.d(TAG + "Stopped reason " + String.valueOf(reason));
persistableBundle = params;
handleEvent(Event.Stopped);
}
@Override
public void onStopFailed(@Reason int reason, PersistableBundle params) {
Log.d(TAG + "RangingSessionCallback#onStopFailed() called");
Log.d(TAG + "StopFailed reason " + String.valueOf(reason));
persistableBundle = params;
handleEvent(Event.StopFailed);
}
@Override
public void onClosed(@Reason int reason, PersistableBundle params) {
Log.d(TAG + "RangingSessionCallback#onClosed() called");
Log.d(TAG + "Closed reason " + String.valueOf(reason));
persistableBundle = params;
handleEvent(Event.Closed);
}
@Override
public void onReportReceived(RangingReport report) {
Log.d(TAG + "RangingSessionCallback#onReportReceived() called");
rangingReport = report;
handleEvent(Event.ReportReceived);
}
}
public UwbManagerFacade(FacadeManager manager) {
super(manager);
mService = manager.getService();
mContext = mService.getBaseContext();
mUwbManager = (UwbManager) mService.getSystemService(Context.UWB_SERVICE);
mEventFacade = manager.getReceiver(EventFacade.class);
}
/**
* Get Uwb adapter state.
*/
@Rpc(description = "Get Uwb adapter state")
public int getAdapterState() {
return mUwbManager.getAdapterState();
}
/**
* Get the UWB state.
*/
@Rpc(description = "Get Uwb state")
public boolean isUwbEnabled() {
return mUwbManager.isUwbEnabled();
}
/**
* Set Uwb state to enabled or disabled.
* @param enabled : boolean - true to enable, false to disable.
*/
@Rpc(description = "Change Uwb state to enabled or disabled")
public void setUwbEnabled(@RpcParameter(name = "enabled") Boolean enabled) {
Log.d(TAG + "Setting Uwb state to " + enabled);
mUwbManager.setUwbEnabled(enabled);
}
/**
* Register uwb adapter state callback.
*/
@Rpc(description = "Register uwb adapter state callback")
public String registerUwbAdapterStateCallback() {
UwbAdapterStateCallback uwbAdapterStateCallback = new UwbAdapterStateCallback(mEventFacade);
String key = uwbAdapterStateCallback.mId;
sUwbAdapterStateCallbackMap.put(key, uwbAdapterStateCallback);
mUwbManager.registerAdapterStateCallback(mExecutor, uwbAdapterStateCallback);
return key;
}
/**
* Unregister uwb adapter state callback.
*/
@Rpc(description = "Unregister uwb adapter state callback.")
public void unregisterUwbAdapterStateCallback(String key) {
UwbAdapterStateCallback uwbAdapterStateCallback = sUwbAdapterStateCallbackMap.get(key);
mUwbManager.unregisterAdapterStateCallback(uwbAdapterStateCallback);
sUwbAdapterStateCallbackMap.remove(key);
}
/**
* Get UWB specification info.
*/
@Rpc(description = "Get Uwb specification info")
public PersistableBundle getSpecificationInfo() {
return mUwbManager.getSpecificationInfo();
}
private byte[] convertJSONArrayToByteArray(JSONArray jArray) throws JSONException {
if (jArray == null) {
return null;
}
byte[] bArray = new byte[jArray.length()];
for (int i = 0; i < jArray.length(); i++) {
bArray[i] = (byte) jArray.getInt(i);
}
return bArray;
}
private FiraRangingReconfigureParams generateFiraRangingReconfigureParams(JSONObject j)
throws JSONException {
if (j == null) {
return null;
}
FiraRangingReconfigureParams.Builder builder = new FiraRangingReconfigureParams.Builder();
if (j.has("action")) {
builder.setAction(j.getInt("action"));
}
if (j.has("addressList")) {
JSONArray jArray = j.getJSONArray("addressList");
UwbAddress[] addressList = new UwbAddress[jArray.length()];
for (int i = 0; i < jArray.length(); i++) {
addressList[i] = UwbAddress.fromBytes(
convertJSONArrayToByteArray(jArray.getJSONArray(i)));
}
builder.setAddressList(addressList);
}
return builder.build();
}
private FiraOpenSessionParams generateFiraOpenSessionParams(JSONObject j) throws JSONException {
if (j == null) {
return null;
}
FiraOpenSessionParams.Builder builder = new FiraOpenSessionParams.Builder();
builder.setProtocolVersion(FiraParams.PROTOCOL_VERSION_1_1);
if (j.has("sessionId")) {
builder.setSessionId(j.getInt("sessionId"));
}
if (j.has("deviceType")) {
builder.setDeviceType(j.getInt("deviceType"));
}
if (j.has("deviceRole")) {
builder.setDeviceRole(j.getInt("deviceRole"));
}
if (j.has("rangingRoundUsage")) {
builder.setRangingRoundUsage(j.getInt("rangingRoundUsage"));
}
if (j.has("multiNodeMode")) {
builder.setMultiNodeMode(j.getInt("multiNodeMode"));
}
if (j.has("deviceAddress")) {
JSONArray jArray = j.getJSONArray("deviceAddress");
byte[] bArray = convertJSONArrayToByteArray(jArray);
UwbAddress deviceAddress = UwbAddress.fromBytes(bArray);
builder.setDeviceAddress(deviceAddress);
}
if (j.has("destinationAddresses")) {
JSONArray jArray = j.getJSONArray("destinationAddresses");
UwbAddress[] destinationUwbAddresses = new UwbAddress[jArray.length()];
for (int i = 0; i < jArray.length(); i++) {
destinationUwbAddresses[i] = UwbAddress.fromBytes(
convertJSONArrayToByteArray(jArray.getJSONArray(i)));
}
builder.setDestAddressList(Arrays.asList(destinationUwbAddresses));
}
if (j.has("initiationTimeMs")) {
builder.setInitiationTimeMs(j.getInt("initiationTimeMs"));
}
if (j.has("slotDurationRstu")) {
builder.setSlotDurationRstu(j.getInt("slotDurationRstu"));
}
if (j.has("slotsPerRangingRound")) {
builder.setSlotsPerRangingRound(j.getInt("slotsPerRangingRound"));
}
if (j.has("rangingIntervalMs")) {
builder.setRangingIntervalMs(j.getInt("rangingIntervalMs"));
}
if (j.has("blockStrideLength")) {
builder.setBlockStrideLength(j.getInt("blockStrideLength"));
}
if (j.has("hoppingMode")) {
builder.setHoppingMode(j.getInt("hoppingMode"));
}
if (j.has("maxRangingRoundRetries")) {
builder.setMaxRangingRoundRetries(j.getInt("maxRangingRoundRetries"));
}
if (j.has("sessionPriority")) {
builder.setSessionPriority(j.getInt("sessionPriority"));
}
if (j.has("macAddressMode")) {
builder.setMacAddressMode(j.getInt("macAddressMode"));
}
if (j.has("inBandTerminationAttemptCount")) {
builder.setInBandTerminationAttemptCount(j.getInt("inBandTerminationAttemptCount"));
}
if (j.has("channel")) {
builder.setChannelNumber(j.getInt("channel"));
}
if (j.has("preamble")) {
builder.setPreambleCodeIndex(j.getInt("preamble"));
}
if (j.has("vendorId")) {
JSONArray jArray = j.getJSONArray("vendorId");
byte[] bArray = convertJSONArrayToByteArray(jArray);
builder.setVendorId(bArray);
}
if (j.has("staticStsIV")) {
JSONArray jArray = j.getJSONArray("staticStsIV");
byte[] bArray = convertJSONArrayToByteArray(jArray);
builder.setStaticStsIV(bArray);
}
if (j.has("aoaResultRequest")) {
builder.setAoaResultRequest(j.getInt("aoaResultRequest"));
}
return builder.build();
}
/**
* Open UWB ranging session.
*/
@Rpc(description = "Open UWB ranging session")
public String openRangingSession(@RpcParameter(name = "config") JSONObject config)
throws JSONException {
RangingSessionCallback rangingSessionCallback = new RangingSessionCallback(
Event.EventAll.getType());
FiraOpenSessionParams params = generateFiraOpenSessionParams(config);
CancellationSignal cancellationSignal = mUwbManager.openRangingSession(
params.toBundle(), mExecutor, rangingSessionCallback);
String key = rangingSessionCallback.mId;
sRangingSessionCallbackMap.put(key, rangingSessionCallback);
return key;
}
/**
* Start UWB ranging.
*/
@Rpc(description = "Start UWB ranging")
public void startRangingSession(String key) {
RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
rangingSessionCallback.rangingSession.start(new PersistableBundle());
}
/**
* Reconfigures UWB ranging session.
*/
@Rpc(description = "Reconfigure UWB ranging session")
public void reconfigureRangingSession(String key,
@RpcParameter(name = "config") JSONObject config) throws JSONException {
RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
FiraRangingReconfigureParams params = generateFiraRangingReconfigureParams(config);
rangingSessionCallback.rangingSession.reconfigure(params.toBundle());
}
private RangingMeasurement getRangingMeasurement(String key, JSONArray jArray)
throws JSONException {
byte[] bArray = convertJSONArrayToByteArray(jArray);
UwbAddress peerAddress = UwbAddress.fromBytes(bArray);
RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
List<RangingMeasurement> rangingMeasurements =
rangingSessionCallback.rangingReport.getMeasurements();
for (RangingMeasurement r: rangingMeasurements) {
if (r.getStatus() == RangingMeasurement.RANGING_STATUS_SUCCESS
&& r.getRemoteDeviceAddress().equals(peerAddress)) {
Log.d(TAG + "Found peer " + peerAddress.toString());
return r;
}
}
Log.w(TAG + "Invalid ranging status or peer not found.");
return null;
}
/**
* Find if UWB peer is found.
*/
@Rpc(description = "Find if UWB peer is found")
public boolean isUwbPeerFound(String key, JSONArray jArray) throws JSONException {
return getRangingMeasurement(key, jArray) != null;
}
/**
* Get UWB distance measurement.
*/
@Rpc(description = "Get UWB ranging distance measurement with peer.")
public double getDistanceMeasurement(String key, JSONArray jArray) throws JSONException {
RangingMeasurement rangingMeasurement = getRangingMeasurement(key, jArray);
if (rangingMeasurement == null || rangingMeasurement.getDistanceMeasurement() == null) {
throw new NullPointerException("Cannot get Distance Measurement on null object.");
}
return rangingMeasurement.getDistanceMeasurement().getMeters();
}
/**
* Get angle of arrival azimuth measurement.
*/
@Rpc(description = "Get UWB AoA Azimuth measurement.")
public double getAoAAzimuthMeasurement(String key, JSONArray jArray) throws JSONException {
RangingMeasurement rangingMeasurement = getRangingMeasurement(key, jArray);
if (rangingMeasurement == null
|| rangingMeasurement.getAngleOfArrivalMeasurement() == null
|| rangingMeasurement.getAngleOfArrivalMeasurement().getAzimuth() == null) {
throw new NullPointerException("Cannot get AoA azimuth measurement on null object.");
}
return rangingMeasurement.getAngleOfArrivalMeasurement().getAzimuth().getRadians();
}
/**
* Get angle of arrival altitude measurement.
*/
@Rpc(description = "Get UWB AoA Altitude measurement.")
public double getAoAAltitudeMeasurement(String key, JSONArray jArray) throws JSONException {
RangingMeasurement rangingMeasurement = getRangingMeasurement(key, jArray);
if (rangingMeasurement == null
|| rangingMeasurement.getAngleOfArrivalMeasurement() == null
|| rangingMeasurement.getAngleOfArrivalMeasurement().getAltitude() == null) {
throw new NullPointerException("Cannot get AoA altitude measurement on null object.");
}
return rangingMeasurement.getAngleOfArrivalMeasurement().getAltitude().getRadians();
}
/**
* Stop UWB ranging.
*/
@Rpc(description = "Stop UWB ranging")
public void stopRangingSession(String key) {
RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
rangingSessionCallback.rangingSession.stop();
}
/**
* Close UWB ranging session.
*/
@Rpc(description = "Close UWB ranging session")
public void closeRangingSession(String key) {
RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
rangingSessionCallback.rangingSession.close();
sRangingSessionCallbackMap.remove(key);
}
@Override
public void shutdown() {}
}