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 package com.android.server.wifi; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.app.AlarmManager; 21 import android.net.MacAddress; 22 import android.net.wifi.ITwtCallback; 23 import android.net.wifi.ITwtCapabilitiesListener; 24 import android.net.wifi.ITwtStatsListener; 25 import android.net.wifi.WifiManager; 26 import android.net.wifi.twt.TwtRequest; 27 import android.net.wifi.twt.TwtSession; 28 import android.net.wifi.twt.TwtSessionCallback; 29 import android.os.Binder; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.IBinder; 33 import android.os.IInterface; 34 import android.os.RemoteException; 35 import android.util.ArraySet; 36 import android.util.Log; 37 import android.util.SparseArray; 38 39 import com.android.wifi.resources.R; 40 41 import java.util.ArrayList; 42 import java.util.BitSet; 43 import java.util.List; 44 45 /** 46 * This class acts as a manager for TWT sessions and callbacks. It establishes a link between unique 47 * callback IDs and their corresponding callbacks, ensuring the correct responses are triggered. To 48 * manage incoming TWT events, the class registers TWT sessions with the appropriate callbacks. 49 * Additionally, it implements a garbage collection task to remove expired callbacks. 50 * 51 * If a registered callback's process goes away, this class will take care of automatically 52 * removing it from the callback list. Twt manager allows simultaneous requests limited by 53 * {@link #MAXIMUM_CALLBACKS}. 54 * 55 * Note: All contexts in TwtManager are in WifiThread. So no locks are used. 56 */ 57 58 class TwtManager { 59 public static final String TAG = "TwtManager"; 60 private static final int TWT_CALLBACK_TIMEOUT_MILLIS = 2000; 61 private static final String TWT_MANAGER_ALARM_TAG = "twtManagerAlarm"; 62 private static final int MAXIMUM_CALLBACKS = 8; 63 64 private class Callback implements IBinder.DeathRecipient { 65 public IInterface mCallback; 66 public final int mOwner; 67 public int mSessionId = -1; 68 public final int mId; 69 public final CallbackType mType; 70 public final long mTimestamp; 71 Callback(int id, IInterface callback, CallbackType type, int owner)72 Callback(int id, IInterface callback, CallbackType type, int owner) { 73 mId = id; 74 mCallback = callback; 75 mType = type; 76 mOwner = owner; 77 mTimestamp = mClock.getElapsedSinceBootMillis(); 78 } 79 80 @Override binderDied()81 public void binderDied() { 82 mHandler.post(() -> { 83 unregisterSession(mSessionId); 84 unregisterCallback(mId); 85 }); 86 } 87 } 88 89 private enum CallbackType {SETUP, STATS, TEARDOWN} 90 private final SparseArray<Callback> mCommandCallbacks = new SparseArray<>(); 91 private final SparseArray<Callback> mTwtSessionCallbacks = new SparseArray<>(); 92 private final BitSet mIdBitSet; 93 private final int mStartOffset; 94 private final int mMaxSessions; 95 private String mInterfaceName; 96 private final Clock mClock; 97 private final AlarmManager mAlarmManager; 98 private final Handler mHandler; 99 ArraySet<Integer> mBlockedOuiSet = new ArraySet<>(); 100 private final WifiNative mWifiNative; 101 private final WifiNativeTwtEvents mWifiNativeTwtEvents; 102 private final AlarmManager.OnAlarmListener mTimeoutListener = () -> { 103 startGarbageCollector(); 104 }; 105 private final WifiInjector mWifiInjector; 106 107 /** 108 * Whenever primary clientModeManager identified by the interface name gets disconnected, reset 109 * the TwtManager. 110 */ 111 private class ClientModeImplListenerInternal implements ClientModeImplListener { 112 @Override onConnectionEnd(@onNull ConcreteClientModeManager clientModeManager)113 public void onConnectionEnd(@NonNull ConcreteClientModeManager clientModeManager) { 114 if (clientModeManager.getInterfaceName() != null 115 && clientModeManager.getInterfaceName().equals(mInterfaceName)) { 116 reset(); 117 } 118 } 119 } 120 TwtManager(@onNull WifiInjector wifiInjector, @NonNull ClientModeImplMonitor cmiMonitor, @NonNull WifiNative wifiNative, @NonNull Handler handler, @NonNull Clock clock, int maxSessions, int startOffset)121 TwtManager(@NonNull WifiInjector wifiInjector, @NonNull ClientModeImplMonitor cmiMonitor, 122 @NonNull WifiNative wifiNative, @NonNull Handler handler, @NonNull Clock clock, 123 int maxSessions, int startOffset) { 124 mWifiInjector = wifiInjector; 125 mAlarmManager = wifiInjector.getAlarmManager(); 126 mHandler = handler; 127 mClock = clock; 128 mMaxSessions = maxSessions; 129 mIdBitSet = new BitSet(MAXIMUM_CALLBACKS); 130 mStartOffset = startOffset; 131 mWifiNative = wifiNative; 132 mWifiNativeTwtEvents = new WifiNativeTwtEvents(); 133 cmiMonitor.registerListener(new ClientModeImplListenerInternal()); 134 int[] ouis = wifiInjector.getContext().getResources().getIntArray( 135 R.array.config_wifiTwtBlockedOuiList); 136 if (ouis != null) { 137 for (int oui : ouis) { 138 mBlockedOuiSet.add(oui); 139 } 140 } 141 } 142 143 /** 144 * Notify teardown to the registered caller 145 */ notifyTeardown(ITwtCallback iTwtCallback, @TwtSessionCallback.TwtReasonCode int reasonCode)146 private void notifyTeardown(ITwtCallback iTwtCallback, 147 @TwtSessionCallback.TwtReasonCode int reasonCode) { 148 if (iTwtCallback == null) { 149 Log.e(TAG, "notifyTeardown: null interface. Reason code " + reasonCode); 150 return; 151 } 152 try { 153 iTwtCallback.onTeardown(reasonCode); 154 } catch (RemoteException e) { 155 Log.e(TAG, "notifyTeardown: " + e); 156 } 157 } 158 getDefaultTwtCapabilities()159 private Bundle getDefaultTwtCapabilities() { 160 Bundle twtCapabilities = new Bundle(); 161 twtCapabilities.putBoolean(WifiManager.TWT_CAPABILITIES_KEY_BOOLEAN_TWT_REQUESTER, false); 162 twtCapabilities.putInt(WifiManager.TWT_CAPABILITIES_KEY_INT_MIN_WAKE_DURATION_MICROS, -1); 163 twtCapabilities.putInt(WifiManager.TWT_CAPABILITIES_KEY_INT_MAX_WAKE_DURATION_MICROS, -1); 164 twtCapabilities.putLong(WifiManager.TWT_CAPABILITIES_KEY_LONG_MIN_WAKE_INTERVAL_MICROS, -1); 165 twtCapabilities.putLong(WifiManager.TWT_CAPABILITIES_KEY_LONG_MAX_WAKE_INTERVAL_MICROS, -1); 166 return twtCapabilities; 167 } 168 getDefaultTwtStats()169 private static Bundle getDefaultTwtStats() { 170 Bundle twtStats = new Bundle(); 171 twtStats.putInt(TwtSession.TWT_STATS_KEY_INT_AVERAGE_TX_PACKET_COUNT, -1); 172 twtStats.putInt(TwtSession.TWT_STATS_KEY_INT_AVERAGE_RX_PACKET_COUNT, -1); 173 twtStats.putInt(TwtSession.TWT_STATS_KEY_INT_AVERAGE_TX_PACKET_SIZE, -1); 174 twtStats.putInt(TwtSession.TWT_STATS_KEY_INT_AVERAGE_RX_PACKET_SIZE, -1); 175 twtStats.putInt(TwtSession.TWT_STATS_KEY_INT_AVERAGE_EOSP_DURATION_MICROS, -1); 176 twtStats.putInt(TwtSession.TWT_STATS_KEY_INT_EOSP_COUNT, -1); 177 return twtStats; 178 } 179 180 /** 181 * Notify failure to the registered caller 182 */ notifyFailure(IInterface iInterface, CallbackType type, @TwtSessionCallback.TwtErrorCode int errorCode)183 private void notifyFailure(IInterface iInterface, CallbackType type, 184 @TwtSessionCallback.TwtErrorCode int errorCode) { 185 if (iInterface == null) { 186 Log.e(TAG, "notifyFailure: null interface. Error code " + errorCode); 187 return; 188 } 189 try { 190 if (type == CallbackType.STATS) { 191 ((ITwtStatsListener) iInterface).onResult(getDefaultTwtStats()); 192 } else { 193 ((ITwtCallback) iInterface).onFailure(errorCode); 194 } 195 } catch (RemoteException e) { 196 Log.e(TAG, "notifyFailure: " + e); 197 } 198 } 199 200 /** 201 * Expire callbacks and fetch next oldest callback's schedule for timeout 202 * 203 * @param now Current reference time 204 * @return Timeout of the oldest callback with respect to current time. A value 0 means no more 205 * callbacks to expire. 206 */ handleExpirationsAndGetNextTimeout(long now)207 private long handleExpirationsAndGetNextTimeout(long now) { 208 long oldest = Long.MAX_VALUE; 209 List<Integer> expiredIds = new ArrayList<>(); 210 for (int i = 0; i < mCommandCallbacks.size(); ++i) { 211 Callback callback = mCommandCallbacks.valueAt(i); 212 if (now - callback.mTimestamp >= TWT_CALLBACK_TIMEOUT_MILLIS) { 213 notifyFailure(callback.mCallback, callback.mType, 214 TwtSessionCallback.TWT_ERROR_CODE_TIMEOUT); 215 // Unregister session now 216 if (callback.mType == CallbackType.TEARDOWN) { 217 unregisterSession(callback.mSessionId); 218 } 219 expiredIds.add(callback.mId); 220 } else { 221 oldest = Math.min(callback.mTimestamp, oldest); 222 } 223 } 224 for (int id : expiredIds) { 225 unregisterCallback(id); 226 } 227 228 if (oldest > now) return 0; 229 // Callbacks which has (age >= TWT_COMMAND_TIMEOUT_MILLIS) is cleaned up already 230 return TWT_CALLBACK_TIMEOUT_MILLIS - (now - oldest); 231 } 232 startGarbageCollector()233 private void startGarbageCollector() { 234 long timeout = handleExpirationsAndGetNextTimeout(mClock.getElapsedSinceBootMillis()); 235 if (timeout <= 0) return; 236 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, 237 mClock.getElapsedSinceBootMillis() + timeout, TWT_MANAGER_ALARM_TAG, 238 mTimeoutListener, mHandler); 239 } 240 stopGarbageCollector()241 private void stopGarbageCollector() { 242 mAlarmManager.cancel(mTimeoutListener); 243 } 244 245 /** 246 * Register a callback 247 * 248 * @param callback A remote interface performing callback 249 * @param type Type of the callback as {@link CallbackType} 250 * @param owner Owner of the callback 251 * @return Returns an unique id. -1 if registration fails. 252 */ registerCallback(IInterface callback, CallbackType type, int owner)253 private int registerCallback(IInterface callback, CallbackType type, int owner) { 254 if (callback == null) { 255 Log.e(TAG, "registerCallback: Null callback"); 256 return -1; 257 } 258 if ((type == CallbackType.SETUP) && (mTwtSessionCallbacks.size() >= mMaxSessions)) { 259 Log.e(TAG, "registerCallback: Maximum sessions reached. Setup not allowed."); 260 notifyFailure(callback, CallbackType.SETUP, 261 TwtSessionCallback.TWT_ERROR_CODE_MAX_SESSIONS_REACHED); 262 return -1; 263 } 264 int id = mIdBitSet.nextClearBit(0); 265 if (id >= MAXIMUM_CALLBACKS) { 266 Log.e(TAG, "registerCallback: No more simultaneous requests possible"); 267 notifyFailure(callback, CallbackType.SETUP, 268 TwtSessionCallback.TWT_ERROR_CODE_NOT_AVAILABLE); 269 return -1; 270 } 271 mIdBitSet.set(id); 272 id += mStartOffset; 273 try { 274 Callback cb = new Callback(id, callback, type, owner); 275 callback.asBinder().linkToDeath(cb, 0); 276 mCommandCallbacks.put(id, cb); 277 } catch (RemoteException e) { 278 Log.e(TAG, "registerCallback: Error on linkToDeath - " + e); 279 notifyFailure(callback, CallbackType.SETUP, TwtSessionCallback.TWT_ERROR_CODE_FAIL); 280 return -1; 281 } catch (IndexOutOfBoundsException e) { 282 Log.e(TAG, "registerCallback: " + e); 283 notifyFailure(callback, CallbackType.SETUP, TwtSessionCallback.TWT_ERROR_CODE_FAIL); 284 return -1; 285 } 286 // First register triggers GC 287 if (mCommandCallbacks.size() == 1) startGarbageCollector(); 288 return id; 289 } 290 291 /** 292 * Unregister a previously registered callback 293 * 294 * @param id Unique callback id returned by 295 * {@link #registerCallback(IInterface, CallbackType, int)} 296 */ unregisterCallback(int id)297 private void unregisterCallback(int id) { 298 try { 299 if (!mCommandCallbacks.contains(id)) return; 300 // Last unregister stops GC 301 if (mCommandCallbacks.size() == 1) stopGarbageCollector(); 302 Callback cb = mCommandCallbacks.get(id); 303 if (!mTwtSessionCallbacks.contains(cb.mSessionId)) { 304 // Note: unregisterSession() will call Binder#unlinktoDeath() 305 cb.mCallback.asBinder().unlinkToDeath(cb, 0); 306 } 307 mCommandCallbacks.delete(id); 308 mIdBitSet.clear(id - mStartOffset); 309 } catch (IndexOutOfBoundsException e) { 310 Log.e(TAG, "unregisterCallback: invalid id " + id + " " + e); 311 } 312 } 313 314 /** 315 * Register a TWT session 316 * 317 * @param id Unique callback id returned by 318 * {@link #registerCallback(IInterface, CallbackType, int)} 319 * @param sessionId TWT session id 320 * @return true if successful, otherwise false. 321 */ registerSession(int id, int sessionId)322 private boolean registerSession(int id, int sessionId) { 323 Callback callback = mCommandCallbacks.get(id); 324 if (callback == null) { 325 Log.e(TAG, "registerSession failed. Invalid id " + id); 326 return false; 327 } 328 if (mTwtSessionCallbacks.contains(sessionId)) { 329 Log.e(TAG, "registerSession failed. Session already exists"); 330 return false; 331 } 332 callback.mSessionId = sessionId; 333 mTwtSessionCallbacks.put(sessionId, callback); 334 return true; 335 } 336 337 /** 338 * Unregister a TWT session 339 * 340 * @param sessionId TWT session id 341 */ unregisterSession(int sessionId)342 private void unregisterSession(int sessionId) { 343 if (!mTwtSessionCallbacks.contains(sessionId)) { 344 Log.e(TAG, "unregisterSession failed. Session does not exist"); 345 return; 346 } 347 Callback callback = mTwtSessionCallbacks.get(sessionId); 348 callback.mCallback.asBinder().unlinkToDeath(callback, 0); 349 mTwtSessionCallbacks.delete(sessionId); 350 } 351 isSessionRegistered(int sessionId)352 private boolean isSessionRegistered(int sessionId) { 353 return mTwtSessionCallbacks.get(sessionId) != null; 354 } 355 356 /** 357 * Get callback from TWT session id 358 * 359 * @param sessionId TWT session id 360 * @return Callback registered, otherwise null 361 */ getCallbackFromSession(int sessionId)362 private IInterface getCallbackFromSession(int sessionId) { 363 if (mTwtSessionCallbacks.get(sessionId) == null) return null; 364 return mTwtSessionCallbacks.get(sessionId).mCallback; 365 } 366 367 /** 368 * Get owner uid 369 * 370 * @param id unique id returned by {@link #registerCallback(IInterface, CallbackType, int)} 371 * @return Owner UID if registered, otherwise -1 372 */ getOwnerUid(int id)373 private int getOwnerUid(int id) { 374 try { 375 return mCommandCallbacks.get(id).mOwner; 376 } catch (IndexOutOfBoundsException e) { 377 Log.e(TAG, "getOwner: invalid id " + id + " " + e); 378 return -1; 379 } 380 } 381 382 /** 383 * Get callback 384 * 385 * @param id unique id returned by {@link #registerCallback(IInterface, CallbackType, int)} 386 * @return Callback if registered, otherwise null 387 */ getCallback(int id)388 private IInterface getCallback(int id) { 389 try { 390 Callback callback = mCommandCallbacks.get(id); 391 if (callback == null) return null; 392 return callback.mCallback; 393 } catch (IndexOutOfBoundsException e) { 394 Log.e(TAG, "getCallback: invalid id " + id + " " + e); 395 return null; 396 } 397 } 398 399 /** 400 * Implementation of TWT events from WifiNative. see {@link #registerWifiNativeTwtEvents()} 401 */ 402 public class WifiNativeTwtEvents implements WifiNative.WifiTwtEvents { 403 @Override onTwtFailure(int cmdId, int twtErrorCode)404 public void onTwtFailure(int cmdId, int twtErrorCode) { 405 ITwtCallback iTwtCallback = (ITwtCallback) getCallback(cmdId); 406 if (iTwtCallback == null) { 407 Log.e(TAG, "onTwtFailure: Command Id is not registered " + cmdId); 408 return; 409 } 410 try { 411 iTwtCallback.onFailure(twtErrorCode); 412 } catch (RemoteException e) { 413 Log.e(TAG, e.getMessage(), e); 414 } 415 unregisterCallback(cmdId); 416 } 417 418 @Override onTwtSessionCreate(int cmdId, int wakeDurationUs, long wakeIntervalUs, int linkId, int sessionId)419 public void onTwtSessionCreate(int cmdId, int wakeDurationUs, long wakeIntervalUs, 420 int linkId, int sessionId) { 421 ITwtCallback iTwtCallback = (ITwtCallback) getCallback(cmdId); 422 if (iTwtCallback == null) { 423 Log.e(TAG, "onTwtSessionCreate failed. No callback registered for " + cmdId); 424 return; 425 } 426 if (!registerSession(cmdId, sessionId)) { 427 Log.e(TAG, "onTwtSessionCreate failed for session " + sessionId); 428 return; 429 } 430 try { 431 iTwtCallback.onCreate(wakeDurationUs, wakeIntervalUs, linkId, getOwnerUid(cmdId), 432 sessionId); 433 } catch (RemoteException e) { 434 Log.e(TAG, e.getMessage(), e); 435 } 436 unregisterCallback(cmdId); 437 } 438 439 @Override onTwtSessionTeardown(int cmdId, int twtSessionId, int twtReasonCode)440 public void onTwtSessionTeardown(int cmdId, int twtSessionId, int twtReasonCode) { 441 ITwtCallback iTwtCallback = (ITwtCallback) getCallback(cmdId); 442 if (iTwtCallback == null) { 443 // Unsolicited teardown. So get callback from session. 444 iTwtCallback = (ITwtCallback) getCallbackFromSession(twtSessionId); 445 if (iTwtCallback == null) return; 446 } 447 try { 448 iTwtCallback.onTeardown(twtReasonCode); 449 } catch (RemoteException e) { 450 Log.e(TAG, e.getMessage(), e); 451 } 452 unregisterCallback(cmdId); 453 unregisterSession(twtSessionId); 454 } 455 456 @Override onTwtSessionStats(int cmdId, int twtSessionId, Bundle twtStats)457 public void onTwtSessionStats(int cmdId, int twtSessionId, Bundle twtStats) { 458 ITwtStatsListener iTwtStatsListener = (ITwtStatsListener) getCallback(cmdId); 459 if (iTwtStatsListener == null) { 460 return; 461 } 462 try { 463 iTwtStatsListener.onResult(twtStats); 464 } catch (RemoteException e) { 465 Log.e(TAG, e.getMessage(), e); 466 } 467 unregisterCallback(cmdId); 468 } 469 } 470 471 /** 472 * Register for TWT events from WifiNative 473 */ registerWifiNativeTwtEvents()474 public void registerWifiNativeTwtEvents() { 475 mWifiNative.registerTwtCallbacks(mWifiNativeTwtEvents); 476 } 477 478 /** 479 * Get TWT capabilities for the interface 480 * 481 * @param interfaceName Interface name 482 * @param listener listener for TWT capabilities 483 */ getTwtCapabilities(@ullable String interfaceName, @NonNull ITwtCapabilitiesListener listener)484 public void getTwtCapabilities(@Nullable String interfaceName, 485 @NonNull ITwtCapabilitiesListener listener) { 486 try { 487 if (interfaceName == null || !isTwtSupported()) { 488 listener.onResult(getDefaultTwtCapabilities()); 489 return; 490 } 491 Bundle twtCapabilities = mWifiNative.getTwtCapabilities(interfaceName); 492 if (twtCapabilities == null) twtCapabilities = getDefaultTwtCapabilities(); 493 listener.onResult(twtCapabilities); 494 } catch (RemoteException e) { 495 Log.e(TAG, e.getMessage(), e); 496 } 497 } 498 499 /** 500 * Sets up a TWT session for the interface 501 * 502 * @param interfaceName Interface name 503 * @param twtRequest TWT request parameters 504 * @param iTwtCallback Callback for the TWT setup command 505 * @param callingUid Caller UID 506 * @param bssid BSSID 507 */ setupTwtSession(@ullable String interfaceName, @NonNull TwtRequest twtRequest, @NonNull ITwtCallback iTwtCallback, int callingUid, @NonNull String bssid)508 public void setupTwtSession(@Nullable String interfaceName, @NonNull TwtRequest twtRequest, 509 @NonNull ITwtCallback iTwtCallback, int callingUid, @NonNull String bssid) { 510 if (!isTwtSupported() || !isTwtCapable(interfaceName)) { 511 notifyFailure(iTwtCallback, CallbackType.SETUP, 512 TwtSessionCallback.TWT_ERROR_CODE_NOT_SUPPORTED); 513 return; 514 } 515 if (isOuiBlockListed(bssid)) { 516 notifyFailure(iTwtCallback, CallbackType.SETUP, 517 TwtSessionCallback.TWT_ERROR_CODE_AP_OUI_BLOCKLISTED); 518 return; 519 } 520 if (!registerInterface(interfaceName)) { 521 notifyFailure(iTwtCallback, CallbackType.SETUP, 522 TwtSessionCallback.TWT_ERROR_CODE_NOT_AVAILABLE); 523 return; 524 } 525 int id = registerCallback(iTwtCallback, TwtManager.CallbackType.SETUP, callingUid); 526 if (id < 0) { 527 return; 528 } 529 if (!mWifiNative.setupTwtSession(id, interfaceName, twtRequest)) { 530 unregisterCallback(id); 531 notifyFailure(iTwtCallback, CallbackType.SETUP, 532 TwtSessionCallback.TWT_ERROR_CODE_NOT_AVAILABLE); 533 } 534 } 535 isTwtSupported()536 private boolean isTwtSupported() { 537 return mWifiInjector.getContext().getResources().getBoolean( 538 R.bool.config_wifiTwtSupported); 539 } 540 isTwtCapable(String interfaceName)541 private boolean isTwtCapable(String interfaceName) { 542 if (interfaceName == null) return false; 543 Bundle twtCapabilities = mWifiNative.getTwtCapabilities(interfaceName); 544 if (twtCapabilities == null) return false; 545 return twtCapabilities.getBoolean(WifiManager.TWT_CAPABILITIES_KEY_BOOLEAN_TWT_REQUESTER); 546 } 547 isOuiBlockListed(@onNull String bssid)548 private boolean isOuiBlockListed(@NonNull String bssid) { 549 if (mBlockedOuiSet.isEmpty()) return false; 550 byte[] macBytes = MacAddress.fromString(bssid).toByteArray(); 551 int oui = (macBytes[0] & 0xFF) << 16 | (macBytes[1] & 0xFF) << 8 | (macBytes[2] & 0xFF); 552 return mBlockedOuiSet.contains(oui); 553 } 554 555 /** 556 * Teardown the TWT session 557 * 558 * @param interfaceName Interface name 559 * @param sessionId TWT session id 560 */ tearDownTwtSession(@ullable String interfaceName, int sessionId)561 public void tearDownTwtSession(@Nullable String interfaceName, int sessionId) { 562 ITwtCallback iTwtCallback = (ITwtCallback) getCallbackFromSession(sessionId); 563 if (iTwtCallback == null) { 564 return; 565 } 566 if (!isRegisteredInterface(interfaceName)) { 567 notifyFailure(iTwtCallback, CallbackType.TEARDOWN, 568 TwtSessionCallback.TWT_ERROR_CODE_NOT_AVAILABLE); 569 return; 570 } 571 int id = registerCallback(iTwtCallback, TwtManager.CallbackType.TEARDOWN, 572 Binder.getCallingUid()); 573 if (id < 0) { 574 return; 575 } 576 if (!mWifiNative.tearDownTwtSession(id, interfaceName, sessionId)) { 577 unregisterCallback(id); 578 notifyFailure(iTwtCallback, CallbackType.TEARDOWN, 579 TwtSessionCallback.TWT_ERROR_CODE_NOT_AVAILABLE); 580 } 581 } 582 583 /** 584 * Gets stats of the TWT session 585 * 586 * @param interfaceName Interface name 587 * @param iTwtStatsListener Listener for TWT stats 588 * @param sessionId TWT session id 589 */ getStatsTwtSession(@ullable String interfaceName, ITwtStatsListener iTwtStatsListener, int sessionId)590 public void getStatsTwtSession(@Nullable String interfaceName, 591 ITwtStatsListener iTwtStatsListener, int sessionId) { 592 if (!isRegisteredInterface(interfaceName)) { 593 notifyFailure(iTwtStatsListener, CallbackType.STATS, 594 TwtSessionCallback.TWT_ERROR_CODE_NOT_AVAILABLE); 595 return; 596 } 597 598 if (!isSessionRegistered(sessionId)) { 599 notifyFailure(iTwtStatsListener, CallbackType.STATS, 600 TwtSessionCallback.TWT_ERROR_CODE_NOT_AVAILABLE); 601 return; 602 } 603 604 int id = registerCallback(iTwtStatsListener, TwtManager.CallbackType.STATS, 605 Binder.getCallingUid()); 606 if (id < 0) { 607 notifyFailure(iTwtStatsListener, CallbackType.STATS, 608 TwtSessionCallback.TWT_ERROR_CODE_NOT_AVAILABLE); 609 return; 610 } 611 if (!mWifiNative.getStatsTwtSession(id, interfaceName, sessionId)) { 612 unregisterCallback(id); 613 notifyFailure(iTwtStatsListener, CallbackType.STATS, 614 TwtSessionCallback.TWT_ERROR_CODE_NOT_AVAILABLE); 615 } 616 } 617 isEmpty()618 private boolean isEmpty() { 619 return (mCommandCallbacks.size() == 0 && mTwtSessionCallbacks.size() == 0); 620 } 621 reset()622 private void reset() { 623 if (isEmpty()) return; 624 stopGarbageCollector(); 625 // Notify failure for all pending callbacks 626 for (int i = 0; i < mCommandCallbacks.size(); ++i) { 627 Callback callback = mCommandCallbacks.valueAt(i); 628 if (!mTwtSessionCallbacks.contains(callback.mSessionId)) { 629 // Session cleanup will call Binder#unlinktoDeath() 630 callback.mCallback.asBinder().unlinkToDeath(callback, 0); 631 } 632 notifyFailure(callback.mCallback, callback.mType, 633 TwtSessionCallback.TWT_ERROR_CODE_FAIL); 634 } 635 // Teardown all active sessions 636 for (int i = 0; i < mTwtSessionCallbacks.size(); ++i) { 637 Callback callback = mTwtSessionCallbacks.valueAt(i); 638 callback.mCallback.asBinder().unlinkToDeath(callback, 0); 639 notifyTeardown((ITwtCallback) callback.mCallback, 640 TwtSessionCallback.TWT_REASON_CODE_INTERNALLY_INITIATED); 641 } 642 mCommandCallbacks.clear(); 643 mTwtSessionCallbacks.clear(); 644 mIdBitSet.clear(); 645 unregisterInterface(); 646 } 647 unregisterInterface()648 private void unregisterInterface() { 649 mInterfaceName = null; 650 } 651 registerInterface(String interfaceName)652 private boolean registerInterface(String interfaceName) { 653 if (interfaceName == null) return false; 654 if (mInterfaceName == null) { 655 mInterfaceName = interfaceName; 656 return true; 657 } 658 // Check if already registered to the same interface 659 if (interfaceName.equals(mInterfaceName)) { 660 return true; 661 } 662 Log.e(TAG, "Already registered to another interface " + mInterfaceName); 663 return false; 664 } 665 isRegisteredInterface(String interfaceName)666 private boolean isRegisteredInterface(String interfaceName) { 667 return (interfaceName != null && interfaceName.equals(mInterfaceName)); 668 } 669 } 670