summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Mahesh KKV <maheshkkv@google.com> 2024-02-13 02:41:43 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-02-13 02:41:43 +0000
commit011be1e351bebfb301b6c2dd3a9e787706b36789 (patch)
treefa9807ff927d868b57e540d4af1cf0556dc7ec66
parent4e6174b26b8bcc470ffbcac894af9538d137bd4e (diff)
parentcce7375b33a6e71b2dd6459840afbd780b3b0038 (diff)
Merge "Add Target Wake Time (TWT) APIs" into main
-rw-r--r--apex/Android.bp1
-rw-r--r--framework/aidl-export/android/net/wifi/twt/TwtRequest.aidl19
-rw-r--r--framework/api/current.txt1
-rw-r--r--framework/api/system-current.txt61
-rw-r--r--framework/java/android/net/wifi/BaseWifiService.java22
-rw-r--r--framework/java/android/net/wifi/ITwtCallback.aidl53
-rw-r--r--framework/java/android/net/wifi/ITwtCapabilitiesListener.aidl29
-rw-r--r--framework/java/android/net/wifi/ITwtStatsListener.aidl29
-rw-r--r--framework/java/android/net/wifi/IWifiManager.aidl13
-rw-r--r--framework/java/android/net/wifi/ScanResult.java24
-rw-r--r--framework/java/android/net/wifi/WifiManager.java263
-rw-r--r--framework/java/android/net/wifi/WifiTwtSession.java125
-rw-r--r--framework/java/android/net/wifi/twt/TwtCallback.java138
-rw-r--r--framework/java/android/net/wifi/twt/TwtRequest.java213
-rw-r--r--framework/java/android/net/wifi/twt/TwtSession.java129
-rw-r--r--framework/tests/src/android/net/wifi/ScanResultTest.java3
-rw-r--r--framework/tests/src/android/net/wifi/WifiManagerTest.java73
-rw-r--r--service/java/com/android/server/wifi/ScanDetail.java3
-rw-r--r--service/java/com/android/server/wifi/WifiServiceImpl.java60
19 files changed, 1258 insertions, 1 deletions
diff --git a/apex/Android.bp b/apex/Android.bp
index e64cef5fc2..aeffc86b3f 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -123,6 +123,7 @@ bootclasspath_fragment {
"android.net.wifi.hotspot2",
"android.net.wifi.p2p",
"android.net.wifi.rtt",
+ "android.net.wifi.twt",
"android.net.wifi.util",
"com.android.wifi",
],
diff --git a/framework/aidl-export/android/net/wifi/twt/TwtRequest.aidl b/framework/aidl-export/android/net/wifi/twt/TwtRequest.aidl
new file mode 100644
index 0000000000..59b415855f
--- /dev/null
+++ b/framework/aidl-export/android/net/wifi/twt/TwtRequest.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.twt;
+
+parcelable TwtRequest;
diff --git a/framework/api/current.txt b/framework/api/current.txt
index 284f246665..7c568ca685 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -55,6 +55,7 @@ package android.net.wifi {
method @FlaggedApi("com.android.wifi.flags.rtt_11az_ntb_ranging_support") public boolean is80211azNtbResponder();
method public boolean is80211mcResponder();
method public boolean isPasspointNetwork();
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public boolean isTwtResponder();
method public void writeToParcel(android.os.Parcel, int);
field public String BSSID;
field public static final int CHANNEL_WIDTH_160MHZ = 3; // 0x3
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index 08ed7c6c6a..c26b30e833 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -729,6 +729,7 @@ package android.net.wifi {
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.OVERRIDE_WIFI_CONFIG}) public android.net.wifi.SoftApConfiguration getSoftApConfiguration();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.MANAGE_WIFI_NETWORK_SELECTION}, conditional=true) public java.util.Set<android.net.wifi.WifiSsid> getSsidsAllowlist();
method @FlaggedApi("com.android.wifi.flags.mlo_link_capabilities_info") @RequiresPermission(android.Manifest.permission.MANAGE_WIFI_NETWORK_SELECTION) public void getSupportedSimultaneousBandCombinations(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<int[]>>);
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") @RequiresPermission(android.Manifest.permission.MANAGE_WIFI_NETWORK_SELECTION) public void getTwtCapabilities(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.os.Bundle>);
method public int getVerboseLoggingLevel();
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void getWifiActivityEnergyInfoAsync(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiActivityEnergyInfoListener);
method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration();
@@ -796,6 +797,7 @@ package android.net.wifi {
method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public boolean setWifiConnectedNetworkScorer(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.WifiConnectedNetworkScorer);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void setWifiPasspointEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public boolean setWifiScoringEnabled(boolean);
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") @RequiresPermission(android.Manifest.permission.MANAGE_WIFI_NETWORK_SELECTION) public void setupTwtSession(@NonNull android.net.wifi.twt.TwtRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.twt.TwtCallback);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsConfiguratorInitiator(@NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsEnrolleeInitiator(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsEnrolleeResponder(@Nullable String, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
@@ -914,6 +916,11 @@ package android.net.wifi {
field public static final int SAP_START_FAILURE_NO_CHANNEL = 1; // 0x1
field public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2; // 0x2
field public static final int SAP_START_FAILURE_USER_REJECTED = 3; // 0x3
+ field @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public static final String TWT_CAPABILITIES_KEY_BOOLEAN_TWT_REQUESTER = "key_requester";
+ field @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public static final String TWT_CAPABILITIES_KEY_INT_MAX_WAKE_DURATION_MICROS = "key_max_wake_duration";
+ field @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public static final String TWT_CAPABILITIES_KEY_INT_MIN_WAKE_DURATION_MICROS = "key_min_wake_duration";
+ field @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public static final String TWT_CAPABILITIES_KEY_LONG_MAX_WAKE_INTERVAL_MICROS = "key_max_wake_interval";
+ field @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public static final String TWT_CAPABILITIES_KEY_LONG_MIN_WAKE_INTERVAL_MICROS = "key_min_wake_interval";
field public static final int VERBOSE_LOGGING_LEVEL_DISABLED = 0; // 0x0
field public static final int VERBOSE_LOGGING_LEVEL_ENABLED = 1; // 0x1
field public static final int VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY = 2; // 0x2
@@ -1697,3 +1704,57 @@ package android.net.wifi.rtt {
}
+package android.net.wifi.twt {
+
+ @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public interface TwtCallback {
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public void onCreate(@NonNull android.net.wifi.twt.TwtSession);
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public void onFailure(int);
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public void onTeardown(int);
+ field public static final int TWT_ERROR_CODE_AP_NOT_SUPPORTED = 1; // 0x1
+ field public static final int TWT_ERROR_CODE_AP_OUI_BLOCKLISTED = 2; // 0x2
+ field public static final int TWT_ERROR_CODE_AP_REJECTED = 3; // 0x3
+ field public static final int TWT_ERROR_CODE_FAIL = 0; // 0x0
+ field public static final int TWT_ERROR_CODE_INVALID_PARAMS = 4; // 0x4
+ field public static final int TWT_ERROR_CODE_MAX_SESSIONS_REACHED = 5; // 0x5
+ field public static final int TWT_ERROR_CODE_NOT_AVAILABLE = 6; // 0x6
+ field public static final int TWT_ERROR_CODE_NOT_SUPPORTED = 7; // 0x7
+ field public static final int TWT_ERROR_CODE_TIMEOUT = 8; // 0x8
+ field public static final int TWT_REASON_CODE_INTERNALLY_INITIATED = 2; // 0x2
+ field public static final int TWT_REASON_CODE_LOCALLY_REQUESTED = 1; // 0x1
+ field public static final int TWT_REASON_CODE_PEER_INITIATED = 3; // 0x3
+ field public static final int TWT_REASON_CODE_UNKNOWN = 0; // 0x0
+ }
+
+ @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public final class TwtRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") @IntRange(from=android.net.wifi.MloLink.INVALID_MLO_LINK_ID, to=0xf) public int getLinkId();
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public int getMaxWakeDurationMicros();
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public long getMaxWakeIntervalMicros();
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public int getMinWakeDurationMicros();
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public long getMinWakeIntervalMicros();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.twt.TwtRequest> CREATOR;
+ }
+
+ @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public static final class TwtRequest.Builder {
+ ctor @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public TwtRequest.Builder(int, int, long, long);
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") @NonNull public android.net.wifi.twt.TwtRequest build();
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") @IntRange(from=0x0, to=0xf) @NonNull public android.net.wifi.twt.TwtRequest.Builder setLinkId(int);
+ }
+
+ @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public interface TwtSession {
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public int getMloLinkId();
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public void getStats(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.os.Bundle>);
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public int getWakeDurationMicros();
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public long getWakeIntervalMicros();
+ method @FlaggedApi("com.android.wifi.flags.android_v_wifi_api") public void teardown();
+ field public static final String TWT_STATS_KEY_INT_AVERAGE_EOSP_DURATION_MICROS = "key_avg_eosp_dur";
+ field public static final String TWT_STATS_KEY_INT_AVERAGE_RX_PACKET_COUNT = "key_avg_rx_pkt_count";
+ field public static final String TWT_STATS_KEY_INT_AVERAGE_RX_PACKET_SIZE = "key_avg_rx_pkt_size";
+ field public static final String TWT_STATS_KEY_INT_AVERAGE_TX_PACKET_COUNT = "key_avg_tx_pkt_count";
+ field public static final String TWT_STATS_KEY_INT_AVERAGE_TX_PACKET_SIZE = "key_avg_tx_pkt_size";
+ field public static final String TWT_STATS_KEY_INT_EOSP_COUNT = "key_eosp_count";
+ }
+
+}
+
diff --git a/framework/java/android/net/wifi/BaseWifiService.java b/framework/java/android/net/wifi/BaseWifiService.java
index 5e1934e6b6..0cf4203527 100644
--- a/framework/java/android/net/wifi/BaseWifiService.java
+++ b/framework/java/android/net/wifi/BaseWifiService.java
@@ -24,6 +24,7 @@ import android.net.Network;
import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.twt.TwtRequest;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -1125,4 +1126,25 @@ public class BaseWifiService extends IWifiManager.Stub {
@NonNull IIntegerListener listener) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public void setupTwtSession(TwtRequest twtRequest, ITwtCallback iTwtCallback, Bundle extras) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void getTwtCapabilities(ITwtCapabilitiesListener listener, Bundle extras) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void getStatsTwtSession(int sessionId, ITwtStatsListener iTwtStatsListener,
+ Bundle extras) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void teardownTwtSession(int sessionId, Bundle extras) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/framework/java/android/net/wifi/ITwtCallback.aidl b/framework/java/android/net/wifi/ITwtCallback.aidl
new file mode 100644
index 0000000000..8adf79ad68
--- /dev/null
+++ b/framework/java/android/net/wifi/ITwtCallback.aidl
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+/**
+ * Interface for target wake time (TWT) callback
+ *
+ * @hide
+ */
+oneway interface ITwtCallback
+{
+ /**
+ * Called when a TWT session setup operation fails.
+ *
+ * @param errorCode setup error code
+ * @hide
+ */
+ void onFailure(int errorCode);
+ /**
+ * Called when a TWT session is torndown. Can be called as a response to
+ * {@link TwtSession#teardown()} or unsolicited. Check the {@link TwtReasonCode} for more
+ * details.
+ *
+ * @param reasonCode Teardown reason code
+ * @hide
+ */
+ void onTeardown(int reasonCode);
+ /**
+ * Called when the TWT session is created.
+ *
+ * @param wakeDuration TWT session wake duration
+ * @param wakeInterval TWT session wake interval
+ * @param mloLinkId Multi link operation link id
+ * @param owner Owner of this session
+ * @param sessionId TWT session id
+ * @hide
+ */
+ void onCreate(int wakeDuration, long wakeInterval, int mloLinkId, int owner, int sessionId);
+}
diff --git a/framework/java/android/net/wifi/ITwtCapabilitiesListener.aidl b/framework/java/android/net/wifi/ITwtCapabilitiesListener.aidl
new file mode 100644
index 0000000000..d0d93c1d7e
--- /dev/null
+++ b/framework/java/android/net/wifi/ITwtCapabilitiesListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.os.Bundle;
+
+/**
+ * Interface for TwtCapabilities callback
+ *
+ * @hide
+ */
+oneway interface ITwtCapabilitiesListener
+{
+ void onResult(in Bundle value);
+}
diff --git a/framework/java/android/net/wifi/ITwtStatsListener.aidl b/framework/java/android/net/wifi/ITwtStatsListener.aidl
new file mode 100644
index 0000000000..3b286f21c0
--- /dev/null
+++ b/framework/java/android/net/wifi/ITwtStatsListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.os.Bundle;
+
+/**
+ * Interface for TwtStats callback
+ *
+ * @hide
+ */
+oneway interface ITwtStatsListener
+{
+ void onResult(in Bundle value);
+}
diff --git a/framework/java/android/net/wifi/IWifiManager.aidl b/framework/java/android/net/wifi/IWifiManager.aidl
index 0801df986c..6da558be0b 100644
--- a/framework/java/android/net/wifi/IWifiManager.aidl
+++ b/framework/java/android/net/wifi/IWifiManager.aidl
@@ -47,6 +47,9 @@ import android.net.wifi.ISubsystemRestartCallback;
import android.net.wifi.ISuggestionConnectionStatusListener;
import android.net.wifi.ISuggestionUserApprovalStatusListener;
import android.net.wifi.ITrafficStateCallback;
+import android.net.wifi.ITwtCallback;
+import android.net.wifi.ITwtCapabilitiesListener;
+import android.net.wifi.ITwtStatsListener;
import android.net.wifi.IWifiBandsListener;
import android.net.wifi.IWifiConnectedNetworkScorer;
import android.net.wifi.IWifiLowLatencyLockListener;
@@ -64,6 +67,8 @@ import android.net.wifi.WifiNetworkSelectionConfig;
import android.net.wifi.WifiNetworkSuggestion;
import android.net.wifi.WifiSsid;
+import android.net.wifi.twt.TwtRequest;
+
import android.os.Bundle;
import android.os.Messenger;
import android.os.ResultReceiver;
@@ -481,4 +486,12 @@ interface IWifiManager
void setSendDhcpHostnameRestriction(String packageName, int restriction);
void querySendDhcpHostnameRestriction(String packageName, in IIntegerListener listener);
+
+ void getTwtCapabilities(in ITwtCapabilitiesListener listener, in Bundle extras);
+
+ void setupTwtSession(in TwtRequest twtRequest, in ITwtCallback callback, in Bundle extras);
+
+ void getStatsTwtSession(in int sessionId, in ITwtStatsListener listener, in Bundle extras);
+
+ void teardownTwtSession(in int sessionId, in Bundle extras);
}
diff --git a/framework/java/android/net/wifi/ScanResult.java b/framework/java/android/net/wifi/ScanResult.java
index 377ae1ee1d..0d29709b96 100644
--- a/framework/java/android/net/wifi/ScanResult.java
+++ b/framework/java/android/net/wifi/ScanResult.java
@@ -33,6 +33,7 @@ import android.os.Parcelable;
import android.util.Log;
import com.android.modules.utils.build.SdkLevel;
+import com.android.wifi.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -733,6 +734,8 @@ public final class ScanResult implements Parcelable {
/** @hide */
public static final long FLAG_80211az_NTB_RESPONDER = 0x0000000000000004;
+ /** @hide */
+ public static final long FLAG_TWT_RESPONDER = 0x0000000000000008;
/*
* These flags are specific to the ScanResult class, and are not related to the |flags|
* field of the per-BSS scan results from WPA supplicant.
@@ -779,6 +782,14 @@ public final class ScanResult implements Parcelable {
}
/**
+ * @return whether AP is Target Wake Time (TWT) Responder.
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ public boolean isTwtResponder() {
+ return (flags & FLAG_TWT_RESPONDER) != 0;
+ }
+
+ /**
* Indicates venue name (such as 'San Francisco Airport') published by access point; only
* available on Passpoint network and if published by access point.
* @deprecated - This information is not provided
@@ -1444,7 +1455,7 @@ public final class ScanResult implements Parcelable {
private int mCenterFreq1 = UNSPECIFIED;
private boolean mIs80211McRTTResponder = false;
private boolean mIs80211azNtbRTTResponder = false;
-
+ private boolean mIsTwtResponder = false;
/** @hide */
@NonNull
public Builder setHessid(long hessid) {
@@ -1544,6 +1555,13 @@ public final class ScanResult implements Parcelable {
}
/** @hide */
+ @NonNull
+ public Builder setIsTwtResponder(boolean isTwtResponder) {
+ mIsTwtResponder = isTwtResponder;
+ return this;
+ }
+
+ /** @hide */
public Builder(WifiSsid wifiSsid, String bssid) {
mWifiSsid = wifiSsid;
mBssid = bssid;
@@ -1593,6 +1611,7 @@ public final class ScanResult implements Parcelable {
mCenterFreq1 = UNSPECIFIED;
mIs80211McRTTResponder = false;
mIs80211azNtbRTTResponder = false;
+ mIsTwtResponder = false;
}
/** @hide */
@@ -1634,6 +1653,7 @@ public final class ScanResult implements Parcelable {
this.flags = 0;
this.flags |= (builder.mIs80211McRTTResponder) ? FLAG_80211mc_RESPONDER : 0;
this.flags |= (builder.mIs80211azNtbRTTResponder) ? FLAG_80211az_NTB_RESPONDER : 0;
+ this.flags |= (builder.mIsTwtResponder) ? FLAG_TWT_RESPONDER : 0;
this.radioChainInfos = null;
this.mApMldMacAddress = null;
}
@@ -1819,6 +1839,8 @@ public final class ScanResult implements Parcelable {
sb.append(", 80211azNtbResponder: ");
sb.append(
((flags & FLAG_80211az_NTB_RESPONDER) != 0) ? "is supported" : "is not supported");
+ sb.append(", TWT Responder: ");
+ sb.append(((flags & FLAG_TWT_RESPONDER) != 0) ? "yes" : "no");
sb.append(", Radio Chain Infos: ").append(Arrays.toString(radioChainInfos));
sb.append(", interface name: ").append(ifaceName);
diff --git a/framework/java/android/net/wifi/WifiManager.java b/framework/java/android/net/wifi/WifiManager.java
index 396dc82d5d..6b51cff916 100644
--- a/framework/java/android/net/wifi/WifiManager.java
+++ b/framework/java/android/net/wifi/WifiManager.java
@@ -36,6 +36,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.StringDef;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
@@ -63,6 +64,9 @@ import android.net.wifi.hotspot2.ProvisioningCallback;
import android.net.wifi.p2p.WifiP2pConfig;
import android.net.wifi.p2p.WifiP2pDiscoveryConfig;
import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.twt.TwtCallback;
+import android.net.wifi.twt.TwtRequest;
+import android.net.wifi.twt.TwtSession;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -12224,4 +12228,263 @@ public class WifiManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Bundle key to check target wake time requester mode supported or not
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ public static final String TWT_CAPABILITIES_KEY_BOOLEAN_TWT_REQUESTER =
+ "key_requester";
+
+ /**
+ * Bundle key to get minimum wake duration supported in microseconds
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ public static final String TWT_CAPABILITIES_KEY_INT_MIN_WAKE_DURATION_MICROS =
+ "key_min_wake_duration";
+ /**
+ * Bundle key to get maximum wake duration supported in microseconds
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ public static final String TWT_CAPABILITIES_KEY_INT_MAX_WAKE_DURATION_MICROS =
+ "key_max_wake_duration";
+ /**
+ * Bundle key to get minimum wake interval supported in microseconds
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ public static final String TWT_CAPABILITIES_KEY_LONG_MIN_WAKE_INTERVAL_MICROS =
+ "key_min_wake_interval";
+ /**
+ * Bundle key to get maximum wake interval supported in microseconds
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ public static final String TWT_CAPABILITIES_KEY_LONG_MAX_WAKE_INTERVAL_MICROS =
+ "key_max_wake_interval";
+
+ /** @hide */
+ @StringDef(prefix = { "TWT_CAPABILITIES_KEY_"}, value = {
+ TWT_CAPABILITIES_KEY_BOOLEAN_TWT_REQUESTER,
+ TWT_CAPABILITIES_KEY_INT_MIN_WAKE_DURATION_MICROS,
+ TWT_CAPABILITIES_KEY_INT_MAX_WAKE_DURATION_MICROS,
+ TWT_CAPABILITIES_KEY_LONG_MIN_WAKE_INTERVAL_MICROS,
+ TWT_CAPABILITIES_KEY_LONG_MAX_WAKE_INTERVAL_MICROS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TwtCapabilities {}
+
+ /**
+ * Get target wake time (TWT) capabilities of the primary station interface.
+ *
+ * Note: Target wake time feature is only supported for primary station. If Wi-Fi is off or the
+ * capability is not available the asynchronous callback will be called with the bundle
+ * with values { false, -1, -1, -1, -1 }.
+ *
+ * @param executor Executor to execute listener callback
+ * @param resultCallback An asynchronous callback that will return a bundle for target wake time
+ * capabilities. See {@link TwtCapabilities} for the string keys for
+ * the bundle.
+ * @throws SecurityException if the caller does not have permission.
+ * @throws NullPointerException if the caller provided null inputs.
+ * @throws UnsupportedOperationException if the API is not supported.
+ * @hide
+ */
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @RequiresPermission(MANAGE_WIFI_NETWORK_SELECTION)
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ public void getTwtCapabilities(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Bundle> resultCallback) {
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(resultCallback, "resultCallback cannot be null");
+ if (!SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ Bundle extras = new Bundle();
+ extras.putParcelable(EXTRA_PARAM_KEY_ATTRIBUTION_SOURCE,
+ mContext.getAttributionSource());
+ mService.getTwtCapabilities(
+ new ITwtCapabilitiesListener.Stub() {
+ @Override
+ public void onResult(Bundle value) {
+ Binder.clearCallingIdentity();
+ executor.execute(() -> {
+ resultCallback.accept(value);
+ });
+ }
+ }, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private class TwtCallbackProxy extends ITwtCallback.Stub {
+ private final Executor mExecutor;
+ private final TwtCallback mCallback;
+
+ private TwtCallbackProxy(Executor executor, TwtCallback callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onFailure(@TwtCallback.TwtErrorCode int errorCode) throws RemoteException {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "TwtCallbackProxy: onFailure(errorCode = " + errorCode + " )");
+ }
+ Binder.clearCallingIdentity();
+ mExecutor.execute(() -> mCallback.onFailure(errorCode));
+ }
+
+ @Override
+ public void onTeardown(@TwtCallback.TwtReasonCode int reasonCode)
+ throws RemoteException {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "TwtCallbackProxy: onTeardown(errorCode = " + reasonCode + " )");
+ }
+ Binder.clearCallingIdentity();
+ mExecutor.execute(() -> mCallback.onTeardown(reasonCode));
+ }
+
+ @Override
+ public void onCreate(int wakeDuration, long wakeInterval, int mloLinkId, int owner,
+ int sessionId) throws RemoteException {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "TwtCallbackProxy: onCreate " + sessionId);
+ }
+
+ WifiTwtSession wifiTwtSession = new WifiTwtSession(WifiManager.this, wakeDuration,
+ wakeInterval, mloLinkId, owner, sessionId);
+ Binder.clearCallingIdentity();
+ mExecutor.execute(() -> mCallback.onCreate(wifiTwtSession));
+ }
+ }
+
+ /**
+ * Set up a TWT session with a TWT responder capable AP. Only supported for primary connected
+ * station which is a TWT requester. See {@link #getTwtCapabilities(Executor, Consumer)} and
+ * {@link ScanResult#isTwtResponder()} to check station and AP support.
+ *
+ * Following callbacks are invoked,
+ * - {@link TwtCallback#onFailure(int)} upon error with error code.
+ * - {@link TwtCallback#onCreate(TwtSession)} upon TWT session creation.
+ * - {@link TwtCallback#onTeardown(int)} upon TWT session teardown.
+ *
+ * Note: {@link #getTwtCapabilities(Executor, Consumer)} gives {@link TwtCapabilities} which can
+ * be used to fill in the valid TWT wake interval and duration ranges for {@link TwtRequest}.
+ *
+ * @param twtRequest TWT request
+ * @param executor Executor to execute listener callback on
+ * @param callback Callback to register
+ * @throws SecurityException if the caller does not have permission.
+ * @throws NullPointerException if the caller provided null inputs.
+ * @throws UnsupportedOperationException if the API is not supported.
+ * @hide
+ */
+ @SystemApi
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @RequiresPermission(MANAGE_WIFI_NETWORK_SELECTION)
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ public void setupTwtSession(@NonNull TwtRequest twtRequest,
+ @NonNull @CallbackExecutor Executor executor, @NonNull TwtCallback callback) {
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
+ Objects.requireNonNull(twtRequest, "twtRequest cannot be null");
+ if (!SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ ITwtCallback.Stub binderCallback = new TwtCallbackProxy(executor, callback);
+ Bundle extras = new Bundle();
+ extras.putParcelable(EXTRA_PARAM_KEY_ATTRIBUTION_SOURCE,
+ mContext.getAttributionSource());
+ mService.setupTwtSession(twtRequest, binderCallback, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get stats of the target wake time session.
+ *
+ * Note: For Internal use only. Expected to be called through
+ * {@link TwtSession#getStats(Executor, Consumer)}. If the command fails, -1 will be returned
+ * for all stats values.
+ *
+ * @param sessionId TWT session id
+ * @param executor The executor on which callback will be invoked.
+ * @param resultCallback The asynchronous callback that will return bundle with key string
+ * {@link TwtSession.TwtStats}.
+ *
+ * @throws SecurityException if the caller does not have permission.
+ * @throws NullPointerException if the caller provided null inputs.
+ * @throws UnsupportedOperationException if the API is not supported or primary station is
+ * not connected.
+ * @hide
+ */
+ public void getStatsTwtSession(@NonNull int sessionId, @NonNull Executor executor,
+ @NonNull Consumer<Bundle> resultCallback) {
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(resultCallback, "resultsCallback cannot be null");
+ if (!SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ Bundle extras = new Bundle();
+ if (SdkLevel.isAtLeastS()) {
+ extras.putParcelable(EXTRA_PARAM_KEY_ATTRIBUTION_SOURCE,
+ mContext.getAttributionSource());
+ }
+ mService.getStatsTwtSession(sessionId,
+ new ITwtStatsListener.Stub() {
+ @Override
+ public void onResult(Bundle value) {
+ Binder.clearCallingIdentity();
+ executor.execute(() -> {
+ resultCallback.accept(value);
+ });
+ }
+ }, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Teardown the target wake time session. Only owner can teardown the session.
+ *
+ * Note: For internal use only. Expected to be called through
+ * {@link TwtCallback#onTeardown(int)}.
+ *
+ * @param sessionId TWT session id
+ * @throws SecurityException if the caller does not have permission.
+ * @throws UnsupportedOperationException if the API is not supported or primary station is not
+ * connected.
+ * @hide
+ */
+ public void teardownTwtSession(int sessionId) {
+ if (!SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ Bundle extras = new Bundle();
+ if (SdkLevel.isAtLeastS()) {
+ extras.putParcelable(EXTRA_PARAM_KEY_ATTRIBUTION_SOURCE,
+ mContext.getAttributionSource());
+ }
+ mService.teardownTwtSession(sessionId, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/framework/java/android/net/wifi/WifiTwtSession.java b/framework/java/android/net/wifi/WifiTwtSession.java
new file mode 100644
index 0000000000..5c3b25685a
--- /dev/null
+++ b/framework/java/android/net/wifi/WifiTwtSession.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.wifi;
+
+import android.net.wifi.twt.TwtSession;
+import android.os.Binder;
+import android.os.Bundle;
+import android.util.CloseGuard;
+import android.util.Log;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Implementation of the interface {@link TwtSession}
+ *
+ * @hide
+ */
+public class WifiTwtSession implements TwtSession, AutoCloseable {
+ private static final String TAG = "WifiTwtSession";
+ public static final int MAX_TWT_SESSIONS = 8;
+ private final int mWakeDurationMicros;
+ private final long mWakeIntervalMicros;
+ private final int mMloLinkId;
+ private final int mOwner;
+ private final int mSessionId;
+ private final WeakReference<WifiManager> mMgr;
+ private final CloseGuard mCloseGuard = new CloseGuard();
+
+ @Override
+ public int getWakeDurationMicros() {
+ return mWakeDurationMicros;
+ }
+
+ @Override
+ public long getWakeIntervalMicros() {
+ return mWakeIntervalMicros;
+ }
+
+ @Override
+ public int getMloLinkId() {
+ return mMloLinkId;
+ }
+
+ public int getOwner() {
+ return mOwner;
+ }
+
+ public int getSessionId() {
+ return mSessionId;
+ }
+
+ @Override
+ public void getStats(Executor executor, Consumer<Bundle> resultCallback) {
+ WifiManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.e(TAG, "getStats: called post garbage collection");
+ return;
+ }
+ if (Binder.getCallingUid() != mOwner) {
+ throw new SecurityException("TWT session is not owned by the caller");
+ }
+ mgr.getStatsTwtSession(mSessionId, executor, resultCallback);
+ }
+
+ @Override
+ public void teardown() {
+ WifiManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.e(TAG, "getStats: called post garbage collection");
+ return;
+ }
+ if (Binder.getCallingUid() != mOwner) {
+ throw new SecurityException("TWT session is not owned by the caller");
+ }
+ close();
+ }
+
+ public WifiTwtSession(WifiManager wifiManager, int wakeDurationMicros, long wakeIntervalMicros,
+ int mloLinkId, int owner, int sessionId) {
+ mMgr = new WeakReference<>(wifiManager);
+ mWakeDurationMicros = wakeDurationMicros;
+ mWakeIntervalMicros = wakeIntervalMicros;
+ mMloLinkId = mloLinkId;
+ mOwner = owner;
+ mSessionId = sessionId;
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * Closes this resource, relinquishing any underlying resources.
+ */
+ @Override
+ public void close() {
+ try {
+ WifiManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "close: called post garbage collection");
+ return;
+ }
+ mgr.teardownTwtSession(mSessionId);
+ mMgr.clear();
+ mCloseGuard.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to close WifiTwtSession.");
+ } finally {
+ Reference.reachabilityFence(this);
+ }
+ }
+}
diff --git a/framework/java/android/net/wifi/twt/TwtCallback.java b/framework/java/android/net/wifi/twt/TwtCallback.java
new file mode 100644
index 0000000000..9cbc2f1fb7
--- /dev/null
+++ b/framework/java/android/net/wifi/twt/TwtCallback.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.twt;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.wifi.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * API interface for target wake time (TWT) Callback.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+public interface TwtCallback {
+ /**
+ * Generic error
+ */
+ int TWT_ERROR_CODE_FAIL = 0;
+ /**
+ * AP does not support TWT
+ */
+ int TWT_ERROR_CODE_AP_NOT_SUPPORTED = 1;
+ /**
+ * AP is blocklisted due to interoperability issue reported with TWT
+ */
+ int TWT_ERROR_CODE_AP_OUI_BLOCKLISTED = 2;
+ /**
+ * AP rejects TWT request
+ */
+ int TWT_ERROR_CODE_AP_REJECTED = 3;
+ /**
+ * Invalid parameters
+ */
+ int TWT_ERROR_CODE_INVALID_PARAMS = 4;
+ /**
+ * Maximum TWT sessions reached
+ */
+ int TWT_ERROR_CODE_MAX_SESSIONS_REACHED = 5;
+ /**
+ * TWT is not available now
+ */
+ int TWT_ERROR_CODE_NOT_AVAILABLE = 6;
+ /**
+ * TWT is not supported by the local device
+ */
+ int TWT_ERROR_CODE_NOT_SUPPORTED = 7;
+ /**
+ * TWT operation Timed out
+ */
+ int TWT_ERROR_CODE_TIMEOUT = 8;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"TWT_ERROR_CODE_"}, value = {TWT_ERROR_CODE_FAIL,
+ TWT_ERROR_CODE_AP_NOT_SUPPORTED, TWT_ERROR_CODE_AP_OUI_BLOCKLISTED,
+ TWT_ERROR_CODE_AP_REJECTED, TWT_ERROR_CODE_INVALID_PARAMS,
+ TWT_ERROR_CODE_MAX_SESSIONS_REACHED, TWT_ERROR_CODE_NOT_AVAILABLE,
+ TWT_ERROR_CODE_NOT_SUPPORTED, TWT_ERROR_CODE_TIMEOUT})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface TwtErrorCode {
+ }
+
+ /**
+ * Unknown reason code
+ */
+ int TWT_REASON_CODE_UNKNOWN = 0;
+ /**
+ * Locally requested
+ */
+ int TWT_REASON_CODE_LOCALLY_REQUESTED = 1;
+ /**
+ * Internally initiated by the driver or firmware
+ */
+ int TWT_REASON_CODE_INTERNALLY_INITIATED = 2;
+ /**
+ * Peer initiated
+ */
+ int TWT_REASON_CODE_PEER_INITIATED = 3;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"TWT_REASON_CODE_"}, value = {TWT_REASON_CODE_UNKNOWN,
+ TWT_REASON_CODE_LOCALLY_REQUESTED, TWT_REASON_CODE_INTERNALLY_INITIATED,
+ TWT_REASON_CODE_PEER_INITIATED})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface TwtReasonCode {
+ }
+
+ /**
+ * Called when a TWT operation fails.
+ *
+ * @param errorCode error code
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ void onFailure(@TwtCallback.TwtErrorCode int errorCode);
+
+ /**
+ * Called when a TWT session is torn down. Can be called as a response to
+ * {@link TwtSession#teardown()} or unsolicited. Check the {@link TwtReasonCode} for more
+ * details.
+ *
+ * @param reasonCode reason for TWT session teardown
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ void onTeardown(@TwtCallback.TwtReasonCode int reasonCode);
+
+ /**
+ * Called when the TWT session is created.
+ *
+ * @param twtSession TWT session
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ void onCreate(@NonNull TwtSession twtSession);
+}
diff --git a/framework/java/android/net/wifi/twt/TwtRequest.java b/framework/java/android/net/wifi/twt/TwtRequest.java
new file mode 100644
index 0000000000..476366a4b3
--- /dev/null
+++ b/framework/java/android/net/wifi/twt/TwtRequest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.twt;
+
+import static android.net.wifi.MloLink.INVALID_MLO_LINK_ID;
+import static android.net.wifi.MloLink.MAX_MLO_LINK_ID;
+import static android.net.wifi.MloLink.MIN_MLO_LINK_ID;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.wifi.MloLink;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.wifi.flags.Flags;
+
+/**
+ * Defines target wake time (TWT) request class.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+public final class TwtRequest implements Parcelable {
+ private final int mMinWakeDurationMicros;
+ private final int mMaxWakeDurationMicros;
+ private final long mMinWakeIntervalMicros;
+ private final long mMaxWakeIntervalMicros;
+ private final int mLinkId;
+
+ private TwtRequest(TwtRequest.Builder builder) {
+ mMinWakeDurationMicros = builder.mMaxWakeDurationMicros;
+ mMaxWakeDurationMicros = builder.mMinWakeDurationMicros;
+ mMinWakeIntervalMicros = builder.mMaxWakeIntervalMicros;
+ mMaxWakeIntervalMicros = builder.mMinWakeIntervalMicros;
+ mLinkId = builder.mLinkId;
+ }
+
+ /**
+ * Get minimum TWT wake duration in microseconds.
+ *
+ * @return Minimum wake duration in microseconds
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ public int getMinWakeDurationMicros() {
+ return mMinWakeDurationMicros;
+ }
+
+ /**
+ * Get maximum TWT wake duration in microseconds.
+ *
+ * @return Maximum wake duration in microseconds
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ public int getMaxWakeDurationMicros() {
+ return mMaxWakeDurationMicros;
+ }
+
+ /**
+ * Get minimum TWT wake interval in microseconds.
+ *
+ * @return Minimum wake interval in microseconds
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ public long getMinWakeIntervalMicros() {
+ return mMinWakeIntervalMicros;
+ }
+
+ /**
+ * Get maximum TWT wake interval in microseconds.
+ *
+ * @return Maximum wake interval in microseconds
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ public long getMaxWakeIntervalMicros() {
+ return mMaxWakeIntervalMicros;
+ }
+
+
+ /**
+ * Get link id (valid only in case of Multi-link operation).
+ *
+ * @return MLO link id in the range {@link MloLink#MIN_MLO_LINK_ID} to
+ * {@link MloLink#MAX_MLO_LINK_ID}. Returns {@link MloLink#INVALID_MLO_LINK_ID} if not set.
+ */
+ @IntRange(from = INVALID_MLO_LINK_ID, to = MAX_MLO_LINK_ID)
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ public int getLinkId() {
+ return mLinkId;
+ }
+
+ @NonNull
+ public static final Creator<TwtRequest> CREATOR = new Creator<TwtRequest>() {
+ @Override
+ public TwtRequest createFromParcel(Parcel in) {
+ Builder builder = new TwtRequest.Builder(in.readInt(), in.readInt(), in.readLong(),
+ in.readLong());
+ int mloLinkId = in.readInt();
+ if (mloLinkId >= MIN_MLO_LINK_ID && mloLinkId <= MAX_MLO_LINK_ID) {
+ builder.setLinkId(mloLinkId);
+ }
+ return builder.build();
+ }
+
+ @Override
+ public TwtRequest[] newArray(int size) {
+ return new TwtRequest[size];
+ }
+ };
+
+
+ /**
+ * Describe the kinds of special objects contained in this Parcelable
+ * instance's marshaled representation.
+ *
+ * @return a bitmask indicating the set of special object types marshaled
+ * by this Parcelable object instance.
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Flatten this object in to a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mMinWakeDurationMicros);
+ dest.writeInt(mMaxWakeDurationMicros);
+ dest.writeLong(mMinWakeIntervalMicros);
+ dest.writeLong(mMinWakeIntervalMicros);
+ dest.writeInt(mLinkId);
+ }
+
+ /**
+ * Builder class used to construct {@link TwtRequest} objects.
+ *
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ public static final class Builder {
+ private final int mMinWakeDurationMicros;
+ private final int mMaxWakeDurationMicros;
+ private final long mMinWakeIntervalMicros;
+ private final long mMaxWakeIntervalMicros;
+ private int mLinkId = INVALID_MLO_LINK_ID;
+
+ /**
+ * Set link id (valid only in case of Multi-link operation).
+ *
+ * @param linkId Link id, which should be in the range {@link MloLink#MIN_MLO_LINK_ID} to
+ * {@link MloLink#MAX_MLO_LINK_ID}
+ * @return The builder to facilitate chaining
+ * @throws IllegalArgumentException if argument is invalid
+ */
+ @IntRange(from = MIN_MLO_LINK_ID, to = MAX_MLO_LINK_ID)
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ @NonNull
+ public TwtRequest.Builder setLinkId(int linkId) {
+ if (linkId < MIN_MLO_LINK_ID || linkId > MAX_MLO_LINK_ID) {
+ throw new IllegalArgumentException("linkId is out of range");
+ }
+ mLinkId = linkId;
+ return this;
+ }
+
+ /**
+ * Constructor for {@link TwtRequest.Builder}.
+ *
+ * @param minWakeDurationMicros Minimum TWT wake duration in microseconds.
+ * @param maxWakeDurationMicros Maximum TWT wake duration in microseconds
+ * @param minWakeIntervalMicros Minimum TWT wake interval in microseconds
+ * @param maxWakeIntervalMicros Maximum TWT wake interval in microseconds
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ public Builder(int minWakeDurationMicros, int maxWakeDurationMicros,
+ long minWakeIntervalMicros, long maxWakeIntervalMicros) {
+ mMinWakeDurationMicros = minWakeDurationMicros;
+ mMaxWakeDurationMicros = maxWakeDurationMicros;
+ mMinWakeIntervalMicros = minWakeIntervalMicros;
+ mMaxWakeIntervalMicros = maxWakeIntervalMicros;
+ }
+
+ /**
+ * Build {@link TwtRequest} given the current configurations made on the builder.
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ @NonNull
+ public TwtRequest build() {
+ return new TwtRequest(this);
+ }
+ }
+}
diff --git a/framework/java/android/net/wifi/twt/TwtSession.java b/framework/java/android/net/wifi/twt/TwtSession.java
new file mode 100644
index 0000000000..2258c5c09d
--- /dev/null
+++ b/framework/java/android/net/wifi/twt/TwtSession.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.twt;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+
+import com.android.wifi.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Defines a target wake time (TWT) session.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+public interface TwtSession {
+ /**
+ * Bundle key to get average number of received packets in each wake duration
+ */
+ String TWT_STATS_KEY_INT_AVERAGE_RX_PACKET_COUNT = "key_avg_rx_pkt_count";
+ /**
+ * Bundle key to get average number of transmitted packets in each wake duration
+ */
+ String TWT_STATS_KEY_INT_AVERAGE_TX_PACKET_COUNT = "key_avg_tx_pkt_count";
+ /**
+ * Bundle key to get average bytes per received packets in each wake duration
+ */
+ String TWT_STATS_KEY_INT_AVERAGE_RX_PACKET_SIZE = "key_avg_rx_pkt_size";
+ /**
+ * Bundle key to get average bytes per transmitted packets in each wake duration
+ */
+ String TWT_STATS_KEY_INT_AVERAGE_TX_PACKET_SIZE = "key_avg_tx_pkt_size";
+ /**
+ * Bundle key to get average end of service period in microseconds
+ */
+ String TWT_STATS_KEY_INT_AVERAGE_EOSP_DURATION_MICROS = "key_avg_eosp_dur";
+ /**
+ * Bundle key to get count of early termination. Value will be -1 if not available.
+ */
+ String TWT_STATS_KEY_INT_EOSP_COUNT = "key_eosp_count";
+
+ /** @hide */
+ @StringDef(prefix = { "TWT_STATS_KEY_"}, value = {
+ TWT_STATS_KEY_INT_AVERAGE_RX_PACKET_COUNT,
+ TWT_STATS_KEY_INT_AVERAGE_TX_PACKET_COUNT,
+ TWT_STATS_KEY_INT_AVERAGE_RX_PACKET_SIZE,
+ TWT_STATS_KEY_INT_AVERAGE_TX_PACKET_SIZE,
+ TWT_STATS_KEY_INT_AVERAGE_EOSP_DURATION_MICROS,
+ TWT_STATS_KEY_INT_EOSP_COUNT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface TwtStats {}
+ /**
+ * Get TWT session wake duration in microseconds.
+ *
+ * @return wake duration in microseconds.
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ int getWakeDurationMicros();
+
+ /**
+ * Get TWT session wake interval in microseconds.
+ *
+ * @return wake interval in microseconds.
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ long getWakeIntervalMicros();
+
+ /**
+ * Get MLO link id if the station connection is Wi-Fi 7, otherwise returns
+ * {@link android.net.wifi.MloLink#INVALID_MLO_LINK_ID}.
+ *
+ * @return MLO link id
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ int getMloLinkId();
+
+ /**
+ * Get stats of the session.
+ *
+ * Note: If the command fails or not available, -1 will be returned for all stats values.
+ *
+ * @param executor The executor on which callback will be invoked.
+ * @param resultCallback An asynchronous callback that will return a bundle for target wake time
+ * stats. See {@link TwtStats} for the string keys for the bundle.
+ * @throws SecurityException if the caller does not have permission.
+ * @throws NullPointerException if the caller provided null inputs.
+ * @throws UnsupportedOperationException if the API is not supported.
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ void getStats(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Bundle> resultCallback);
+
+ /**
+ * Teardown the session. See {@link TwtCallback#onTeardown(int)}. Also closes this session,
+ * relinquishing any underlying resources.
+ *
+ * Note: Only owner of the session can close it. Otherwise, Exception is thrown.
+ *
+ * @throws SecurityException if the caller does not have permission.
+ * @throws UnsupportedOperationException if the API is not supported.
+ */
+ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
+ void teardown();
+}
diff --git a/framework/tests/src/android/net/wifi/ScanResultTest.java b/framework/tests/src/android/net/wifi/ScanResultTest.java
index e4af45ef3c..32265c3279 100644
--- a/framework/tests/src/android/net/wifi/ScanResultTest.java
+++ b/framework/tests/src/android/net/wifi/ScanResultTest.java
@@ -263,6 +263,7 @@ public class ScanResultTest {
+ "standard: 11ac, "
+ "80211mcResponder: is not supported, "
+ "80211azNtbResponder: is not supported, "
+ + "TWT Responder: no, "
+ "Radio Chain Infos: null, interface name: test_ifname", scanResult.toString());
}
@@ -286,6 +287,7 @@ public class ScanResultTest {
+ "standard: 11ac, "
+ "80211mcResponder: is not supported, "
+ "80211azNtbResponder: is not supported, "
+ + "TWT Responder: no, "
+ "Radio Chain Infos: [RadioChainInfo: id=0, level=-45, "
+ "RadioChainInfo: id=1, level=-54], interface name: test_ifname",
scanResult.toString());
@@ -305,6 +307,7 @@ public class ScanResultTest {
+ "standard: 11ac, "
+ "80211mcResponder: is not supported, "
+ "80211azNtbResponder: is not supported, "
+ + "TWT Responder: no, "
+ "Radio Chain Infos: null, interface name: test_ifname", scanResult.toString());
}
diff --git a/framework/tests/src/android/net/wifi/WifiManagerTest.java b/framework/tests/src/android/net/wifi/WifiManagerTest.java
index 9f2c44a40c..cb05e51bd1 100644
--- a/framework/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/framework/tests/src/android/net/wifi/WifiManagerTest.java
@@ -120,6 +120,8 @@ import android.net.wifi.WifiUsabilityStatsEntry.ContentionTimeStats;
import android.net.wifi.WifiUsabilityStatsEntry.LinkStats;
import android.net.wifi.WifiUsabilityStatsEntry.RadioStats;
import android.net.wifi.WifiUsabilityStatsEntry.RateStats;
+import android.net.wifi.twt.TwtCallback;
+import android.net.wifi.twt.TwtRequest;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -4158,4 +4160,75 @@ public class WifiManagerTest {
verify(mWifiService).queryWepAllowed(
any(IBooleanListener.Stub.class));
}
+
+ @Test
+ public void testGetTwtCapabilities() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastV());
+ Consumer<Bundle> resultCallback = mock(Consumer.class);
+ SynchronousExecutor executor = mock(SynchronousExecutor.class);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ // Null check
+ assertThrows("null executor should trigger exception", NullPointerException.class,
+ () -> mWifiManager.getTwtCapabilities(executor, null));
+ assertThrows("null executor should trigger exception", NullPointerException.class,
+ () -> mWifiManager.getTwtCapabilities(null, resultCallback));
+ // Get and verify
+ mWifiManager.getTwtCapabilities(executor, resultCallback);
+ verify(mWifiService).getTwtCapabilities(any(ITwtCapabilitiesListener.Stub.class),
+ bundleCaptor.capture());
+ verify(mContext.getAttributionSource()).equals(
+ bundleCaptor.getValue().getParcelable(EXTRA_PARAM_KEY_ATTRIBUTION_SOURCE));
+ }
+
+ @Test
+ public void testSetupTwtSession() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastV());
+ TwtCallback resultCallback = mock(TwtCallback.class);
+ SynchronousExecutor executor = mock(SynchronousExecutor.class);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ TwtRequest twtRequest = mock(TwtRequest.class);
+ // Null check
+ assertThrows("null executor should trigger exception", NullPointerException.class,
+ () -> mWifiManager.setupTwtSession(null, executor, resultCallback));
+ assertThrows("null executor should trigger exception", NullPointerException.class,
+ () -> mWifiManager.setupTwtSession(twtRequest, null, resultCallback));
+ assertThrows("null executor should trigger exception", NullPointerException.class,
+ () -> mWifiManager.setupTwtSession(twtRequest, executor, null));
+ // Call twtSessionSetup and verify
+ mWifiManager.setupTwtSession(twtRequest, executor, resultCallback);
+ verify(mWifiService).setupTwtSession(any(TwtRequest.class), any(ITwtCallback.class),
+ bundleCaptor.capture());
+ verify(mContext.getAttributionSource()).equals(
+ bundleCaptor.getValue().getParcelable(EXTRA_PARAM_KEY_ATTRIBUTION_SOURCE));
+ }
+
+ @Test
+ public void testGetStatsTwtSession() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastV());
+ Consumer<Bundle> resultCallback = mock(Consumer.class);
+ SynchronousExecutor executor = mock(SynchronousExecutor.class);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ // Null check
+ assertThrows("null executor should trigger exception", NullPointerException.class,
+ () -> mWifiManager.getStatsTwtSession(0, null, resultCallback));
+ assertThrows("null executor should trigger exception", NullPointerException.class,
+ () -> mWifiManager.getStatsTwtSession(0, executor, null));
+ // Call twtSessionGetStats and verify
+ mWifiManager.getStatsTwtSession(2, executor, resultCallback);
+ verify(mWifiService).getStatsTwtSession(eq(2), any(ITwtStatsListener.class),
+ bundleCaptor.capture());
+ verify(mContext.getAttributionSource()).equals(
+ bundleCaptor.getValue().getParcelable(EXTRA_PARAM_KEY_ATTRIBUTION_SOURCE));
+ }
+
+ @Test
+ public void testTeardownTwtSession() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastV());
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ // Call twtSessionTeardown and verify
+ mWifiManager.teardownTwtSession(10);
+ verify(mWifiService).teardownTwtSession(eq(10), bundleCaptor.capture());
+ verify(mContext.getAttributionSource()).equals(
+ bundleCaptor.getValue().getParcelable(EXTRA_PARAM_KEY_ATTRIBUTION_SOURCE));
+ }
}
diff --git a/service/java/com/android/server/wifi/ScanDetail.java b/service/java/com/android/server/wifi/ScanDetail.java
index e5cf746a71..ccae57e244 100644
--- a/service/java/com/android/server/wifi/ScanDetail.java
+++ b/service/java/com/android/server/wifi/ScanDetail.java
@@ -60,6 +60,7 @@ public class ScanDetail {
int centerFreq1 = ScanResult.UNSPECIFIED;
boolean isPasspoint = false;
boolean is80211McResponder = false;
+ boolean isTwtResponder = false;
if (networkDetail != null) {
hessid = networkDetail.getHESSID();
anqpDomainId = networkDetail.getAnqpDomainID();
@@ -73,6 +74,7 @@ public class ScanDetail {
&& networkDetail.isInterworking()
&& networkDetail.getHSRelease() != null;
is80211McResponder = networkDetail.is80211McResponderSupport();
+ isTwtResponder = networkDetail.isIndividualTwtSupported();
}
sBuilder.clear();
mScanResult = sBuilder
@@ -85,6 +87,7 @@ public class ScanDetail {
.setRssi(level)
.setFrequency(frequency)
.setTsf(tsf)
+ .setIsTwtResponder(isTwtResponder)
.build();
mSeen = System.currentTimeMillis();
mScanResult.seen = mSeen;
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index 70da8f3be6..b316695f34 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -119,6 +119,9 @@ import android.net.wifi.ISubsystemRestartCallback;
import android.net.wifi.ISuggestionConnectionStatusListener;
import android.net.wifi.ISuggestionUserApprovalStatusListener;
import android.net.wifi.ITrafficStateCallback;
+import android.net.wifi.ITwtCallback;
+import android.net.wifi.ITwtCapabilitiesListener;
+import android.net.wifi.ITwtStatsListener;
import android.net.wifi.IWifiBandsListener;
import android.net.wifi.IWifiConnectedNetworkScorer;
import android.net.wifi.IWifiLowLatencyLockListener;
@@ -154,6 +157,9 @@ import android.net.wifi.WifiSsid;
import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.twt.TwtCallback;
+import android.net.wifi.twt.TwtRequest;
+import android.net.wifi.twt.TwtSession;
import android.net.wifi.util.ScanResultUtil;
import android.os.AsyncTask;
import android.os.Binder;
@@ -8185,4 +8191,58 @@ public class WifiServiceImpl extends BaseWifiService {
}
return mWifiGlobals.forceOverlayConfigValue(configString, value, isEnabled);
}
+
+ /**
+ * See {@link WifiManager#getTwtCapabilities(Executor, Consumer)}
+ */
+ @Override
+ public void getTwtCapabilities(ITwtCapabilitiesListener listener, Bundle extras) {
+ if (!SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException("SDK level too old");
+ }
+ enforceAnyPermissionOf(android.Manifest.permission.MANAGE_WIFI_NETWORK_SELECTION);
+ if (mVerboseLoggingEnabled) {
+ mLog.info("getTwtCapabilities: Uid=% Package Name=%").c(Binder.getCallingUid()).c(
+ getPackageName(extras)).flush();
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener should not be null");
+ }
+ mWifiThreadRunner.post(() -> {
+ try {
+ // TODO: Implementation. Returning not supported.
+ Bundle twtCapabilities = new Bundle();
+ twtCapabilities.putBoolean(WifiManager.TWT_CAPABILITIES_KEY_BOOLEAN_TWT_REQUESTER,
+ false);
+ listener.onResult(twtCapabilities);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ });
+ }
+
+ /**
+ * See {@link WifiManager#setupTwtSession(TwtRequest, Executor, TwtCallback)}
+ */
+ @Override
+ public void setupTwtSession(TwtRequest twtRequest, ITwtCallback iTwtCallback, Bundle extras) {
+ // TODO: Implementation
+ }
+
+ /**
+ * See {@link TwtSession#getStats(Executor, Consumer)}}
+ */
+ @Override
+ public void getStatsTwtSession(int sessionId, ITwtStatsListener iTwtStatsListener,
+ Bundle extras) {
+ // TODO: Implementation
+ }
+
+ /**
+ * See {@link TwtSession#teardown()}
+ */
+ @Override
+ public void teardownTwtSession(int sessionId, Bundle extras) {
+ // TODO: Implementation
+ }
}