[UwbManagerSnippet] Migrate methods from SL4A to snippets.

Bug: 229730966
Test: None
Change-Id: I105a5d4b0a74fa75e9984bb86b2aeafca067c79d
diff --git a/hostsidetests/multidevices/uwb/snippet/UwbManagerSnippet.java b/hostsidetests/multidevices/uwb/snippet/UwbManagerSnippet.java
index 484d8cb..93d0e14 100644
--- a/hostsidetests/multidevices/uwb/snippet/UwbManagerSnippet.java
+++ b/hostsidetests/multidevices/uwb/snippet/UwbManagerSnippet.java
@@ -18,20 +18,43 @@
 
 import android.app.UiAutomation;
 import android.content.Context;
+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 androidx.test.platform.app.InstrumentationRegistry;
 
 import com.google.android.mobly.snippet.Snippet;
+import com.google.android.mobly.snippet.event.EventCache;
+import com.google.android.mobly.snippet.event.SnippetEvent;
+import com.google.android.mobly.snippet.rpc.AsyncRpc;
 import com.google.android.mobly.snippet.rpc.Rpc;
 import com.google.android.mobly.snippet.util.Log;
+import com.google.uwb.support.ccc.CccOpenRangingParams;
+import com.google.uwb.support.ccc.CccParams;
+import com.google.uwb.support.ccc.CccPulseShapeCombo;
+import com.google.uwb.support.ccc.CccRangingStartedParams;
+import com.google.uwb.support.fira.FiraOpenSessionParams;
+import com.google.uwb.support.fira.FiraParams;
+import com.google.uwb.support.fira.FiraRangingReconfigureParams;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
 
 import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 
 /** Snippet class exposing Android APIs for Uwb. */
 public class UwbManagerSnippet implements Snippet {
     private static class UwbManagerSnippetException extends Exception {
-        private static final long serialVersionUID = 1;
 
         UwbManagerSnippetException(String msg) {
             super(msg);
@@ -41,8 +64,16 @@
             super(msg, err);
         }
     }
+
+    private static final String TAG = "UwbManagerSnippet: ";
     private final UwbManager mUwbManager;
     private final Context mContext;
+    private final Executor mExecutor = Executors.newSingleThreadExecutor();
+    private final EventCache mEventCache = EventCache.getInstance();
+    private static HashMap<String, RangingSessionCallback> sRangingSessionCallbackMap =
+            new HashMap<String, RangingSessionCallback>();
+    private static HashMap<String, UwbAdapterStateCallback> sUwbAdapterStateCallbackMap =
+            new HashMap<String, UwbAdapterStateCallback>();
 
     public UwbManagerSnippet() throws Throwable {
         mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -50,6 +81,186 @@
         adoptShellPermission();
     }
 
+    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 final int mType;
+        Event(int type) {
+            mType = type;
+        }
+        private int getType() {
+            return mType;
+        }
+    }
+
+    class UwbAdapterStateCallback implements UwbManager.AdapterStateCallback {
+
+        public String mId;
+
+        UwbAdapterStateCallback(String id) {
+            mId = id;
+        }
+
+        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));
+            SnippetEvent event = new SnippetEvent(mId, "UwbAdapterStateCallback");
+            event.getData().putString("uwbAdapterStateEvent", toString(state));
+            mEventCache.postEvent(event);
+        }
+    }
+
+    class RangingSessionCallback implements RangingSession.Callback {
+
+        public RangingSession rangingSession;
+        public PersistableBundle persistableBundle;
+        public PersistableBundle sessionInfo;
+        public RangingReport rangingReport;
+        public String mId;
+
+        RangingSessionCallback(String id, int events) {
+            mId = id;
+        }
+
+        private void handleEvent(Event e) {
+            Log.d(TAG + "RangingSessionCallback#handleEvent() for " + e.toString());
+            SnippetEvent event = new SnippetEvent(mId, "RangingSessionCallback");
+            event.getData().putString("rangingSessionEvent", e.toString());
+            mEventCache.postEvent(event);
+        }
+
+        @Override
+        public void onOpened(RangingSession session) {
+            Log.d(TAG + "RangingSessionCallback#onOpened() called");
+            rangingSession = session;
+            handleEvent(Event.Opened);
+        }
+
+        @Override
+        public void onOpenFailed(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(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(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(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(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(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);
+        }
+
+    }
+
+    /** Register uwb adapter state callback. */
+    @AsyncRpc(description = "Register uwb adapter state callback")
+    public void registerUwbAdapterStateCallback(String callbackId, String key) {
+        UwbAdapterStateCallback uwbAdapterStateCallback = new UwbAdapterStateCallback(callbackId);
+        sUwbAdapterStateCallbackMap.put(key, uwbAdapterStateCallback);
+        mUwbManager.registerAdapterStateCallback(mExecutor, uwbAdapterStateCallback);
+    }
+
+    /** 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 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() {
@@ -62,6 +273,318 @@
         mUwbManager.setUwbEnabled(enabled);
     }
 
+    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 CccRangingStartedParams generateCccRangingStartedParams(JSONObject j)
+            throws JSONException {
+        if (j == null) {
+            return null;
+        }
+        CccRangingStartedParams.Builder builder = new CccRangingStartedParams.Builder();
+        if (j.has("stsIndex")) {
+            builder.setStartingStsIndex(j.getInt("stsIndex"));
+        }
+        if (j.has("uwbTime")) {
+            builder.setUwbTime0(j.getInt("uwbTime"));
+        }
+        if (j.has("hopModeKey")) {
+            builder.setHopModeKey(j.getInt("hopModeKey"));
+        }
+        if (j.has("syncCodeIndex")) {
+            builder.setSyncCodeIndex(j.getInt("syncCodeIndex"));
+        }
+        if (j.has("ranMultiplier")) {
+            builder.setRanMultiplier(j.getInt("ranMultiplier"));
+        }
+
+        return builder.build();
+    }
+
+    private CccOpenRangingParams generateCccOpenRangingParams(JSONObject j) throws JSONException {
+        if (j == null) {
+            return null;
+        }
+        CccOpenRangingParams.Builder builder = new CccOpenRangingParams.Builder();
+        builder.setProtocolVersion(CccParams.PROTOCOL_VERSION_1_0);
+        if (j.has("sessionId")) {
+            builder.setSessionId(j.getInt("sessionId"));
+        }
+        if (j.has("uwbConfig")) {
+            builder.setUwbConfig(j.getInt("uwbConfig"));
+        }
+        if (j.has("ranMultiplier")) {
+            builder.setRanMultiplier(j.getInt("ranMultiplier"));
+        }
+        if (j.has("channel")) {
+            builder.setChannel(j.getInt("channel"));
+        }
+        if (j.has("chapsPerSlot")) {
+            builder.setNumChapsPerSlot(j.getInt("chapsPerSlot"));
+        }
+        if (j.has("responderNodes")) {
+            builder.setNumResponderNodes(j.getInt("responderNodes"));
+        }
+        if (j.has("slotsPerRound")) {
+            builder.setNumSlotsPerRound(j.getInt("slotsPerRound"));
+        }
+        if (j.has("hoppingMode")) {
+            builder.setHoppingConfigMode(j.getInt("hoppingMode"));
+        }
+        if (j.has("hoppingSequence")) {
+            builder.setHoppingSequence(j.getInt("hoppingSequence"));
+        }
+        if (j.has("syncCodeIndex")) {
+            builder.setSyncCodeIndex(j.getInt("syncCodeIndex"));
+        }
+        if (j.has("pulseShapeCombo")) {
+            JSONObject pulseShapeCombo = j.getJSONObject("pulseShapeCombo");
+            builder.setPulseShapeCombo(new CccPulseShapeCombo(
+                    pulseShapeCombo.getInt("pulseShapeComboTx"),
+                    pulseShapeCombo.getInt("pulseShapeComboRx")));
+        }
+
+        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();
+    }
+
+    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;
+    }
+
+    /** Open FIRA UWB ranging session. */
+    @AsyncRpc(description = "Open FIRA UWB ranging session")
+    public void openFiraRangingSession(String callbackId, String key, JSONObject config)
+            throws JSONException {
+        RangingSessionCallback rangingSessionCallback = new RangingSessionCallback(
+                callbackId, Event.EventAll.getType());
+        FiraOpenSessionParams params = generateFiraOpenSessionParams(config);
+        mUwbManager.openRangingSession(params.toBundle(), mExecutor, rangingSessionCallback);
+        sRangingSessionCallbackMap.put(key, rangingSessionCallback);
+    }
+
+    /** Open CCC UWB ranging session. */
+    @AsyncRpc(description = "Open CCC UWB ranging session")
+    public void openCccRangingSession(String callbackId, String key, JSONObject config)
+            throws JSONException {
+        RangingSessionCallback rangingSessionCallback = new RangingSessionCallback(
+                callbackId, Event.EventAll.getType());
+        CccOpenRangingParams params = generateCccOpenRangingParams(config);
+        mUwbManager.openRangingSession(params.toBundle(), mExecutor, rangingSessionCallback);
+        sRangingSessionCallbackMap.put(key, rangingSessionCallback);
+    }
+
+    /** Start FIRA UWB ranging. */
+    @Rpc(description = "Start FIRA UWB ranging")
+    public void startFiraRangingSession(String key) {
+        RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
+        rangingSessionCallback.rangingSession.start(new PersistableBundle());
+    }
+
+    /** Start CCC UWB ranging. */
+    @Rpc(description = "Start CCC UWB ranging")
+    public void startCccRangingSession(String key, JSONObject config) throws JSONException {
+        RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
+        CccRangingStartedParams params = generateCccRangingStartedParams(config);
+        rangingSessionCallback.rangingSession.start(params.toBundle());
+    }
+
+    /** Reconfigures FIRA UWB ranging session. */
+    @Rpc(description = "Reconfigure FIRA UWB ranging session")
+    public void reconfigureFiraRangingSession(String key, JSONObject config) throws JSONException {
+        RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
+        FiraRangingReconfigureParams params = generateFiraRangingReconfigureParams(config);
+        rangingSessionCallback.rangingSession.reconfigure(params.toBundle());
+    }
+
+    /**
+     * 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() {}