• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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