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