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