• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.wifi.aware;
18 
19 import android.app.UiAutomation;
20 import android.Manifest;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.pm.PackageManager;
26 import android.net.NetworkSpecifier;
27 import android.net.MacAddress;
28 import android.net.wifi.aware.AttachCallback;
29 import android.net.wifi.aware.Characteristics;
30 import android.net.wifi.aware.DiscoverySession;
31 import android.net.wifi.aware.DiscoverySessionCallback;
32 import android.net.wifi.aware.IdentityChangedListener;
33 import android.net.wifi.aware.PeerHandle;
34 import android.net.wifi.aware.PublishConfig;
35 import android.net.wifi.aware.PublishDiscoverySession;
36 import android.net.wifi.aware.ServiceDiscoveryInfo;
37 import android.net.wifi.aware.SubscribeConfig;
38 import android.net.wifi.aware.SubscribeDiscoverySession;
39 import android.net.wifi.aware.WifiAwareManager;
40 import android.net.wifi.aware.WifiAwareNetworkSpecifier;
41 import android.net.wifi.aware.WifiAwareSession;
42 import android.net.wifi.rtt.RangingRequest;
43 import android.net.wifi.rtt.RangingResult;
44 import android.net.wifi.rtt.RangingResultCallback;
45 import android.net.wifi.rtt.WifiRttManager;
46 import android.net.wifi.ScanResult;
47 import android.net.wifi.WifiInfo;
48 import android.net.wifi.WifiManager;
49 import android.os.Bundle;
50 import android.os.Handler;
51 import android.os.HandlerThread;
52 import android.text.TextUtils;
53 import android.util.Base64;
54 
55 import android.os.RemoteException;
56 
57 import androidx.annotation.NonNull;
58 import androidx.test.core.app.ApplicationProvider;
59 import androidx.test.platform.app.InstrumentationRegistry;
60 
61 import com.google.android.mobly.snippet.Snippet;
62 import com.google.android.mobly.snippet.event.EventCache;
63 import com.google.android.mobly.snippet.event.SnippetEvent;
64 import com.google.android.mobly.snippet.rpc.AsyncRpc;
65 import com.google.android.mobly.snippet.rpc.Rpc;
66 import com.google.android.mobly.snippet.rpc.RpcOptional;
67 import com.google.android.mobly.snippet.util.Log;
68 
69 import org.json.JSONArray;
70 import org.json.JSONException;
71 import org.json.JSONObject;
72 
73 import java.nio.charset.StandardCharsets;
74 import java.util.ArrayList;
75 import java.util.Arrays;
76 import java.util.List;
77 import java.util.ListIterator;
78 import java.util.concurrent.ConcurrentHashMap;
79 
80 /**
81  * Snippet class for exposing {@link WifiAwareManager} APIs.
82  */
83 public class WifiAwareManagerSnippet implements Snippet {
84     private final Context mContext;
85     private final WifiAwareManager mWifiAwareManager;
86     private final WifiRttManager mWifiRttManager;
87     private final WifiManager mWifiManager;
88     private final Handler mHandler;
89     // WifiAwareSession will be initialized after attach.
90     private final ConcurrentHashMap<String, WifiAwareSession> mAttachSessions =
91             new ConcurrentHashMap<>();
92     // DiscoverySession will be initialized after publish or subscribe
93     private final ConcurrentHashMap<String, DiscoverySession> mDiscoverySessions =
94             new ConcurrentHashMap<>();
95     private final ConcurrentHashMap<Integer, PeerHandle> mPeerHandles = new ConcurrentHashMap<>();
96     private final EventCache eventCache = EventCache.getInstance();
97     private WifiAwareStateChangedReceiver stateChangedReceiver;
98 
99     /**
100      * Custom exception class for handling specific errors related to the WifiAwareManagerSnippet
101      * operations.
102      */
103     private static class WifiAwareManagerSnippetException extends Exception {
WifiAwareManagerSnippetException(String msg)104         WifiAwareManagerSnippetException(String msg) {
105             super(msg);
106         }
107     }
108 
WifiAwareManagerSnippet()109     public WifiAwareManagerSnippet() throws WifiAwareManagerSnippetException {
110         mContext = ApplicationProvider.getApplicationContext();
111         PermissionUtils.checkPermissions(mContext, Manifest.permission.ACCESS_WIFI_STATE,
112                 Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.ACCESS_FINE_LOCATION,
113                 Manifest.permission.NEARBY_WIFI_DEVICES
114         );
115         mWifiAwareManager = mContext.getSystemService(WifiAwareManager.class);
116         checkWifiAwareManager();
117         mWifiRttManager = mContext.getSystemService(WifiRttManager.class);
118         mWifiManager = mContext.getSystemService(WifiManager.class);
119         HandlerThread handlerThread = new HandlerThread("Snippet-Aware");
120         handlerThread.start();
121         mHandler = new Handler(handlerThread.getLooper());
122     }
adoptShellPermission()123     private void adoptShellPermission() throws RemoteException {
124         UiAutomation uia = InstrumentationRegistry.getInstrumentation().getUiAutomation();
125         uia.adoptShellPermissionIdentity();
126     }
127 
dropShellPermission()128     private void dropShellPermission() throws RemoteException {
129         UiAutomation uia = InstrumentationRegistry.getInstrumentation().getUiAutomation();
130         uia.dropShellPermissionIdentity();
131     }
132 
133     /**
134      * Returns the MAC address of the currently active access point.
135      */
136     @Rpc(description = "Returns information about the currently active access point.")
wifiGetActiveNetworkMacAddress()137     public String wifiGetActiveNetworkMacAddress() throws Exception {
138         WifiInfo info = null;
139         try {
140             adoptShellPermission();
141             info = mWifiManager.getConnectionInfo();
142         } catch (RemoteException e) {
143             Log.e("RemoteException message: " + e);
144         } finally {
145             // cleanup
146             dropShellPermission();
147         }
148         return info.getMacAddress();
149     }
150 
151     /**
152      * Returns whether Wi-Fi Aware is supported.
153      */
154     @Rpc(description = "Is Wi-Fi Aware supported.")
wifiAwareIsSupported()155     public boolean wifiAwareIsSupported() {
156         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
157     }
158 
159     /**
160      * Returns whether Wi-Fi RTT is supported.
161      */
162     @Rpc(description = "Is Wi-Fi RTT supported.")
wifiAwareIsRttSupported()163     public boolean wifiAwareIsRttSupported() {
164         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT);
165     }
166 
167     /**
168      * Use {@link WifiAwareManager#attach(AttachCallback, Handler)} to attach to the Wi-Fi Aware.
169      *
170      * @param callbackId Assigned automatically by mobly. Also will be used as Attach session id for
171      *                   further operations
172      */
173     @AsyncRpc(
174             description = "Attach to the Wi-Fi Aware service - enabling the application to "
175                     + "create discovery sessions or publish or subscribe to services."
176     )
wifiAwareAttach(String callbackId)177     public void wifiAwareAttach(String callbackId) {
178         attach(callbackId, false);
179     }
180 
181     /**
182      * Use {@link WifiAwareManager#attach(AttachCallback, Handler)} to attach to the Wi-Fi Aware.
183      *
184      * @param callbackId Assigned automatically by mobly. Also will be used as Attach session id for
185      *                   further operations
186      * @param identityCb If true, the application will be notified of changes to the device's
187      */
188     @AsyncRpc(
189             description = "Attach to the Wi-Fi Aware service - enabling the application to "
190                     + "create discovery sessions or publish or subscribe to services."
191     )
wifiAwareAttached(String callbackId, boolean identityCb)192     public void wifiAwareAttached(String callbackId, boolean identityCb)
193             throws WifiAwareManagerSnippetException {
194         attach(callbackId, identityCb);
195     }
196 
attach(String callbackId, boolean identityCb)197     private void attach(String callbackId, boolean identityCb) {
198         AttachCallback attachCallback = new AttachCallback() {
199             @Override
200             public void onAttachFailed() {
201                 super.onAttachFailed();
202                 sendEvent(callbackId, "onAttachFailed");
203             }
204 
205             @Override
206             public void onAttached(WifiAwareSession session) {
207                 super.onAttached(session);
208                 mAttachSessions.put(callbackId, session);
209                 sendEvent(callbackId, "onAttached");
210 
211             }
212 
213             @Override
214             public void onAwareSessionTerminated() {
215                 super.onAwareSessionTerminated();
216                 mAttachSessions.remove(callbackId);
217                 sendEvent(callbackId, "onAwareSessionTerminated");
218             }
219         };
220         if (identityCb) {
221             mWifiAwareManager.attach(attachCallback,
222                     new AwareIdentityChangeListenerPostsEvents(eventCache, callbackId), mHandler
223             );
224         } else {
225             mWifiAwareManager.attach(attachCallback, mHandler);
226         }
227 
228     }
229 
230     private static class AwareIdentityChangeListenerPostsEvents extends IdentityChangedListener {
231         private final EventCache eventCache;
232         private final String callbackId;
233 
AwareIdentityChangeListenerPostsEvents(EventCache eventCache, String callbackId)234         public AwareIdentityChangeListenerPostsEvents(EventCache eventCache, String callbackId) {
235             this.eventCache = eventCache;
236             this.callbackId = callbackId;
237         }
238 
239         @Override
onIdentityChanged(byte[] mac)240         public void onIdentityChanged(byte[] mac) {
241             SnippetEvent event = new SnippetEvent(callbackId, "WifiAwareAttachOnIdentityChanged");
242             event.getData().putLong("timestampMs", System.currentTimeMillis());
243             event.getData().putString("mac", MacAddress.fromBytes(mac).toString());
244             eventCache.postEvent(event);
245             Log.d("WifiAwareattach identity changed called for WifiAwareAttachOnIdentityChanged");
246         }
247     }
248 
249     /**
250      * Starts listening for wifiAware state change related broadcasts.
251      *
252      * @param callbackId the callback id
253      */
254     @AsyncRpc(description = "Start listening for wifiAware state change related broadcasts.")
wifiAwareMonitorStateChange(String callbackId)255     public void wifiAwareMonitorStateChange(String callbackId) {
256         stateChangedReceiver = new WifiAwareStateChangedReceiver(eventCache, callbackId);
257         IntentFilter filter = new IntentFilter(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED);
258         mContext.registerReceiver(stateChangedReceiver, filter);
259     }
260 
261     /**
262      * Stops listening for wifiAware state change related broadcasts.
263      */
264     @Rpc(description = "Stop listening for wifiAware state change related broadcasts.")
wifiAwareMonitorStopStateChange()265     public void wifiAwareMonitorStopStateChange() {
266         if (stateChangedReceiver != null) {
267             mContext.unregisterReceiver(stateChangedReceiver);
268             stateChangedReceiver = null;
269         }
270     }
271 
272     class WifiAwareStateChangedReceiver extends BroadcastReceiver {
273         private final EventCache eventCache;
274         private final String callbackId;
275 
WifiAwareStateChangedReceiver(EventCache eventCache, String callbackId)276         public WifiAwareStateChangedReceiver(EventCache eventCache, String callbackId) {
277             this.eventCache = eventCache;
278             this.callbackId = callbackId;
279         }
280 
281         @Override
onReceive(Context c, Intent intent)282         public void onReceive(Context c, Intent intent) {
283             boolean isAvailable = mWifiAwareManager.isAvailable();
284             SnippetEvent event = new SnippetEvent(callbackId,
285                     "WifiAwareState" + (isAvailable ? "Available" : "NotAvailable")
286             );
287             eventCache.postEvent(event);
288         }
289     }
290 
291     /**
292      * Use {@link WifiAwareSession#close()} to detach from the Wi-Fi Aware.
293      *
294      * @param sessionId The Id of the Aware attach session
295      */
296     @Rpc(description = "Detach from the Wi-Fi Aware service.")
wifiAwareDetach(String sessionId)297     public void wifiAwareDetach(String sessionId) {
298         WifiAwareSession session = mAttachSessions.remove(sessionId);
299         if (session != null) {
300             session.close();
301         }
302 
303     }
304 
305     /**
306      * Check if Wi-Fi Aware is attached.
307      *
308      * @param sessionId The Id of the Aware attached event callback id
309      */
310     @Rpc(description = "Check if Wi-Fi aware is attached")
wifiAwareIsSessionAttached(String sessionId)311     public boolean wifiAwareIsSessionAttached(String sessionId) {
312         return !mAttachSessions.isEmpty() && mAttachSessions.containsKey(sessionId);
313     }
314 
315     /**
316      * Check if Wi-Fi Aware is  pairing supported.
317      */
318     @Rpc(description = "Check if Wi-Fi aware pairing is available")
wifiAwareIsAwarePairingSupported()319     public Boolean wifiAwareIsAwarePairingSupported() throws WifiAwareManagerSnippetException {
320         checkWifiAwareManager();
321         Characteristics characteristics = mWifiAwareManager.getCharacteristics();
322         if (characteristics == null) {
323             throw new WifiAwareManagerSnippetException(
324                     "Can not get Wi-Fi Aware characteristics. Possible reasons include: 1. The "
325                             + "Wi-Fi Aware service is not initialized. Please call "
326                             + "attachWifiAware first. 2. The device does not support Wi-Fi Aware."
327                             + " Check the device's hardware and driver Wi-Fi Aware support.");
328 
329         }
330         return characteristics.isAwarePairingSupported();
331     }
332 
333 
334     /**
335      * Check if Wi-Fi Aware services is available.
336      */
checkWifiAwareManager()337     private void checkWifiAwareManager() throws WifiAwareManagerSnippetException {
338         if (mWifiAwareManager == null) {
339             throw new WifiAwareManagerSnippetException("Device does not support Wi-Fi Aware.");
340         }
341     }
342 
343     /**
344      * Checks if Wi-Fi RTT Manager has been set.
345      */
checkWifiRttManager()346     private void checkWifiRttManager() throws WifiAwareManagerSnippetException {
347         if (mWifiRttManager == null) {
348             throw new WifiAwareManagerSnippetException("Device does not support Wi-Fi Rtt.");
349         }
350     }
351 
352     /**
353      * Checks if Wi-Fi RTT is available.
354      */
355     @Rpc(description = "Check if Wi-Fi Aware is available")
wifiRttIsAvailable()356     public Boolean wifiRttIsAvailable() {
357         return mWifiRttManager.isAvailable();
358     }
checkWifiRttAvailable()359     private void checkWifiRttAvailable() throws WifiAwareManagerSnippetException {
360         if (!mWifiRttManager.isAvailable()) {
361             throw new WifiAwareManagerSnippetException("WiFi RTT is not available now.");
362         }
363     }
364 
365     /**
366      * Check if Wi-Fi Aware is available.
367      */
368     @Rpc(description = "Check if Wi-Fi Aware is available")
wifiAwareIsAvailable()369     public Boolean wifiAwareIsAvailable() {
370         return mWifiAwareManager.isAvailable();
371     }
372 
373     /**
374      * Send callback event of current method
375      */
sendEvent(String callbackId, String methodName)376     private void sendEvent(String callbackId, String methodName) {
377         SnippetEvent event = new SnippetEvent(callbackId, methodName);
378         EventCache.getInstance().postEvent(event);
379     }
380 
381     class WifiAwareDiscoverySessionCallback extends DiscoverySessionCallback {
382 
383         String mCallBackId = "";
384 
WifiAwareDiscoverySessionCallback(String callBackId)385         WifiAwareDiscoverySessionCallback(String callBackId) {
386             this.mCallBackId = callBackId;
387         }
388 
putMatchFilterData(List<byte[]> matchFilter, SnippetEvent event)389         private void putMatchFilterData(List<byte[]> matchFilter, SnippetEvent event) {
390             Bundle[] matchFilterBundle = new Bundle[matchFilter.size()];
391             int index = 0;
392             for (byte[] filter : matchFilter) {
393                 Bundle bundle = new Bundle();
394                 bundle.putByteArray("value", filter);
395                 matchFilterBundle[index] = bundle;
396                 index++;
397             }
398             event.getData().putParcelableArray("matchFilter", matchFilterBundle);
399         }
400 
401         @Override
onPublishStarted(PublishDiscoverySession session)402         public void onPublishStarted(PublishDiscoverySession session) {
403             mDiscoverySessions.put(mCallBackId, session);
404             SnippetEvent snippetEvent = new SnippetEvent(mCallBackId, "discoveryResult");
405             snippetEvent.getData().putString("callbackName", "onPublishStarted");
406             snippetEvent.getData().putBoolean("isSessionInitialized", session != null);
407             EventCache.getInstance().postEvent(snippetEvent);
408         }
409 
410         @Override
onSubscribeStarted(SubscribeDiscoverySession session)411         public void onSubscribeStarted(SubscribeDiscoverySession session) {
412             mDiscoverySessions.put(mCallBackId, session);
413             SnippetEvent snippetEvent = new SnippetEvent(mCallBackId, "discoveryResult");
414             snippetEvent.getData().putString("callbackName", "onSubscribeStarted");
415             snippetEvent.getData().putBoolean("isSessionInitialized", session != null);
416             EventCache.getInstance().postEvent(snippetEvent);
417         }
418 
419         @Override
onSessionConfigUpdated()420         public void onSessionConfigUpdated() {
421             sendEvent(mCallBackId, "onSessionConfigUpdated");
422         }
423 
424         @Override
onSessionConfigFailed()425         public void onSessionConfigFailed() {
426             sendEvent(mCallBackId, "onSessionConfigFailed");
427         }
428 
429         @Override
onSessionTerminated()430         public void onSessionTerminated() {
431             sendEvent(mCallBackId, "onSessionTerminated");
432         }
433 
434         @Override
onServiceDiscovered(ServiceDiscoveryInfo info)435         public void onServiceDiscovered(ServiceDiscoveryInfo info) {
436             mPeerHandles.put(info.getPeerHandle().hashCode(), info.getPeerHandle());
437             SnippetEvent event = new SnippetEvent(mCallBackId, "onServiceDiscovered");
438             event.getData().putByteArray("serviceSpecificInfo", info.getServiceSpecificInfo());
439             event.getData().putString("pairedAlias", info.getPairedAlias());
440             event.getData().putInt("peerId", info.getPeerHandle().hashCode());
441             List<byte[]> matchFilter = info.getMatchFilters();
442             putMatchFilterData(matchFilter, event);
443             EventCache.getInstance().postEvent(event);
444         }
445 
446         @Override
onServiceDiscoveredWithinRange( PeerHandle peerHandle, byte[] serviceSpecificInfo, List<byte[]> matchFilter, int distanceMm )447         public void onServiceDiscoveredWithinRange(
448                 PeerHandle peerHandle, byte[] serviceSpecificInfo, List<byte[]> matchFilter,
449                 int distanceMm
450         ) {
451             mPeerHandles.put(peerHandle.hashCode(), peerHandle);
452             SnippetEvent event = new SnippetEvent(mCallBackId, "onServiceDiscoveredWithinRange");
453             event.getData().putByteArray("serviceSpecificInfo", serviceSpecificInfo);
454             event.getData().putInt("distanceMm", distanceMm);
455             event.getData().putInt("peerId", peerHandle.hashCode());
456             putMatchFilterData(matchFilter, event);
457             EventCache.getInstance().postEvent(event);
458         }
459 
460         @Override
onMessageSendSucceeded(int messageId)461         public void onMessageSendSucceeded(int messageId) {
462             SnippetEvent event = new SnippetEvent(mCallBackId, "messageSendResult");
463             event.getData().putString("callbackName", "onMessageSendSucceeded");
464             event.getData().putInt("messageId", messageId);
465             EventCache.getInstance().postEvent(event);
466         }
467 
468         @Override
onMessageSendFailed(int messageId)469         public void onMessageSendFailed(int messageId) {
470             SnippetEvent event = new SnippetEvent(mCallBackId, "messageSendResult");
471             event.getData().putString("callbackName", "onMessageSendFailed");
472             event.getData().putInt("messageId", messageId);
473             EventCache.getInstance().postEvent(event);
474         }
475 
476         @Override
onMessageReceived(PeerHandle peerHandle, byte[] message)477         public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
478             mPeerHandles.put(peerHandle.hashCode(), peerHandle);
479             SnippetEvent event = new SnippetEvent(mCallBackId, "onMessageReceived");
480             event.getData().putByteArray("receivedMessage", message);
481             event.getData().putInt("peerId", peerHandle.hashCode());
482             EventCache.getInstance().postEvent(event);
483         }
484 
485         @Override
onPairingSetupRequestReceived(PeerHandle peerHandle, int requestId)486         public void onPairingSetupRequestReceived(PeerHandle peerHandle, int requestId) {
487             SnippetEvent event = new SnippetEvent(mCallBackId, "onPairingSetupRequestReceived");
488             event.getData().putInt("pairingRequestId", requestId);
489             event.getData().putInt("peerId", peerHandle.hashCode());
490             EventCache.getInstance().postEvent(event);
491         }
492 
493         @Override
onPairingSetupSucceeded(PeerHandle peerHandle, String alias)494         public void onPairingSetupSucceeded(PeerHandle peerHandle, String alias) {
495             SnippetEvent event = new SnippetEvent(mCallBackId, "onPairingSetupSucceeded");
496             event.getData().putString("pairedAlias", alias);
497             event.getData().putInt("peerId", peerHandle.hashCode());
498             EventCache.getInstance().postEvent(event);
499         }
500 
501         @Override
onPairingSetupFailed(PeerHandle peerHandle)502         public void onPairingSetupFailed(PeerHandle peerHandle) {
503             SnippetEvent event = new SnippetEvent(mCallBackId, "onPairingSetupFailed");
504             event.getData().putInt("peerId", peerHandle.hashCode());
505             EventCache.getInstance().postEvent(event);
506         }
507 
508         @Override
onPairingVerificationSucceed( @onNull PeerHandle peerHandle, @NonNull String alias )509         public void onPairingVerificationSucceed(
510                 @NonNull PeerHandle peerHandle, @NonNull String alias
511         ) {
512             super.onPairingVerificationSucceed(peerHandle, alias);
513             SnippetEvent event = new SnippetEvent(mCallBackId, "onPairingVerificationSucceed");
514             event.getData().putString("pairedAlias", alias);
515             event.getData().putInt("peerId", peerHandle.hashCode());
516             EventCache.getInstance().postEvent(event);
517         }
518 
519         @Override
onPairingVerificationFailed(PeerHandle peerHandle)520         public void onPairingVerificationFailed(PeerHandle peerHandle) {
521             SnippetEvent event = new SnippetEvent(mCallBackId, "onPairingVerificationFailed");
522             event.getData().putInt("peerId", peerHandle.hashCode());
523             EventCache.getInstance().postEvent(event);
524         }
525 
526         @Override
onBootstrappingSucceeded(PeerHandle peerHandle, int method)527         public void onBootstrappingSucceeded(PeerHandle peerHandle, int method) {
528             SnippetEvent event = new SnippetEvent(mCallBackId, "onBootstrappingSucceeded");
529             event.getData().putInt("bootstrappingMethod", method);
530             event.getData().putInt("peerId", peerHandle.hashCode());
531             EventCache.getInstance().postEvent(event);
532         }
533 
534         @Override
onBootstrappingFailed(PeerHandle peerHandle)535         public void onBootstrappingFailed(PeerHandle peerHandle) {
536             SnippetEvent event = new SnippetEvent(mCallBackId, "onBootstrappingFailed");
537             event.getData().putInt("peerId", peerHandle.hashCode());
538             EventCache.getInstance().postEvent(event);
539         }
540 
541         @Override
onServiceLost(PeerHandle peerHandle, int reason)542         public void onServiceLost(PeerHandle peerHandle, int reason) {
543             SnippetEvent event = new SnippetEvent(mCallBackId, "WifiAwareSessionOnServiceLost");
544             event.getData().putString("discoverySessionId", mCallBackId);
545             event.getData().putInt("peerId", peerHandle.hashCode());
546             event.getData().putInt("lostReason", reason);
547             EventCache.getInstance().postEvent(event);
548         }
549     }
550 
getWifiAwareSession(String sessionId)551     private WifiAwareSession getWifiAwareSession(String sessionId)
552             throws WifiAwareManagerSnippetException {
553         WifiAwareSession session = mAttachSessions.get(sessionId);
554         if (session == null) {
555             throw new WifiAwareManagerSnippetException(
556                     "Wi-Fi Aware session is not attached. Please call wifiAwareAttach first.");
557         }
558         return session;
559     }
560 
561 
562     /**
563      * Creates a new Aware subscribe discovery session. For Android T and later, this method
564      * requires NEARBY_WIFI_DEVICES permission and user permission flag "neverForLocation". For
565      * earlier versions, this method requires NEARBY_WIFI_DEVICES and ACCESS_FINE_LOCATION
566      * permissions.
567      *
568      * @param sessionId       The Id of the Aware attach session, should be the callbackId from
569      *                        {@link #wifiAwareAttach(String)}
570      * @param callbackId      Assigned automatically by mobly. Also will be used as discovery
571      *                        session id for further operations
572      * @param subscribeConfig Defines the subscription configuration via WifiAwareJsonDeserializer.
573      */
574     @AsyncRpc(
575             description = "Create a Wi-Fi Aware subscribe discovery session and handle callbacks."
576     )
wifiAwareSubscribe( String callbackId, String sessionId, SubscribeConfig subscribeConfig )577     public void wifiAwareSubscribe(
578             String callbackId, String sessionId, SubscribeConfig subscribeConfig
579     ) throws JSONException, WifiAwareManagerSnippetException {
580         WifiAwareSession session = getWifiAwareSession(sessionId);
581         Log.v("Creating a new Aware subscribe session with config: " + subscribeConfig.toString());
582         WifiAwareDiscoverySessionCallback myDiscoverySessionCallback =
583                 new WifiAwareDiscoverySessionCallback(callbackId);
584         session.subscribe(subscribeConfig, myDiscoverySessionCallback, mHandler);
585     }
586 
587     /**
588      * Creates a new Aware publish discovery session. Requires NEARBY_WIFI_DEVICES (with
589      * neverForLocation) or ACCESS_FINE_LOCATION for Android TIRAMISU+. ACCESS_FINE_LOCATION is
590      * required for earlier versions.
591      *
592      * @param sessionId     The Id of the Aware attach session, should be the callbackId from
593      *                      {@link #wifiAwareAttach(String)}
594      * @param callbackId    Assigned automatically by mobly. Also will be used as discovery session
595      *                      id for further operations
596      * @param publishConfig Defines the publish configuration via WifiAwareJsonDeserializer.
597      */
598     @AsyncRpc(description = "Create a Wi-Fi Aware publish discovery session and handle callbacks.")
wifiAwarePublish(String callbackId, String sessionId, PublishConfig publishConfig)599     public void wifiAwarePublish(String callbackId, String sessionId, PublishConfig publishConfig)
600             throws JSONException, WifiAwareManagerSnippetException {
601         WifiAwareSession session = getWifiAwareSession(sessionId);
602         Log.v("Creating a new Aware publish session with config: " + publishConfig.toString());
603         WifiAwareDiscoverySessionCallback myDiscoverySessionCallback =
604                 new WifiAwareDiscoverySessionCallback(callbackId);
605         session.publish(publishConfig, myDiscoverySessionCallback, mHandler);
606     }
607 
getPeerHandler(int peerId)608     private PeerHandle getPeerHandler(int peerId) throws WifiAwareManagerSnippetException {
609         PeerHandle handle = mPeerHandles.get(peerId);
610         if (handle == null) {
611             throw new WifiAwareManagerSnippetException(
612                     "GetPeerHandler failed. Please call publish or subscribe method, error "
613                             + "peerId: " + peerId + ", mPeerHandles: " + mPeerHandles);
614         }
615         return handle;
616     }
617 
getDiscoverySession(String discoverySessionId)618     private DiscoverySession getDiscoverySession(String discoverySessionId)
619             throws WifiAwareManagerSnippetException {
620         DiscoverySession session = mDiscoverySessions.get(discoverySessionId);
621         if (session == null) {
622             throw new WifiAwareManagerSnippetException(
623                     "GetDiscoverySession failed. Please call publish or subscribe method, "
624                             + "error discoverySessionId: " + discoverySessionId
625                             + ", mDiscoverySessions: " + mDiscoverySessions);
626         }
627         return session;
628 
629     }
630 
631     /**
632      * Sends a message to a peer using Wi-Fi Aware.
633      *
634      * <p>This method sends a specified message to a peer device identified by a peer handle
635      * in an ongoing Wi-Fi Aware discovery session. The message is sent asynchronously, and the
636      * method waits for the send status to confirm whether the message was successfully sent or if
637      * any errors occurred.</p>
638      *
639      * <p>Before sending the message, this method checks if there is an active discovery
640      * session. If there is no active session, it throws a
641      * {@link WifiAwareManagerSnippetException}.</p>
642      *
643      * @param discoverySessionId The Id of the discovery session, should be the callbackId from
644      *                           publish/subscribe action
645      * @param peerId             identifier for the peer handle
646      * @param messageId          an integer representing the message ID, which is used to track the
647      *                           message.
648      * @param message            a {@link String} containing the message to be sent.
649      * @throws WifiAwareManagerSnippetException if there is no active discovery session or if
650      *                                          sending the message fails.
651      * @see android.net.wifi.aware.DiscoverySession#sendMessage
652      * @see android.net.wifi.aware.PeerHandle
653      * @see java.nio.charset.StandardCharsets#UTF_8
654      */
655     @Rpc(description = "Send a message to a peer using Wi-Fi Aware.")
wifiAwareSendMessage( String discoverySessionId, int peerId, int messageId, String message )656     public void wifiAwareSendMessage(
657             String discoverySessionId, int peerId, int messageId, String message
658     ) throws WifiAwareManagerSnippetException {
659         // 4. send message & wait for send status
660         DiscoverySession session = getDiscoverySession(discoverySessionId);
661         PeerHandle handle = getPeerHandler(peerId);
662         session.sendMessage(handle, messageId, message.getBytes(StandardCharsets.UTF_8));
663     }
664 
665     /**
666      * Closes the current Wi-Fi Aware discovery session if it is active.
667      *
668      * <p>This method checks if there is an active discovery session. If so,
669      * it closes the session and sets the session object to null. This ensures that resources are
670      * properly released and the session is cleanly terminated.</p>
671      *
672      * @param discoverySessionId The Id of the discovery session
673      */
674     @Rpc(description = "Close the current Wi-Fi Aware discovery session.")
wifiAwareCloseDiscoverSession(String discoverySessionId)675     public void wifiAwareCloseDiscoverSession(String discoverySessionId) {
676         DiscoverySession session = mDiscoverySessions.remove(discoverySessionId);
677         if (session != null) {
678             session.close();
679         }
680     }
681 
682     /**
683      * Closes all Wi-Fi Aware session if it is active. And clear all cache sessions
684      */
685     @Rpc(description = "Close the current Wi-Fi Aware session.")
wifiAwareCloseAllWifiAwareSession()686     public void wifiAwareCloseAllWifiAwareSession() {
687         for (WifiAwareSession session : mAttachSessions.values()) {
688             session.close();
689         }
690         mAttachSessions.clear();
691         mDiscoverySessions.clear();
692         mPeerHandles.clear();
693     }
694 
695     /**
696      * Creates a Wi-Fi Aware network specifier for requesting network through connectivityManager.
697      *
698      * @param discoverySessionId The Id of the discovery session,
699      * @param peerId             The Id of the peer handle
700      * @param isAcceptAnyPeer    A boolean value indicating whether the network specifier should
701      * @return a {@link String} containing the network specifier encoded as a Base64 string.
702      * @throws JSONException                    if there is an error parsing the JSON object.
703      * @throws WifiAwareManagerSnippetException if there is an error creating the network
704      *                                          specifier.
705      */
706     @Rpc(
707             description = "Create a network specifier to be used when specifying a Aware network "
708                     + "request"
709     )
wifiAwareCreateNetworkSpecifier( String discoverySessionId, Integer peerId, boolean isAcceptAnyPeer, @RpcOptional JSONObject jsonObject )710     public String wifiAwareCreateNetworkSpecifier(
711             String discoverySessionId, Integer peerId, boolean isAcceptAnyPeer,
712             @RpcOptional JSONObject jsonObject
713     ) throws JSONException, WifiAwareManagerSnippetException {
714         DiscoverySession session = getDiscoverySession(discoverySessionId);
715         PeerHandle handle = null;
716         if (peerId != null && peerId > 0){
717             handle = getPeerHandler(peerId);
718         }
719         WifiAwareNetworkSpecifier.Builder builder;
720         if (isAcceptAnyPeer) {
721             builder = new WifiAwareNetworkSpecifier.Builder((PublishDiscoverySession) session);
722         } else {
723             builder = new WifiAwareNetworkSpecifier.Builder(session, handle);
724         }
725         WifiAwareNetworkSpecifier specifier =
726                 WifiAwareJsonDeserializer.jsonToNetworkSpecifier(jsonObject, builder);
727         return SerializationUtil.parcelableToString(specifier);
728     }
729 
730     /**
731      * Creates a oob NetworkSpecifier for requesting a Wi-Fi Aware network via ConnectivityManager.
732      *
733      * @param sessionId The Id of the AwareSession session,
734      * @param role             The role of this device: AwareDatapath Role.
735      * @param macAddress    The MAC address of the peer's Aware discovery interface.
736      * @return A {@link NetworkSpecifier}  to be used to construct
737      * @throws WifiAwareManagerSnippetException if there is an error creating the network
738      *                                          specifier.
739      */
740     @Rpc(
741             description = "Create a oob network specifier to be used when specifying a Aware "
742                     + "network request"
743     )
createNetworkSpecifierOob(String sessionId, int role, String macAddress, String passphrase, String pmk)744     public NetworkSpecifier createNetworkSpecifierOob(String sessionId, int role, String macAddress,
745         String passphrase, String pmk)
746             throws WifiAwareManagerSnippetException {
747             WifiAwareSession session = getWifiAwareSession(sessionId);
748              NetworkSpecifier specifier = null;
749             byte[] peermac = null;
750             byte[] pmkDecoded = null;
751             if (!TextUtils.isEmpty(pmk)){
752                 pmkDecoded = Base64.decode(pmk, Base64.DEFAULT);
753             }
754             if (macAddress != null) {
755                 peermac = MacAddress.fromString(macAddress).toByteArray();
756             }
757             if (passphrase != null && !passphrase.isEmpty()) {
758                 specifier = session.createNetworkSpecifierPassphrase(role, peermac, passphrase);
759             }
760             else if (pmk != null) {
761                 specifier = session.createNetworkSpecifierPmk(role, peermac, pmkDecoded);
762             }
763             else if (peermac != null){
764                 specifier = session.createNetworkSpecifierOpen(role, peermac);
765             } else {
766             throw new WifiAwareManagerSnippetException(
767                 "At least one of passphrase, or macAddress must be provided.");
768             }
769             return specifier;
770     }
771 
772     @Override
shutdown()773     public void shutdown() throws Exception {
774         wifiAwareCloseAllWifiAwareSession();
775     }
776 
777     /**
778      * Returns the characteristics of the WiFi Aware interface.
779      *
780      * @return WiFi Aware characteristics
781      */
782     @Rpc(description = "Get the characteristics of the WiFi Aware interface.")
getCharacteristics()783     public Characteristics getCharacteristics() {
784         return mWifiAwareManager.getCharacteristics();
785     }
786 
787     /**
788      * Creates a wifiAwareUpdatePublish discovery session. Requires NEARBY_WIFI_DEVICES (with
789      * neverForLocation) or ACCESS_FINE_LOCATION for Android TIRAMISU+. ACCESS_FINE_LOCATION is
790      * required for earlier versions.
791      *
792      * @param sessionId     The Id of the Aware attach session, should be the callbackId from
793      *                      {@link #wifiAwareAttach(String)}
794      * @param publishConfig Defines the publish configuration via WifiAwareJsonDeserializer.
795      */
796     @Rpc(description = "Create a wifiAwareUpdatePublish discovery session and handle callbacks.")
wifiAwareUpdatePublish(String sessionId, PublishConfig publishConfig)797     public void wifiAwareUpdatePublish(String sessionId, PublishConfig publishConfig)
798             throws JSONException, WifiAwareManagerSnippetException, IllegalArgumentException {
799         DiscoverySession session = getDiscoverySession(sessionId);
800         if (session == null) {
801             throw new IllegalStateException(
802                     "Calling wifiAwareUpdatePublish before session (session ID " + sessionId
803                             + ") is ready");
804         }
805         if (!(session instanceof PublishDiscoverySession)) {
806             throw new IllegalArgumentException(
807                     "Calling wifiAwareUpdatePublish with a subscribe session ID");
808         }
809         Log.v("Updating a  Aware publish session with config: " + publishConfig.toString());
810 
811         ((PublishDiscoverySession) session).updatePublish(publishConfig);
812     }
813 
814     /**
815      * Creates a wifiAwareUpdateSubscribe discovery session. For Android T and later, this method
816      * requires NEARBY_WIFI_DEVICES permission and user permission flag "neverForLocation". For
817      * earlier versions, this method requires NEARBY_WIFI_DEVICES and ACCESS_FINE_LOCATION
818      * permissions.
819      *
820      * @param sessionId       The Id of the Aware attach session, should be the callbackId from
821      *                        {@link #wifiAwareAttach(String)}
822      * @param subscribeConfig Defines the subscription configuration via WifiAwareJsonDeserializer.
823      */
824     @Rpc(description = "Create a wifiAwareUpdateSubscribe discovery session and handle callbacks.")
wifiAwareUpdateSubscribe( String sessionId, SubscribeConfig subscribeConfig )825     public void wifiAwareUpdateSubscribe(
826             String sessionId, SubscribeConfig subscribeConfig
827     ) throws JSONException, WifiAwareManagerSnippetException {
828         DiscoverySession session = getDiscoverySession(sessionId);
829         if (session == null) {
830             throw new IllegalStateException(
831                     "Calling wifiAwareUpdateSubscribe before session (session ID " + sessionId
832                             + ") is ready");
833         }
834         if (!(session instanceof SubscribeDiscoverySession)) {
835             throw new IllegalArgumentException(
836                     "Calling wifiAwareUpdateSubscribe with a publish session ID");
837         }
838         Log.v("Creating a wifiAwareUpdateSubscribe session with config: "
839                 + subscribeConfig.toString());
840         ((SubscribeDiscoverySession) session).updateSubscribe(subscribeConfig);
841 
842     }
843 
844     /**
845      * Converts a JSON representation of a ScanResult to an actual ScanResult object. Mirror of
846      * the code in
847      * {@link com.googlecode.android_scripting.jsonrpc.JsonBuilder#buildJsonScanResult(ScanResult)}.
848      *
849      * @param j JSON object representing a ScanResult.
850      * @return a ScanResult object
851      * @throws JSONException on any JSON errors
852      */
getScanResult(JSONObject j)853     public static ScanResult getScanResult(JSONObject j) throws JSONException {
854         if (j == null) {
855             return null;
856         }
857 
858         ScanResult scanResult = new ScanResult();
859 
860         if (j.has("BSSID")) {
861             scanResult.BSSID = j.getString("BSSID");
862         }
863         if (j.has("SSID")) {
864             scanResult.SSID = j.getString("SSID");
865         }
866         if (j.has("frequency")) {
867             scanResult.frequency = j.getInt("frequency");
868         }
869         if (j.has("level")) {
870             scanResult.level = j.getInt("level");
871         }
872         if (j.has("capabilities")) {
873             scanResult.capabilities = j.getString("capabilities");
874         }
875         if (j.has("timestamp")) {
876             scanResult.timestamp = j.getLong("timestamp");
877         }
878         if (j.has("centerFreq0")) {
879             scanResult.centerFreq0 = j.getInt("centerFreq0");
880         }
881         if (j.has("centerFreq1")) {
882             scanResult.centerFreq1 = j.getInt("centerFreq1");
883         }
884         if (j.has("channelWidth")) {
885             scanResult.channelWidth = j.getInt("channelWidth");
886         }
887         if (j.has("operatorFriendlyName")) {
888             scanResult.operatorFriendlyName = j.getString("operatorFriendlyName");
889         }
890         if (j.has("venueName")) {
891             scanResult.venueName = j.getString("venueName");
892         }
893 
894         return scanResult;
895     }
896 
897     /**
898      * Converts a JSONArray toa a list of ScanResult.
899      *
900      * @param j JSONArray representing a collection of ScanResult objects
901      * @return a list of ScanResult objects
902      * @throws JSONException on any JSON error
903      */
getScanResults(JSONArray j)904     public static List<ScanResult> getScanResults(JSONArray j) throws JSONException {
905         if (j == null || j.length() == 0) {
906             return null;
907         }
908 
909         ArrayList<ScanResult> scanResults = new ArrayList<>(j.length());
910         for (int i = 0; i < j.length(); ++i) {
911             scanResults.add(getScanResult(j.getJSONObject(i)));
912         }
913 
914         return scanResults;
915     }
916 
917     /**
918      * Starts Wi-Fi RTT ranging with Wi-Fi Aware access points.
919      *
920      * @param callbackId        Assigned automatically by mobly for all async RPCs.
921      * @param requestJsonObject The ranging request in JSONArray type for calling {@link
922      *                          android.net.wifi.ScanResult}.
923      */
924     @AsyncRpc(description = "Start ranging to an Access Points.")
wifiRttStartRangingToAccessPoints( String callbackId, JSONArray requestJsonObject )925     public void wifiRttStartRangingToAccessPoints(
926             String callbackId, JSONArray requestJsonObject
927     ) throws JSONException, WifiAwareManagerSnippetException {
928         Log.v("wifiRttStartRangingToAccessPoints: " + requestJsonObject);
929         RangingRequest request = new RangingRequest.Builder().addAccessPoints(
930                             getScanResults(requestJsonObject)).build();
931         Log.v("Starting Wi-Fi RTT ranging with access point: " + request.toString());
932         RangingCallback rangingCb = new RangingCallback(eventCache, callbackId);
933         mWifiRttManager.startRanging(request, command -> mHandler.post(command), rangingCb);
934     }
935 
936     /**
937      * Starts Wi-Fi RTT ranging with Wi-Fi Aware peers.
938      *
939      * @param callbackId        Assigned automatically by mobly for all async RPCs.
940      * @param requestJsonObject The ranging request in JSONObject type for calling {@link
941      *                          android.net.wifi.rtt.WifiRttManager#startRanging startRanging}.
942      */
943     @AsyncRpc(description = "Start Wi-Fi RTT ranging with Wi-Fi Aware peers.")
wifiAwareStartRanging( String callbackId, JSONObject requestJsonObject )944     public void wifiAwareStartRanging(
945             String callbackId, JSONObject requestJsonObject
946     ) throws JSONException, WifiAwareManagerSnippetException {
947         checkWifiRttManager();
948         checkWifiRttAvailable();
949         RangingRequest request = WifiAwareJsonDeserializer.jsonToRangingRequest(
950                 requestJsonObject, mPeerHandles);
951         Log.v("Starting Wi-Fi RTT ranging with config: " + request.toString());
952         RangingCallback rangingCb = new RangingCallback(eventCache, callbackId);
953         mWifiRttManager.startRanging(request, command -> mHandler.post(command), rangingCb);
954     }
955 
956     /**
957      * Ranging result callback class.
958      */
959     private static class RangingCallback extends RangingResultCallback {
960         private static final String EVENT_NAME_RANGING_RESULT = "WifiRttRangingOnRangingResult";
961         private final EventCache mEventCache;
962         private final String mCallbackId;
963 
RangingCallback(EventCache eventCache, String callbackId)964         RangingCallback(EventCache eventCache, String callbackId) {
965             this.mEventCache = eventCache;
966             this.mCallbackId = callbackId;
967         }
968 
969         @Override
onRangingFailure(int code)970         public void onRangingFailure(int code) {
971             SnippetEvent event = new SnippetEvent(mCallbackId, EVENT_NAME_RANGING_RESULT);
972             event.getData().putString("callbackName", "onRangingFailure");
973             event.getData().putInt("status", code);
974             mEventCache.postEvent(event);
975         }
976 
977         @Override
onRangingResults(List<RangingResult> results)978         public void onRangingResults(List<RangingResult> results) {
979             SnippetEvent event = new SnippetEvent(mCallbackId, EVENT_NAME_RANGING_RESULT);
980             event.getData().putString("callbackName", "onRangingResults");
981 
982             Bundle[] resultBundles = new Bundle[results.size()];
983             for (int i = 0; i < results.size(); i++) {
984                 RangingResult result = results.get(i);
985                 resultBundles[i] = new Bundle();
986                 resultBundles[i].putInt("status", result.getStatus());
987                 if (result.getStatus() == RangingResult.STATUS_SUCCESS) {
988                     resultBundles[i].putInt("distanceMm", result.getDistanceMm());
989                     resultBundles[i].putInt("rssi", result.getRssi());
990                     resultBundles[i].putInt("distanceStdDevMm", result.getDistanceStdDevMm());
991                     resultBundles[i].putInt(
992                         "numAttemptedMeasurements",
993                         result.getNumAttemptedMeasurements());
994                     resultBundles[i].putInt(
995                         "numSuccessfulMeasurements",
996                         result.getNumSuccessfulMeasurements());
997                     resultBundles[i].putByteArray("lci", result.getLci());
998                     resultBundles[i].putByteArray("lcr", result.getLcr());
999                     resultBundles[i].putLong("timestamp", result.getRangingTimestampMillis());
1000                 }
1001                 PeerHandle peer = result.getPeerHandle();
1002                 if (peer != null) {
1003                     resultBundles[i].putInt("peerId", peer.hashCode());
1004                 } else {
1005                     resultBundles[i].putBundle("peerId", null);
1006                 }
1007                 MacAddress mac = result.getMacAddress();
1008                 resultBundles[i].putString("mac", mac != null ? mac.toString() : null);
1009                 resultBundles[i].putString("macAsString",
1010                     mac != null ? result.getMacAddress().toString() : null);
1011             }
1012             event.getData().putParcelableArray("results", resultBundles);
1013             mEventCache.postEvent(event);
1014         }
1015     }
1016 
1017     /**
1018      * Return whether this device supports setting a channel requirement in a data-path request.
1019      */
1020     @Rpc(
1021             description = "Return whether this device supports setting a channel requirement in a "
1022                 + "data-path request."
1023     )
wifiAwareIsSetChannelOnDataPathSupported()1024     public boolean wifiAwareIsSetChannelOnDataPathSupported() {
1025         return mWifiAwareManager.isSetChannelOnDataPathSupported();
1026     }
1027 
1028 }
1029 
1030