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.android.server.wifi.usd; 18 19 import android.annotation.NonNull; 20 import android.annotation.SuppressLint; 21 import android.app.AlarmManager; 22 import android.net.MacAddress; 23 import android.net.wifi.IBooleanListener; 24 import android.net.wifi.usd.Characteristics; 25 import android.net.wifi.usd.Config; 26 import android.net.wifi.usd.IPublishSessionCallback; 27 import android.net.wifi.usd.ISubscribeSessionCallback; 28 import android.net.wifi.usd.PublishConfig; 29 import android.net.wifi.usd.PublishSession; 30 import android.net.wifi.usd.PublishSessionCallback; 31 import android.net.wifi.usd.SessionCallback; 32 import android.net.wifi.usd.SubscribeConfig; 33 import android.net.wifi.usd.SubscribeSession; 34 import android.net.wifi.usd.SubscribeSessionCallback; 35 import android.os.Bundle; 36 import android.os.IBinder; 37 import android.os.RemoteCallbackList; 38 import android.os.RemoteException; 39 import android.util.Log; 40 import android.util.SparseArray; 41 42 import com.android.server.wifi.ActiveModeWarden; 43 import com.android.server.wifi.Clock; 44 import com.android.server.wifi.SupplicantStaIfaceHal; 45 import com.android.server.wifi.WifiThreadRunner; 46 47 import java.util.ArrayList; 48 import java.util.HashMap; 49 import java.util.List; 50 import java.util.Objects; 51 import java.util.concurrent.Executor; 52 import java.util.function.Consumer; 53 54 import javax.annotation.concurrent.NotThreadSafe; 55 56 /** 57 * This class UsdRequestManager acts as central point for handling various USD requests from 58 * applications such as publish, subscribe, send message, etc. It sends the command to HAL to 59 * carry out these actions and expect for callbacks from HAL on various events such as susbcribe/ 60 * publish started, service discovered, received a message from the peer, etc. 61 * 62 * <p>Here is how it works, 63 * <ul> 64 * <li>Role: The UsdRequestManager can act as either a publisher or subscriber 65 * <li>Request handling: It manages incoming requests and ensures the new commands are not accepted 66 * while a previous subscribe or publish is still awaiting for the response from HAL. 67 * <li>Session Management: USD session are organized and tracked using unique session IDs. Each 68 * session maintains a collection of USD discovery results which are indexed by the USD peer. 69 * <li>USD Peer: A peer is created for discover and also created a unique id (hash) which maps to 70 * local session id, remote session id and remote mac address. Applications are given this unique 71 * id (hash) on various indications. 72 * 73 * <p>Essentially, this class streamlines USD communication by managing requests, organizing 74 * sessions, and maintaining information about discovered peers. It also enforces a sequential 75 * processing of requests to prevent conflicts and ensure reliable communication with HAL. 76 * </ul> 77 */ 78 @NotThreadSafe 79 @SuppressLint("NewApi") 80 public class UsdRequestManager { 81 public static final String TAG = "UsdRequestManager"; 82 private static final int DEFAULT_COMMAND_ID = 100; 83 private static final int USD_TEMP_SESSION_ID = 255; 84 private static final int INVALID_ID = -1; 85 private static final String USD_REQUEST_MANAGER_ALARM_TAG = "UsdRequestManagerAlarmTag"; 86 87 /** 88 * A unique peer hash (a unique peer id) generator. Application will get the peer hash as the 89 * identifier of the peer. Also peer hash is globally mapped to a peer (defined by ownId, 90 * peerId and peer mac address). 91 */ 92 private static int sNextPeerHash = 100; 93 private final UsdNativeManager mUsdNativeManager; 94 private final ActiveModeWarden mActiveModeWarden; 95 private SupplicantStaIfaceHal.UsdCapabilitiesInternal mUsdCapabilities; 96 private final WifiThreadRunner mWifiThreadRunner; 97 private final Clock mClock; 98 private enum Role { 99 NONE, PUBLISHER, SUBSCRIBER 100 } 101 private Role mRequesterRole; 102 private final AlarmManager mAlarmManager; 103 private final AlarmManager.OnAlarmListener mTimeoutListener = () -> { 104 startCleaningUpExpiredSessions(); 105 }; 106 private static final int TEMP_SESSION_TIMEOUT_MILLIS = 1000; 107 private static final int TTL_GAP_MILLIS = 1000; 108 109 private final RemoteCallbackList<IBooleanListener> mPublisherListenerList = 110 new RemoteCallbackList<IBooleanListener>(); 111 private final RemoteCallbackList<IBooleanListener> mSubscriberListenerList = 112 new RemoteCallbackList<IBooleanListener>(); 113 startCleaningUpExpiredSessions()114 private void startCleaningUpExpiredSessions() { 115 long current = mClock.getElapsedSinceBootMillis(); 116 long nextSchedule = Long.MAX_VALUE; 117 long age; 118 List<Integer> sessionsToDelete = new ArrayList<>(); 119 120 // Cleanup sessions which crossed the TTL. 121 for (int i = 0; i < mUsdSessions.size(); i++) { 122 UsdSession usdSession = mUsdSessions.valueAt(i); 123 int sessionId = mUsdSessions.keyAt(i); 124 int ttlMillis = TEMP_SESSION_TIMEOUT_MILLIS; 125 if (sessionId != USD_TEMP_SESSION_ID) { 126 ttlMillis = ((usdSession.getRole() == Role.PUBLISHER) 127 ? usdSession.mPublishConfig.getTtlSeconds() 128 : usdSession.mSubscribeConfig.getTtlSeconds()) * 1000 + TTL_GAP_MILLIS; 129 } 130 age = current - usdSession.mCreationTimeMillis; 131 if (age >= ttlMillis) { 132 sessionsToDelete.add(sessionId); 133 } else { 134 nextSchedule = Math.min(ttlMillis - age, nextSchedule); 135 } 136 } 137 138 for (int sessionId : sessionsToDelete) { 139 mUsdSessions.get(sessionId).sessionCleanup(); 140 mUsdSessions.remove(sessionId); 141 } 142 143 // Reschedule if necessary. 144 if (mUsdSessions.size() > 0 && nextSchedule < Long.MAX_VALUE) { 145 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, 146 mClock.getElapsedSinceBootMillis() + nextSchedule, 147 USD_REQUEST_MANAGER_ALARM_TAG, mTimeoutListener, 148 mWifiThreadRunner.getHandler()); 149 } 150 } 151 stopCleaningUpExpiredSessions()152 private void stopCleaningUpExpiredSessions() { 153 mAlarmManager.cancel(mTimeoutListener); 154 } 155 156 /** 157 * A class to represent USD peer. A combination of ownId, peerId and peerMacAddress define a 158 * unique peer. 159 */ 160 public static final class UsdPeer { 161 public final int ownId; 162 public final int peerId; 163 public final MacAddress peerMacAddress; 164 UsdPeer(int ownId, int peerId, MacAddress peerMacAddress)165 public UsdPeer(int ownId, int peerId, MacAddress peerMacAddress) { 166 this.ownId = ownId; 167 this.peerId = peerId; 168 this.peerMacAddress = peerMacAddress; 169 } 170 171 @Override equals(Object o)172 public boolean equals(Object o) { 173 if (this == o) return true; 174 if (!(o instanceof UsdPeer peer)) return false; 175 return ownId == peer.ownId && peerId == peer.peerId && peerMacAddress.equals( 176 peer.peerMacAddress); 177 } 178 179 @Override hashCode()180 public int hashCode() { 181 return Objects.hash(ownId, peerId, peerMacAddress); 182 } 183 } 184 185 /** 186 * A class representing USD session. 187 */ 188 private final class UsdSession implements IBinder.DeathRecipient { 189 private int mId = INVALID_ID; 190 private Role mSessionRole = Role.NONE; 191 private PublishConfig mPublishConfig; 192 private IPublishSessionCallback mIPublishSessionCallback; 193 private SubscribeConfig mSubscribeConfig; 194 private ISubscribeSessionCallback mISubscribeSessionCallback; 195 private final long mCreationTimeMillis; 196 /** 197 * Maps peer to peer hash (a unique identifier to the peer). 198 */ 199 private final HashMap<UsdPeer, Integer> mSessionPeers = new HashMap<>(); 200 201 /** 202 * Get Role of the session. See {@link Role} for different roles. 203 */ getRole()204 public Role getRole() { 205 return mSessionRole; 206 } 207 208 /** 209 * Set session id for this session. 210 */ setSessionId(int sessionId)211 public void setSessionId(int sessionId) { 212 mId = sessionId; 213 } 214 215 /** 216 * Adds a peer to the session if not already there. It creates a unique id (key) and add the 217 * peer to a map. 218 */ addPeerOnce(UsdPeer peer)219 public void addPeerOnce(UsdPeer peer) { 220 if (mSessionPeers.containsKey(peer)) return; 221 int peerHash = sNextPeerHash++; 222 mSessionPeers.put(peer, peerHash); 223 addPeerToGlobalMap(peerHash, peer); 224 } 225 226 /** 227 * Get unique hash (a unique id) for a peer. 228 */ getPeerHash(UsdPeer peer)229 public int getPeerHash(UsdPeer peer) { 230 return mSessionPeers.getOrDefault(peer, INVALID_ID); 231 } 232 233 /** 234 * Clear all peers for this session. 235 */ releasePeers()236 public void releasePeers() { 237 // Release all peers associated to this session from global map. 238 for (int peerHash : mSessionPeers.values()) { 239 removePeerFromGlobalMap(peerHash); 240 } 241 mSessionPeers.clear(); 242 } 243 244 /** 245 * A constructor for publisher session. 246 */ UsdSession(PublishConfig publishConfig, IPublishSessionCallback callback)247 UsdSession(PublishConfig publishConfig, IPublishSessionCallback callback) { 248 mSessionRole = Role.PUBLISHER; 249 mPublishConfig = publishConfig; 250 mIPublishSessionCallback = callback; 251 // Register the recipient for a notification if this binder goes away. 252 try { 253 callback.asBinder().linkToDeath(this, 0); 254 } catch (RemoteException e) { 255 Log.e(TAG, "UsdSession linkToDeath " + e); 256 } 257 mCreationTimeMillis = mClock.getElapsedSinceBootMillis(); 258 } 259 260 /** 261 * A constructor for subscriber session. 262 */ UsdSession(SubscribeConfig subscribeConfig, ISubscribeSessionCallback callback)263 UsdSession(SubscribeConfig subscribeConfig, ISubscribeSessionCallback callback) { 264 mSessionRole = Role.SUBSCRIBER; 265 mSubscribeConfig = subscribeConfig; 266 mISubscribeSessionCallback = callback; 267 // Register the recipient for a notification if this binder goes away. 268 try { 269 callback.asBinder().linkToDeath(this, 0); 270 } catch (RemoteException e) { 271 Log.e(TAG, "UsdSession linkToDeath " + e); 272 } 273 mCreationTimeMillis = mClock.getElapsedSinceBootMillis(); 274 } 275 276 @Override binderDied()277 public void binderDied() { 278 mWifiThreadRunner.post(() -> sessionCleanup()); 279 } 280 281 /** 282 * A sessionCleanup function for the USD session. 283 */ sessionCleanup()284 public void sessionCleanup() { 285 releasePeers(); 286 if (mSessionRole == Role.PUBLISHER) { 287 mIPublishSessionCallback.asBinder().unlinkToDeath(this, 0); 288 } else { 289 mISubscribeSessionCallback.asBinder().unlinkToDeath(this, 0); 290 } 291 if (isSingleSession()) { 292 mRequesterRole = Role.NONE; 293 stopCleaningUpExpiredSessions(); 294 // Once last session is cleaned up, broadcast subscriber/publisher status. 295 if (mSessionRole == Role.PUBLISHER) { 296 broadcastSubscriberStatus(); 297 } else { 298 broadcastPublisherStatus(); 299 } 300 } 301 mUsdSessions.remove(mId); 302 mSessionRole = Role.NONE; 303 } 304 } 305 306 /** 307 * A class for USD discovery info from HAL. 308 */ 309 public static final class UsdHalDiscoveryInfo { 310 public final int ownId; 311 public final int peerId; 312 public MacAddress peerMacAddress; 313 public final byte[] serviceSpecificInfo; 314 @Config.ServiceProtoType 315 public final int serviceProtoType; 316 public final boolean isFsdEnabled; 317 public final byte[] matchFilter; 318 UsdHalDiscoveryInfo(int ownId, int peerId, MacAddress peerMacAddress, byte[] serviceSpecificInfo, int serviceProtoType, boolean isFsdEnabled, byte[] matchFilter)319 public UsdHalDiscoveryInfo(int ownId, int peerId, MacAddress peerMacAddress, 320 byte[] serviceSpecificInfo, int serviceProtoType, boolean isFsdEnabled, 321 byte[] matchFilter) { 322 this.ownId = ownId; 323 this.peerId = peerId; 324 this.peerMacAddress = peerMacAddress; 325 this.serviceSpecificInfo = serviceSpecificInfo; 326 this.serviceProtoType = serviceProtoType; 327 this.isFsdEnabled = isFsdEnabled; 328 this.matchFilter = matchFilter; 329 } 330 } 331 332 private final SparseArray<UsdSession> mUsdSessions = new SparseArray<>(); 333 private final SparseArray<UsdPeer> mGlobalPeerMap = new SparseArray<>(); 334 isSingleSession()335 private boolean isSingleSession() { 336 return mUsdSessions.size() == 1; 337 } 338 339 /** 340 * Add peer to the global peer map. 341 */ addPeerToGlobalMap(int peerHash, UsdPeer peer)342 private void addPeerToGlobalMap(int peerHash, UsdPeer peer) { 343 mGlobalPeerMap.put(peerHash, peer); 344 } 345 346 /** 347 * Checks whether peer existing in the global peer map. 348 */ doesPeerExistInGlobalMap(int peerHash)349 private boolean doesPeerExistInGlobalMap(int peerHash) { 350 return mGlobalPeerMap.contains(peerHash); 351 } 352 353 /** 354 * Gets peer from the global peer map. Returns null if peer does not exist. 355 */ getPeerFromGlobalMap(int peerHash)356 private UsdPeer getPeerFromGlobalMap(int peerHash) { 357 return mGlobalPeerMap.get(peerHash); 358 } 359 360 /** 361 * Removes peer from global peer map. 362 */ removePeerFromGlobalMap(int peerHash)363 private void removePeerFromGlobalMap(int peerHash) { 364 mGlobalPeerMap.remove(peerHash); 365 } 366 367 /** 368 * Constructor. 369 */ UsdRequestManager(UsdNativeManager usdNativeManager, WifiThreadRunner wifiThreadRunner, ActiveModeWarden activeModeWarden, Clock clock, AlarmManager alarmManager)370 public UsdRequestManager(UsdNativeManager usdNativeManager, WifiThreadRunner wifiThreadRunner, 371 ActiveModeWarden activeModeWarden, Clock clock, AlarmManager alarmManager) { 372 mUsdNativeManager = usdNativeManager; 373 mActiveModeWarden = activeModeWarden; 374 mWifiThreadRunner = wifiThreadRunner; 375 registerUsdEventsCallback(new UsdNativeEventsCallback()); 376 mClock = clock; 377 mAlarmManager = alarmManager; 378 mRequesterRole = Role.NONE; 379 } 380 isUsdPublisherSupported()381 private boolean isUsdPublisherSupported() { 382 return mUsdCapabilities != null && mUsdCapabilities.isUsdPublisherSupported; 383 } 384 isUsdSubscriberSupported()385 private boolean isUsdSubscriberSupported() { 386 return mUsdCapabilities != null && mUsdCapabilities.isUsdSubscriberSupported; 387 } 388 389 /** 390 * Get USD characteristics. 391 */ getCharacteristics()392 public Characteristics getCharacteristics() { 393 Bundle bundle = new Bundle(); 394 if (mUsdCapabilities == null) { 395 mUsdCapabilities = mUsdNativeManager.getUsdCapabilities(); 396 } 397 if (mUsdCapabilities != null) { 398 bundle.putInt(Characteristics.KEY_MAX_NUM_SUBSCRIBE_SESSIONS, 399 mUsdCapabilities.maxNumSubscribeSessions); 400 bundle.putInt(Characteristics.KEY_MAX_NUM_PUBLISH_SESSIONS, 401 mUsdCapabilities.maxNumPublishSessions); 402 bundle.putInt(Characteristics.KEY_MAX_SERVICE_SPECIFIC_INFO_LENGTH, 403 mUsdCapabilities.maxLocalSsiLengthBytes); 404 bundle.putInt(Characteristics.KEY_MAX_MATCH_FILTER_LENGTH, 405 mUsdCapabilities.maxMatchFilterLengthBytes); 406 bundle.putInt(Characteristics.KEY_MAX_SERVICE_NAME_LENGTH, 407 mUsdCapabilities.maxServiceNameLengthBytes); 408 } 409 return new Characteristics(bundle); 410 } 411 412 /** 413 * Whether subscriber is available. 414 */ isSubscriberAvailable()415 public boolean isSubscriberAvailable() { 416 return mUsdSessions.size() == 0 || mUsdSessions.valueAt(0).mSessionRole == Role.SUBSCRIBER; 417 } 418 419 /** 420 * Whether publisher is available. 421 */ isPublisherAvailable()422 public boolean isPublisherAvailable() { 423 return mUsdSessions.size() == 0 || mUsdSessions.valueAt(0).mSessionRole == Role.PUBLISHER; 424 } 425 notifyStatus(IBooleanListener listener, String errMsg, boolean isSuccess)426 private void notifyStatus(IBooleanListener listener, String errMsg, boolean isSuccess) { 427 if (!isSuccess) { 428 Log.e(TAG, "notifyStatus: " + errMsg); 429 } 430 try { 431 listener.onResult(isSuccess); 432 } catch (RemoteException e) { 433 Log.e(TAG, e.toString()); 434 } 435 } 436 getUsdInterfaceName()437 private String getUsdInterfaceName() { 438 return mActiveModeWarden.getPrimaryClientModeManager().getInterfaceName(); 439 } 440 441 /** 442 * See {@link SubscribeSession#sendMessage(int, byte[], Executor, Consumer)} and 443 * {@link PublishSession#sendMessage(int, byte[], Executor, Consumer)} 444 */ sendMessage(int sessionId, int peerHash, @NonNull byte[] message, @NonNull IBooleanListener listener)445 public void sendMessage(int sessionId, int peerHash, @NonNull byte[] message, 446 @NonNull IBooleanListener listener) { 447 if (!isUsdAvailable()) { 448 notifyStatus(listener, "USD is not available", false); 449 return; 450 } 451 if (getUsdInterfaceName() == null) { 452 notifyStatus(listener, "USD interface name is null", false); 453 return; 454 } 455 if (!mUsdSessions.contains(sessionId)) { 456 notifyStatus(listener, "Session does not exist. Session id = " + sessionId, false); 457 return; 458 } 459 if (message.length > mUsdCapabilities.maxLocalSsiLengthBytes) { 460 notifyStatus(listener, "longer message than supported. Max len supported = " 461 + mUsdCapabilities.maxLocalSsiLengthBytes + " len = " + message.length, false); 462 return; 463 } 464 if (!doesPeerExistInGlobalMap(peerHash)) { 465 notifyStatus(listener, "Invalid peer hash = " + peerHash, false); 466 return; 467 } 468 UsdPeer peer = getPeerFromGlobalMap(peerHash); 469 if (mUsdNativeManager.sendMessage(getUsdInterfaceName(), sessionId, peer.peerId, 470 peer.peerMacAddress, message)) { 471 notifyStatus(listener, "", true); 472 } else { 473 notifyStatus(listener, "sendMessage failed", false); 474 } 475 } 476 isUsdAvailable()477 private boolean isUsdAvailable() { 478 if (mRequesterRole == Role.PUBLISHER) { 479 return isPublisherAvailable(); 480 } else if (mRequesterRole == Role.SUBSCRIBER) { 481 return isSubscriberAvailable(); 482 } 483 return false; 484 } 485 486 /** 487 * See {@link SubscribeSession#cancel()} 488 */ cancelSubscribe(int sessionId)489 public void cancelSubscribe(int sessionId) { 490 if (getUsdInterfaceName() == null) { 491 Log.e(TAG, "cancelSubscribe: USD interface name is null"); 492 return; 493 } 494 if (mRequesterRole == Role.SUBSCRIBER && mUsdSessions.contains(sessionId)) { 495 mUsdNativeManager.cancelSubscribe(getUsdInterfaceName(), sessionId); 496 } 497 } 498 499 /** 500 * See {@link PublishSession#cancel()} 501 */ cancelPublish(int sessionId)502 public void cancelPublish(int sessionId) { 503 if (getUsdInterfaceName() == null) { 504 Log.e(TAG, "cancelPublish: USD interface name is null"); 505 return; 506 } 507 if (mRequesterRole == Role.PUBLISHER && mUsdSessions.contains(sessionId)) { 508 mUsdNativeManager.cancelPublish(getUsdInterfaceName(), sessionId); 509 } 510 } 511 512 /** 513 * See {@link PublishSession#updatePublish(byte[])} 514 */ updatePublish(int sessionId, byte[] ssi)515 public void updatePublish(int sessionId, byte[] ssi) { 516 if (getUsdInterfaceName() == null) { 517 Log.e(TAG, "updatePublish: USD interface name is null"); 518 return; 519 } 520 if (mRequesterRole == Role.PUBLISHER && mUsdSessions.contains(sessionId) 521 && isPublisherAvailable()) { 522 mUsdNativeManager.updatePublish(getUsdInterfaceName(), sessionId, ssi); 523 } 524 } 525 notifyPublishFailure(IPublishSessionCallback callback, int reasonCode, String reason)526 private void notifyPublishFailure(IPublishSessionCallback callback, int reasonCode, 527 String reason) { 528 try { 529 Log.w(TAG, reason); 530 callback.onPublishFailed(reasonCode); 531 } catch (RemoteException e) { 532 Log.e(TAG, "publish: " + e); 533 } 534 } 535 536 /** 537 * See {@link android.net.wifi.usd.UsdManager#publish(PublishConfig, Executor, 538 * PublishSessionCallback)} 539 */ publish(PublishConfig publishConfig, IPublishSessionCallback callback)540 public void publish(PublishConfig publishConfig, IPublishSessionCallback callback) { 541 if (!isUsdPublisherSupported() || !isPublisherAvailable()) { 542 notifyPublishFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE, "Not available"); 543 return; 544 } 545 if (getUsdInterfaceName() == null) { 546 notifyPublishFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE, 547 "USD interface name is null"); 548 return; 549 } 550 // Check if the Role is already taken. 551 if (mRequesterRole == Role.SUBSCRIBER) { 552 notifyPublishFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE, 553 "Subscriber is running"); 554 return; 555 } 556 if (sessionCreationInProgress()) { 557 notifyPublishFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE, 558 "Publish session creation in progress"); 559 return; 560 } 561 // Check if maximum sessions reached 562 if (mUsdSessions.size() >= mUsdCapabilities.maxNumPublishSessions) { 563 notifyPublishFailure(callback, SessionCallback.FAILURE_MAX_SESSIONS_REACHED, 564 "Maximum number of publish sessions reached, num of sessions = " 565 + mUsdSessions.size()); 566 return; 567 } 568 // publish 569 if (mUsdNativeManager.publish(getUsdInterfaceName(), DEFAULT_COMMAND_ID, publishConfig)) { 570 createPublishSession(publishConfig, callback); 571 // Next: onUsdPublishStarted or onUsdPublishConfigFailed 572 } else { 573 notifyPublishFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE, "Failed"); 574 } 575 } 576 sessionCreationInProgress()577 private boolean sessionCreationInProgress() { 578 return mUsdSessions.contains(USD_TEMP_SESSION_ID); 579 } 580 notifySubscribeFailure(ISubscribeSessionCallback callback, int reasonCode, String reason)581 private void notifySubscribeFailure(ISubscribeSessionCallback callback, int reasonCode, 582 String reason) { 583 try { 584 Log.w(TAG, reason); 585 callback.onSubscribeFailed(reasonCode); 586 } catch (RemoteException e) { 587 Log.e(TAG, "subscribe: " + e); 588 } 589 } 590 createPublishSession(PublishConfig config, IPublishSessionCallback callback)591 private void createPublishSession(PublishConfig config, IPublishSessionCallback callback) { 592 UsdSession usdSession = new UsdSession(config, callback); 593 // Use a temp session id. Will get updated in onPublisherStarted. 594 usdSession.setSessionId(USD_TEMP_SESSION_ID); 595 mUsdSessions.put(USD_TEMP_SESSION_ID, usdSession); 596 if (isSingleSession()) { 597 mRequesterRole = Role.PUBLISHER; 598 startCleaningUpExpiredSessions(); 599 // After first publisher session is created, notify subscriber status as not available. 600 broadcastSubscriberStatus(); 601 } 602 } 603 createSubscribeSession(SubscribeConfig config, ISubscribeSessionCallback callback)604 private void createSubscribeSession(SubscribeConfig config, 605 ISubscribeSessionCallback callback) { 606 UsdSession usdSession = new UsdSession(config, callback); 607 // Use a temp session id. Will get updated in onSubscriberStarted. 608 usdSession.setSessionId(USD_TEMP_SESSION_ID); 609 mUsdSessions.put(USD_TEMP_SESSION_ID, usdSession); 610 if (isSingleSession()) { 611 mRequesterRole = Role.SUBSCRIBER; 612 startCleaningUpExpiredSessions(); 613 // After first subscriber session is created, notify publisher status as not available. 614 broadcastPublisherStatus(); 615 } 616 } 617 618 /** 619 * See {@link android.net.wifi.usd.UsdManager#subscribe(SubscribeConfig, Executor, 620 * SubscribeSessionCallback)} 621 */ subscribe(SubscribeConfig subscribeConfig, ISubscribeSessionCallback callback)622 public void subscribe(SubscribeConfig subscribeConfig, ISubscribeSessionCallback callback) { 623 if (!isUsdSubscriberSupported() || !isSubscriberAvailable()) { 624 notifySubscribeFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE, 625 "Not available"); 626 return; 627 } 628 if (getUsdInterfaceName() == null) { 629 notifySubscribeFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE, 630 "USD interface name is null"); 631 return; 632 } 633 // Check if the Role is already taken. 634 if (mRequesterRole == Role.PUBLISHER) { 635 notifySubscribeFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE, 636 "Publisher is running"); 637 return; 638 } 639 if (sessionCreationInProgress()) { 640 notifySubscribeFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE, 641 "Subscribe session creation in progress"); 642 return; 643 } 644 // Check if maximum sessions reached 645 if (mUsdSessions.size() >= mUsdCapabilities.maxNumSubscribeSessions) { 646 notifySubscribeFailure(callback, SessionCallback.FAILURE_MAX_SESSIONS_REACHED, 647 "Maximum number of subscribe sessions reached"); 648 return; 649 } 650 // subscribe 651 if (mUsdNativeManager.subscribe(getUsdInterfaceName(), DEFAULT_COMMAND_ID, 652 subscribeConfig)) { 653 createSubscribeSession(subscribeConfig, callback); 654 // Next: onUsdSubscribeStarted or onUsdSubscribeConfigFailed 655 } else { 656 notifySubscribeFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE, "Failed"); 657 } 658 } 659 660 /** 661 * Register USD events from HAL. 662 */ registerUsdEventsCallback(UsdNativeEventsCallback usdNativeEventsCallback)663 public void registerUsdEventsCallback(UsdNativeEventsCallback usdNativeEventsCallback) { 664 mUsdNativeManager.registerUsdEventsCallback(usdNativeEventsCallback); 665 } 666 667 /** 668 * Validate the session. 669 */ isValidSession(UsdSession session, int sessionId, Role role)670 private static boolean isValidSession(UsdSession session, int sessionId, Role role) { 671 if (session == null) { 672 Log.e(TAG, "isValidSession: session does not exist (id = " + sessionId + ")"); 673 return false; 674 } 675 if (session.mSessionRole != role) { 676 Log.e(TAG, "isValidSession: Invalid session role (id = " + sessionId + ")"); 677 return false; 678 } 679 if (session.mId != sessionId) { 680 Log.e(TAG, "isValidSession: Invalid session id (id = " + sessionId + ")"); 681 return false; 682 } 683 return true; 684 } 685 686 /** 687 * Implementation of USD callbacks. All callbacks are posted to Wi-Fi thread from 688 * SupplicantStaIfaceCallbackAidlImpl. 689 */ 690 public class UsdNativeEventsCallback implements UsdNativeManager.UsdEventsCallback { 691 @Override onUsdPublishStarted(int cmdId, int publishId)692 public void onUsdPublishStarted(int cmdId, int publishId) { 693 if (cmdId != DEFAULT_COMMAND_ID) { 694 Log.e(TAG, "onUsdPublishStarted: Invalid command id = " + cmdId); 695 return; 696 } 697 UsdSession usdSession = mUsdSessions.get(USD_TEMP_SESSION_ID); 698 if (!isValidSession(usdSession, USD_TEMP_SESSION_ID, Role.PUBLISHER)) return; 699 mUsdSessions.put(publishId, usdSession); 700 usdSession.setSessionId(publishId); 701 mUsdSessions.remove(USD_TEMP_SESSION_ID); 702 try { 703 usdSession.mIPublishSessionCallback.onPublishStarted(publishId); 704 } catch (RemoteException e) { 705 Log.e(TAG, "onUsdPublishStarted " + e); 706 } 707 // Next: onUsdPublishReplied or onUsdPublishTerminated 708 } 709 710 @Override onUsdSubscribeStarted(int cmdId, int subscribeId)711 public void onUsdSubscribeStarted(int cmdId, int subscribeId) { 712 if (cmdId != DEFAULT_COMMAND_ID) { 713 Log.e(TAG, "onUsdSubscribeStarted: Invalid command id = " + cmdId); 714 return; 715 } 716 UsdSession usdSession = mUsdSessions.get(USD_TEMP_SESSION_ID); 717 if (!isValidSession(usdSession, USD_TEMP_SESSION_ID, Role.SUBSCRIBER)) return; 718 mUsdSessions.put(subscribeId, usdSession); 719 usdSession.setSessionId(subscribeId); 720 mUsdSessions.remove(USD_TEMP_SESSION_ID); 721 try { 722 usdSession.mISubscribeSessionCallback.onSubscribeStarted(subscribeId); 723 } catch (RemoteException e) { 724 Log.e(TAG, "onUsdSubscribeStarted " + e); 725 } 726 // Next: onUsdServiceDiscovered or onUsdSubscribeTerminated 727 } 728 729 @Override onUsdPublishConfigFailed(int cmdId, @SessionCallback.FailureCode int errorCode)730 public void onUsdPublishConfigFailed(int cmdId, 731 @SessionCallback.FailureCode int errorCode) { 732 if (cmdId != DEFAULT_COMMAND_ID) { 733 Log.e(TAG, "onUsdPublishConfigFailed: Invalid command id = " + cmdId); 734 return; 735 } 736 UsdSession usdSession = mUsdSessions.get(USD_TEMP_SESSION_ID); 737 if (isValidSession(usdSession, USD_TEMP_SESSION_ID, Role.PUBLISHER)) return; 738 usdSession.sessionCleanup(); 739 try { 740 usdSession.mIPublishSessionCallback.onPublishFailed(errorCode); 741 } catch (RemoteException e) { 742 Log.e(TAG, "onUsdPublishConfigFailed " + e); 743 } 744 } 745 746 @Override onUsdSubscribeConfigFailed(int cmdId, @SessionCallback.FailureCode int errorCode)747 public void onUsdSubscribeConfigFailed(int cmdId, 748 @SessionCallback.FailureCode int errorCode) { 749 if (cmdId != DEFAULT_COMMAND_ID) { 750 Log.e(TAG, "onUsdSubscribeConfigFailed: Invalid command id = " + cmdId); 751 return; 752 } 753 UsdSession usdSession = mUsdSessions.get(USD_TEMP_SESSION_ID); 754 if (!isValidSession(usdSession, USD_TEMP_SESSION_ID, Role.SUBSCRIBER)) return; 755 usdSession.sessionCleanup(); 756 try { 757 usdSession.mISubscribeSessionCallback.onSubscribeFailed(errorCode); 758 } catch (RemoteException e) { 759 Log.e(TAG, "onUsdSubscribeConfigFailed " + e); 760 } 761 } 762 763 @Override onUsdPublishTerminated(int publishId, int reasonCode)764 public void onUsdPublishTerminated(int publishId, int reasonCode) { 765 if (!mUsdSessions.contains(publishId)) { 766 return; 767 } 768 UsdSession usdSession = mUsdSessions.get(publishId); 769 if (!isValidSession(usdSession, publishId, Role.PUBLISHER)) return; 770 try { 771 usdSession.mIPublishSessionCallback.onPublishSessionTerminated(reasonCode); 772 } catch (RemoteException e) { 773 Log.e(TAG, "onUsdPublishTerminated " + e); 774 } 775 usdSession.sessionCleanup(); 776 } 777 778 @Override onUsdSubscribeTerminated(int subscribeId, int reasonCode)779 public void onUsdSubscribeTerminated(int subscribeId, int reasonCode) { 780 if (!mUsdSessions.contains(subscribeId)) { 781 return; 782 } 783 UsdSession usdSession = mUsdSessions.get(subscribeId); 784 if (!isValidSession(usdSession, subscribeId, Role.SUBSCRIBER)) return; 785 try { 786 usdSession.mISubscribeSessionCallback.onSubscribeSessionTerminated(reasonCode); 787 } catch (RemoteException e) { 788 Log.e(TAG, "onUsdSubscribeTerminated " + e); 789 } 790 usdSession.sessionCleanup(); 791 } 792 793 @Override onUsdPublishReplied(UsdHalDiscoveryInfo info)794 public void onUsdPublishReplied(UsdHalDiscoveryInfo info) { 795 // Check whether session matches. 796 if (!mUsdSessions.contains(info.ownId)) { 797 return; 798 } 799 // Check whether events are enabled for the publisher. 800 UsdSession usdSession = mUsdSessions.get(info.ownId); 801 if (isValidSession(usdSession, info.ownId, Role.PUBLISHER)) return; 802 if (!usdSession.mPublishConfig.isEventsEnabled()) return; 803 // Add the peer to the session if not already present. 804 UsdPeer peer = new UsdPeer(info.ownId, info.peerId, info.peerMacAddress); 805 usdSession.addPeerOnce(peer); 806 try { 807 // Pass unique peer hash to the application. When the application gives back the 808 // peer hash, it'll be used to retrieve the peer. 809 usdSession.mIPublishSessionCallback.onPublishReplied(usdSession.getPeerHash(peer), 810 info.serviceSpecificInfo, info.serviceProtoType, info.isFsdEnabled); 811 } catch (RemoteException e) { 812 Log.e(TAG, "onUsdPublishReplied " + e); 813 } 814 } 815 816 @Override onUsdServiceDiscovered(UsdHalDiscoveryInfo info)817 public void onUsdServiceDiscovered(UsdHalDiscoveryInfo info) { 818 // Check whether session matches. 819 if (!mUsdSessions.contains(info.ownId)) { 820 return; 821 } 822 // Add the peer to the session if not already present. 823 UsdPeer peer = new UsdPeer(info.ownId, info.peerId, info.peerMacAddress); 824 UsdSession usdSession = mUsdSessions.get(info.ownId); 825 if (isValidSession(usdSession, info.ownId, Role.SUBSCRIBER)) return; 826 usdSession.addPeerOnce(peer); 827 try { 828 // Pass unique peer hash to the application. When the application gives back the 829 // peer hash, it'll be used to retrieve the peer. 830 usdSession.mISubscribeSessionCallback.onSubscribeDiscovered( 831 usdSession.getPeerHash(peer), info.serviceSpecificInfo, 832 info.serviceProtoType, info.isFsdEnabled); 833 } catch (RemoteException e) { 834 Log.e(TAG, "onUsdServiceDiscovered " + e); 835 } 836 } 837 838 @Override onUsdMessageReceived(int ownId, int peerId, MacAddress peerMacAddress, byte[] message)839 public void onUsdMessageReceived(int ownId, int peerId, MacAddress peerMacAddress, 840 byte[] message) { 841 // Check whether session matches. 842 if (!mUsdSessions.contains(ownId)) { 843 return; 844 } 845 // Add the peer to the session if not already present. 846 UsdPeer peer = new UsdPeer(ownId, peerId, peerMacAddress); 847 UsdSession usdSession = mUsdSessions.get(ownId); 848 if (isValidSession(usdSession, ownId, mRequesterRole)) return; 849 usdSession.addPeerOnce(peer); 850 try { 851 // Pass unique peer hash to the application. When the application gives back the 852 // peer hash, it'll be used to retrieve the peer. 853 if (mRequesterRole == Role.SUBSCRIBER) { 854 usdSession.mISubscribeSessionCallback.onMessageReceived( 855 usdSession.getPeerHash(peer), message); 856 } else { 857 usdSession.mIPublishSessionCallback.onMessageReceived( 858 usdSession.getPeerHash(peer), message); 859 } 860 } catch (RemoteException e) { 861 Log.e(TAG, "onUsdMessageReceived " + e); 862 } 863 } 864 } 865 broadcastPublisherStatus()866 private void broadcastPublisherStatus() { 867 int numListeners = mPublisherListenerList.beginBroadcast(); 868 for (int i = 0; i < numListeners; i++) { 869 IBooleanListener listener = mPublisherListenerList.getBroadcastItem(i); 870 try { 871 listener.onResult(isPublisherAvailable()); 872 } catch (RemoteException e) { 873 Log.e(TAG, "broadcastPublisherStatus: " + e); 874 } 875 } 876 mPublisherListenerList.finishBroadcast(); 877 } 878 broadcastSubscriberStatus()879 private void broadcastSubscriberStatus() { 880 int numListeners = mSubscriberListenerList.beginBroadcast(); 881 for (int i = 0; i < numListeners; i++) { 882 IBooleanListener listener = mSubscriberListenerList.getBroadcastItem(i); 883 try { 884 listener.onResult(isSubscriberAvailable()); 885 } catch (RemoteException e) { 886 Log.e(TAG, "broadcastSubscriberStatus: " + e); 887 } 888 } 889 mSubscriberListenerList.finishBroadcast(); 890 } 891 892 /** 893 * Register for publisher status listener and notify the application on current status. 894 */ registerPublisherStatusListener(IBooleanListener listener)895 public void registerPublisherStatusListener(IBooleanListener listener) { 896 mPublisherListenerList.register(listener); 897 try { 898 listener.onResult(isPublisherAvailable()); 899 } catch (RemoteException e) { 900 Log.e(TAG, "registerPublisherStatusListener: " + e); 901 } 902 } 903 904 /** 905 * Unregister previously registered publisher status listener. 906 */ unregisterPublisherStatusListener(IBooleanListener listener)907 public void unregisterPublisherStatusListener(IBooleanListener listener) { 908 mPublisherListenerList.unregister(listener); 909 } 910 911 /** 912 * Register for subscriber status listener and notify the application on current status. 913 */ registerSubscriberStatusListener(IBooleanListener listener)914 public void registerSubscriberStatusListener(IBooleanListener listener) { 915 mSubscriberListenerList.register(listener); 916 try { 917 listener.onResult(isSubscriberAvailable()); 918 } catch (RemoteException e) { 919 Log.e(TAG, "registerSubscriberStatusListener: " + e); 920 } 921 } 922 923 /** 924 * Unregister previously registered subscriber status listener. 925 */ unregisterSubscriberStatusListener(IBooleanListener listener)926 public void unregisterSubscriberStatusListener(IBooleanListener listener) { 927 mSubscriberListenerList.unregister(listener); 928 } 929 } 930