diff options
author | 2024-02-13 02:41:43 +0000 | |
---|---|---|
committer | 2024-02-13 02:41:43 +0000 | |
commit | 011be1e351bebfb301b6c2dd3a9e787706b36789 (patch) | |
tree | fa9807ff927d868b57e540d4af1cf0556dc7ec66 | |
parent | 4e6174b26b8bcc470ffbcac894af9538d137bd4e (diff) | |
parent | cce7375b33a6e71b2dd6459840afbd780b3b0038 (diff) |
Merge "Add Target Wake Time (TWT) APIs" into main
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 + } } |