/* * Copyright (C) 2008 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 com.android.server.wifi; import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE; import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_AP; import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_AP_BRIDGE; import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_NAN; import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_STA; import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_P2P; import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_NATIVE_SUPPORTED_FEATURES; import static com.android.server.wifi.p2p.WifiP2pNative.P2P_IFACE_NAME; import static com.android.server.wifi.p2p.WifiP2pNative.P2P_INTERFACE_PROPERTY; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.hardware.wifi.WifiStatusCode; import android.net.MacAddress; import android.net.TrafficStats; import android.net.apf.ApfCapabilities; import android.net.wifi.CoexUnsafeChannel; import android.net.wifi.MscsParams; import android.net.wifi.OuiKeyedData; import android.net.wifi.QosPolicyParams; import android.net.wifi.ScanResult; import android.net.wifi.SecurityParams; import android.net.wifi.SoftApConfiguration; import android.net.wifi.WifiAnnotations; import android.net.wifi.WifiAvailableChannel; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiContext; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.RoamingMode; import android.net.wifi.WifiScanner; import android.net.wifi.WifiScanner.ScanData; import android.net.wifi.WifiSsid; import android.net.wifi.nl80211.DeviceWiphyCapabilities; import android.net.wifi.nl80211.NativeScanResult; import android.net.wifi.nl80211.NativeWifiClient; import android.net.wifi.nl80211.RadioChainInfo; import android.net.wifi.nl80211.WifiNl80211Manager; import android.net.wifi.twt.TwtRequest; import android.net.wifi.twt.TwtSessionCallback; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.SystemClock; import android.os.WorkSource; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.Immutable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; import com.android.modules.utils.build.SdkLevel; import com.android.server.wifi.SupplicantStaIfaceHal.QosPolicyStatus; import com.android.server.wifi.hal.WifiChip; import com.android.server.wifi.hal.WifiHal; import com.android.server.wifi.hal.WifiNanIface; import com.android.server.wifi.hotspot2.NetworkDetail; import com.android.server.wifi.mockwifi.MockWifiServiceUtil; import com.android.server.wifi.proto.WifiStatsLog; import com.android.server.wifi.util.FrameParser; import com.android.server.wifi.util.InformationElementUtil; import com.android.server.wifi.util.NativeUtil; import com.android.server.wifi.util.NetdWrapper; import com.android.server.wifi.util.NetdWrapper.NetdEventObserver; import com.android.wifi.resources.R; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.TimeZone; /** * Native calls for bring up/shut down of the supplicant daemon and for * sending requests to the supplicant daemon * * {@hide} */ public class WifiNative { private static final String TAG = "WifiNative"; private final SupplicantStaIfaceHal mSupplicantStaIfaceHal; private final HostapdHal mHostapdHal; private final WifiVendorHal mWifiVendorHal; private final WifiNl80211Manager mWifiCondManager; private final WifiMonitor mWifiMonitor; private final PropertyService mPropertyService; private final WifiMetrics mWifiMetrics; private final Handler mHandler; private final Random mRandom; private final BuildProperties mBuildProperties; private final WifiInjector mWifiInjector; private final WifiContext mContext; private NetdWrapper mNetdWrapper; private boolean mVerboseLoggingEnabled = false; private boolean mIsEnhancedOpenSupported = false; private final List mCachedCoexUnsafeChannels = new ArrayList<>(); private int mCachedCoexRestrictions; private CountryCodeChangeListenerInternal mCountryCodeChangeListener; private boolean mUseFakeScanDetails; private final ArrayList mFakeScanDetails = new ArrayList<>(); private long mCachedFeatureSet; private boolean mQosPolicyFeatureEnabled = false; private final Map mWifiCondIfacesForBridgedAp = new ArrayMap<>(); private MockWifiServiceUtil mMockWifiModem = null; private InterfaceObserverInternal mInterfaceObserver; private InterfaceEventCallback mInterfaceListener; private @WifiManager.MloMode int mCachedMloMode = WifiManager.MLO_MODE_DEFAULT; private boolean mIsLocationModeEnabled = false; private long mLastLocationModeEnabledTimeMs = 0; private Map mCachedTwtCapabilities = new ArrayMap<>(); /** * Mapping of unknown AKMs configured in overlay config item * config_wifiUnknownAkmToKnownAkmMapping to ScanResult security key management scheme * (ScanResult.KEY_MGMT_XX) */ @VisibleForTesting @Nullable SparseIntArray mUnknownAkmMap; public WifiNative(WifiVendorHal vendorHal, SupplicantStaIfaceHal staIfaceHal, HostapdHal hostapdHal, WifiNl80211Manager condManager, WifiMonitor wifiMonitor, PropertyService propertyService, WifiMetrics wifiMetrics, Handler handler, Random random, BuildProperties buildProperties, WifiInjector wifiInjector) { mWifiVendorHal = vendorHal; mSupplicantStaIfaceHal = staIfaceHal; mHostapdHal = hostapdHal; mWifiCondManager = condManager; mWifiMonitor = wifiMonitor; mPropertyService = propertyService; mWifiMetrics = wifiMetrics; mHandler = handler; mRandom = random; mBuildProperties = buildProperties; mWifiInjector = wifiInjector; mContext = wifiInjector.getContext(); initializeUnknownAkmMapping(); } private void initializeUnknownAkmMapping() { String[] unknownAkmMapping = mContext.getResources() .getStringArray(R.array.config_wifiUnknownAkmToKnownAkmMapping); if (unknownAkmMapping == null) { return; } for (String line : unknownAkmMapping) { if (line == null) { continue; } String[] items = line.split(","); if (items.length != 2) { Log.e( TAG, "Failed to parse config_wifiUnknownAkmToKnownAkmMapping line=" + line + ". Should contain only two values separated by comma"); continue; } try { int unknownAkm = Integer.parseInt(items[0].trim()); int knownAkm = Integer.parseInt(items[1].trim()); // Convert the OEM configured known AKM suite selector to // ScanResult security key management scheme(ScanResult.KEY_MGMT_XX)*/ int keyMgmtScheme = InformationElementUtil.Capabilities.akmToScanResultKeyManagementScheme( knownAkm); if (keyMgmtScheme != ScanResult.KEY_MGMT_UNKNOWN) { if (mUnknownAkmMap == null) { mUnknownAkmMap = new SparseIntArray(); } mUnknownAkmMap.put(unknownAkm, keyMgmtScheme); Log.d( TAG, "unknown AKM = " + unknownAkm + " - converted keyMgmtScheme: " + keyMgmtScheme); } else { Log.e( TAG, "Known AKM: " + knownAkm + " is not defined in the framework." + " Hence Failed to add AKM: " + unknownAkm + " in UnknownAkmMap." + " Parsed config from overlay: " + line); } } catch (Exception e) { // failure to parse. Something is wrong with the configuration. Log.e( TAG, "Parsing config_wifiUnknownAkmToKnownAkmMapping line=" + line + ". Exception occurred:" + e); } } } /** * Enable verbose logging for all sub modules. */ public void enableVerboseLogging(boolean verboseEnabled, boolean halVerboseEnabled) { Log.d(TAG, "enableVerboseLogging " + verboseEnabled + " hal " + halVerboseEnabled); mVerboseLoggingEnabled = verboseEnabled; mWifiCondManager.enableVerboseLogging(verboseEnabled); mSupplicantStaIfaceHal.enableVerboseLogging(verboseEnabled, halVerboseEnabled); mHostapdHal.enableVerboseLogging(verboseEnabled, halVerboseEnabled); mWifiVendorHal.enableVerboseLogging(verboseEnabled, halVerboseEnabled); mIfaceMgr.enableVerboseLogging(verboseEnabled); } /** * Get TWT capabilities for the interface */ public Bundle getTwtCapabilities(String interfaceName) { return mCachedTwtCapabilities.get(interfaceName); } /** * Callbacks for SoftAp interface. */ public class SoftApHalCallbackFromWificond implements WifiNl80211Manager.SoftApCallback { // placeholder for now - provide a shell so that clients don't use a // WifiNl80211Manager-specific API. private String mIfaceName; private SoftApHalCallback mSoftApHalCallback; SoftApHalCallbackFromWificond(String ifaceName, SoftApHalCallback softApHalCallback) { mIfaceName = ifaceName; mSoftApHalCallback = softApHalCallback; } @Override public void onFailure() { mSoftApHalCallback.onFailure(); } @Override public void onSoftApChannelSwitched(int frequency, int bandwidth) { mSoftApHalCallback.onInfoChanged(mIfaceName, frequency, bandwidth, ScanResult.WIFI_STANDARD_UNKNOWN, null, Collections.emptyList()); } @Override public void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected) { mSoftApHalCallback.onConnectedClientsChanged(mIfaceName, client.getMacAddress(), isConnected); } } @SuppressLint("NewApi") private static class CountryCodeChangeListenerInternal implements WifiNl80211Manager.CountryCodeChangedListener { private WifiCountryCode.ChangeListener mListener; public void setChangeListener(@NonNull WifiCountryCode.ChangeListener listener) { mListener = listener; } public void onSetCountryCodeSucceeded(String country) { Log.d(TAG, "onSetCountryCodeSucceeded: " + country); if (mListener != null) { mListener.onSetCountryCodeSucceeded(country); } } @Override public void onCountryCodeChanged(String country) { Log.d(TAG, "onCountryCodeChanged: " + country); if (mListener != null) { mListener.onDriverCountryCodeChanged(country); } } } /** * Callbacks for SoftAp instance. */ public interface SoftApHalCallback { /** * Invoked when there is a fatal failure and the SoftAp is shutdown. */ void onFailure(); /** * Invoked when there is a fatal happen in specific instance only. */ default void onInstanceFailure(String instanceName) {} /** * Invoked when a channel switch event happens - i.e. the SoftAp is moved to a different * channel. Also called on initial registration. * * @param apIfaceInstance The identity of the ap instance. * @param frequency The new frequency of the SoftAp. A value of 0 is invalid and is an * indication that the SoftAp is not enabled. * @param bandwidth The new bandwidth of the SoftAp. * @param generation The new generation of the SoftAp. * @param vendorData List of {@link OuiKeyedData} containing vendor-specific configuration * data, or empty list if not provided. */ void onInfoChanged(String apIfaceInstance, int frequency, int bandwidth, int generation, MacAddress apIfaceInstanceMacAddress, @NonNull List vendorData); /** * Invoked when there is a change in the associated station (STA). * * @param apIfaceInstance The identity of the ap instance. * @param clientAddress Macaddress of the client. * @param isConnected Indication as to whether the client is connected (true), or * disconnected (false). */ void onConnectedClientsChanged(String apIfaceInstance, MacAddress clientAddress, boolean isConnected); } /******************************************************** * Interface management related methods. ********************************************************/ /** * Meta-info about every iface that is active. */ public static class Iface { /** Type of ifaces possible */ public static final int IFACE_TYPE_AP = 0; public static final int IFACE_TYPE_STA_FOR_CONNECTIVITY = 1; public static final int IFACE_TYPE_STA_FOR_SCAN = 2; public static final int IFACE_TYPE_P2P = 3; public static final int IFACE_TYPE_NAN = 4; @IntDef({IFACE_TYPE_AP, IFACE_TYPE_STA_FOR_CONNECTIVITY, IFACE_TYPE_STA_FOR_SCAN, IFACE_TYPE_P2P, IFACE_TYPE_NAN}) @Retention(RetentionPolicy.SOURCE) public @interface IfaceType{} /** Identifier allocated for the interface */ public final int id; /** Type of the iface: STA (for Connectivity or Scan) or AP */ public @IfaceType int type; /** Name of the interface */ public String name; /** Is the interface up? This is used to mask up/down notifications to external clients. */ public boolean isUp; /** External iface destroyed listener for the iface */ public InterfaceCallback externalListener; /** Network observer registered for this interface */ public NetworkObserverInternal networkObserver; /** Interface feature set / capabilities */ public long featureSet; public int bandsSupported; public DeviceWiphyCapabilities phyCapabilities; public WifiHal.WifiInterface iface; Iface(int id, @Iface.IfaceType int type) { this.id = id; this.type = type; } @Override public String toString() { StringBuffer sb = new StringBuffer(); String typeString; switch(type) { case IFACE_TYPE_STA_FOR_CONNECTIVITY: typeString = "STA_CONNECTIVITY"; break; case IFACE_TYPE_STA_FOR_SCAN: typeString = "STA_SCAN"; break; case IFACE_TYPE_AP: typeString = "AP"; break; default: typeString = ""; break; } sb.append("Iface:") .append("{") .append("Name=").append(name) .append(",") .append("Id=").append(id) .append(",") .append("Type=").append(typeString) .append("}"); return sb.toString(); } } /** * Iface Management entity. This class maintains list of all the active ifaces. */ private static class IfaceManager { /** Integer to allocate for the next iface being created */ private int mNextId; /** Map of the id to the iface structure */ private HashMap mIfaces = new HashMap<>(); private boolean mVerboseLoggingEnabled = false; public void enableVerboseLogging(boolean enable) { mVerboseLoggingEnabled = enable; } /** Allocate a new iface for the given type */ private Iface allocateIface(@Iface.IfaceType int type) { if (mVerboseLoggingEnabled) { Log.d(TAG, "IfaceManager#allocateIface: type=" + type + ", pre-map=" + mIfaces); } Iface iface = new Iface(mNextId, type); mIfaces.put(mNextId, iface); mNextId++; return iface; } /** Remove the iface using the provided id */ private Iface removeIface(int id) { if (mVerboseLoggingEnabled) { Log.d(TAG, "IfaceManager#removeIface: id=" + id + ", pre-map=" + mIfaces); } return mIfaces.remove(id); } /** Lookup the iface using the provided id */ private Iface getIface(int id) { return mIfaces.get(id); } /** Lookup the iface using the provided name */ private Iface getIface(@NonNull String ifaceName) { for (Iface iface : mIfaces.values()) { if (TextUtils.equals(iface.name, ifaceName)) { return iface; } } return null; } /** Iterator to use for deleting all the ifaces while performing teardown on each of them */ private Iterator getIfaceIdIter() { return mIfaces.keySet().iterator(); } /** Checks if there are any iface active. */ private boolean hasAnyIface() { return !mIfaces.isEmpty(); } /** Checks if there are any iface of the given type active. */ private boolean hasAnyIfaceOfType(@Iface.IfaceType int type) { for (Iface iface : mIfaces.values()) { if (iface.type == type) { return true; } } return false; } /** Checks if there are any P2P iface active. */ private boolean hasAnyP2pIface() { return hasAnyIfaceOfType(Iface.IFACE_TYPE_P2P); } /** Checks if there are any STA (for connectivity) iface active. */ private boolean hasAnyStaIfaceForConnectivity() { return hasAnyIfaceOfType(Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY); } /** Checks if there are any STA (for scan) iface active. */ private boolean hasAnyStaIfaceForScan() { return hasAnyIfaceOfType(Iface.IFACE_TYPE_STA_FOR_SCAN); } /** Checks if there are any AP iface active. */ private boolean hasAnyApIface() { return hasAnyIfaceOfType(Iface.IFACE_TYPE_AP); } private @NonNull Set findAllStaIfaceNames() { Set ifaceNames = new ArraySet<>(); for (Iface iface : mIfaces.values()) { if (iface.type == Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY || iface.type == Iface.IFACE_TYPE_STA_FOR_SCAN) { ifaceNames.add(iface.name); } } return ifaceNames; } private @NonNull Set findAllApIfaceNames() { Set ifaceNames = new ArraySet<>(); for (Iface iface : mIfaces.values()) { if (iface.type == Iface.IFACE_TYPE_AP) { ifaceNames.add(iface.name); } } return ifaceNames; } /** Removes the existing iface that does not match the provided id. */ public Iface removeExistingIface(int newIfaceId) { if (mVerboseLoggingEnabled) { Log.d(TAG, "IfaceManager#removeExistingIface: newIfaceId=" + newIfaceId + ", pre-map=" + mIfaces); } Iface removedIface = null; // The number of ifaces in the database could be 1 existing & 1 new at the max. if (mIfaces.size() > 2) { Log.wtf(TAG, "More than 1 existing interface found"); } Iterator> iter = mIfaces.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); if (entry.getKey() != newIfaceId) { removedIface = entry.getValue(); iter.remove(); } } return removedIface; } @Override public String toString() { return mIfaces.toString(); } } private class NormalScanEventCallback implements WifiNl80211Manager.ScanEventCallback { private String mIfaceName; NormalScanEventCallback(String ifaceName) { mIfaceName = ifaceName; } @Override public void onScanResultReady() { Log.d(TAG, "Scan result ready event"); mWifiMonitor.broadcastScanResultEvent(mIfaceName); } @Override public void onScanFailed() { Log.d(TAG, "Scan failed event"); mWifiMonitor.broadcastScanFailedEvent(mIfaceName, WifiScanner.REASON_UNSPECIFIED); } @Override public void onScanFailed(int errorCode) { Log.d(TAG, "Scan failed event: errorCode: " + errorCode); mWifiMonitor.broadcastScanFailedEvent(mIfaceName, errorCode); } } private class PnoScanEventCallback implements WifiNl80211Manager.ScanEventCallback { private String mIfaceName; PnoScanEventCallback(String ifaceName) { mIfaceName = ifaceName; } @Override public void onScanResultReady() { Log.d(TAG, "Pno scan result event"); mWifiMonitor.broadcastPnoScanResultEvent(mIfaceName); mWifiMetrics.incrementPnoFoundNetworkEventCount(); } @Override public void onScanFailed() { Log.d(TAG, "Pno Scan failed event"); WifiStatsLog.write(WifiStatsLog.PNO_SCAN_STOPPED, WifiStatsLog.PNO_SCAN_STOPPED__STOP_REASON__SCAN_FAILED, 0, false, false, false, false, // default values WifiStatsLog.PNO_SCAN_STOPPED__FAILURE_CODE__WIFICOND_SCAN_FAILURE); } } private final Object mLock = new Object(); private final IfaceManager mIfaceMgr = new IfaceManager(); private HashSet mStatusListeners = new HashSet<>(); /** Helper method invoked to start supplicant if there were no ifaces */ private boolean startHal() { synchronized (mLock) { if (!mIfaceMgr.hasAnyIface()) { if (mWifiVendorHal.isVendorHalSupported()) { if (!mWifiVendorHal.startVendorHal()) { Log.e(TAG, "Failed to start vendor HAL"); return false; } if (SdkLevel.isAtLeastS()) { mWifiVendorHal.setCoexUnsafeChannels( mCachedCoexUnsafeChannels, mCachedCoexRestrictions); } } else { Log.i(TAG, "Vendor Hal not supported, ignoring start."); } } registerWificondListenerIfNecessary(); return true; } } /** Helper method invoked to stop HAL if there are no more ifaces */ private void stopHalAndWificondIfNecessary() { synchronized (mLock) { if (!mIfaceMgr.hasAnyIface()) { if (!mWifiCondManager.tearDownInterfaces()) { Log.e(TAG, "Failed to teardown ifaces from wificond"); } if (mWifiVendorHal.isVendorHalSupported()) { mWifiVendorHal.stopVendorHal(); } else { Log.i(TAG, "Vendor Hal not supported, ignoring stop."); } } } } /** * Helper method invoked to setup wificond related callback/listener. */ private void registerWificondListenerIfNecessary() { if (mCountryCodeChangeListener == null && SdkLevel.isAtLeastS()) { // The country code listener is a new API in S. mCountryCodeChangeListener = new CountryCodeChangeListenerInternal(); mWifiCondManager.registerCountryCodeChangedListener(Runnable::run, mCountryCodeChangeListener); } } private static final int CONNECT_TO_SUPPLICANT_RETRY_INTERVAL_MS = 100; private static final int CONNECT_TO_SUPPLICANT_RETRY_TIMES = 50; /** * This method is called to wait for establishing connection to wpa_supplicant. * * @return true if connection is established, false otherwise. */ private boolean startAndWaitForSupplicantConnection() { // Start initialization if not already started. if (!mSupplicantStaIfaceHal.isInitializationStarted() && !mSupplicantStaIfaceHal.initialize()) { return false; } if (!mSupplicantStaIfaceHal.startDaemon()) { Log.e(TAG, "Failed to startup supplicant"); return false; } boolean connected = false; int connectTries = 0; while (!connected && connectTries++ < CONNECT_TO_SUPPLICANT_RETRY_TIMES) { // Check if the initialization is complete. connected = mSupplicantStaIfaceHal.isInitializationComplete(); if (connected) { break; } try { Thread.sleep(CONNECT_TO_SUPPLICANT_RETRY_INTERVAL_MS); } catch (InterruptedException ignore) { } } return connected; } /** Helper method invoked to start supplicant if there were no STA ifaces */ private boolean startSupplicant() { synchronized (mLock) { if (!mIfaceMgr.hasAnyStaIfaceForConnectivity()) { if (!startAndWaitForSupplicantConnection()) { Log.e(TAG, "Failed to connect to supplicant"); return false; } if (!mSupplicantStaIfaceHal.registerDeathHandler( new SupplicantDeathHandlerInternal())) { Log.e(TAG, "Failed to register supplicant death handler"); return false; } } return true; } } /** Helper method invoked to stop supplicant if there are no more STA ifaces */ private void stopSupplicantIfNecessary() { synchronized (mLock) { if (!mIfaceMgr.hasAnyStaIfaceForConnectivity()) { if (mSupplicantStaIfaceHal.isInitializationStarted()) { if (!mSupplicantStaIfaceHal.deregisterDeathHandler()) { Log.e(TAG, "Failed to deregister supplicant death handler"); } } if (!mIfaceMgr.hasAnyP2pIface()) { if (mSupplicantStaIfaceHal.isInitializationStarted()) { mSupplicantStaIfaceHal.terminate(); } else { mWifiInjector.getWifiP2pNative().stopP2pSupplicantIfNecessary(); } } } } } /** Helper method invoked to start hostapd if there were no AP ifaces */ private boolean startHostapd() { synchronized (mLock) { if (!mIfaceMgr.hasAnyApIface()) { if (!startAndWaitForHostapdConnection()) { Log.e(TAG, "Failed to connect to hostapd"); return false; } if (!mHostapdHal.registerDeathHandler( new HostapdDeathHandlerInternal())) { Log.e(TAG, "Failed to register hostapd death handler"); return false; } } return true; } } /** Helper method invoked to stop hostapd if there are no more AP ifaces */ private void stopHostapdIfNecessary() { synchronized (mLock) { if (!mIfaceMgr.hasAnyApIface()) { if (!mHostapdHal.deregisterDeathHandler()) { Log.e(TAG, "Failed to deregister hostapd death handler"); } mHostapdHal.terminate(); } } } /** * Helper method to register a new {@link InterfaceObserverInternal}, if there is no previous * observer in place and {@link WifiGlobals#isWifiInterfaceAddedSelfRecoveryEnabled()} is * enabled. */ private void registerInterfaceObserver() { if (!mWifiInjector.getWifiGlobals().isWifiInterfaceAddedSelfRecoveryEnabled()) { return; } if (mInterfaceObserver != null) { Log.d(TAG, "Interface observer has previously been registered."); return; } mInterfaceObserver = new InterfaceObserverInternal(); mNetdWrapper.registerObserver(mInterfaceObserver); Log.d(TAG, "Registered new interface observer."); } /** Helper method to register a network observer and return it */ private boolean registerNetworkObserver(NetworkObserverInternal observer) { if (observer == null) return false; mNetdWrapper.registerObserver(observer); return true; } /** Helper method to unregister a network observer */ private boolean unregisterNetworkObserver(NetworkObserverInternal observer) { if (observer == null) return false; mNetdWrapper.unregisterObserver(observer); return true; } /** * Helper method invoked to teardown client iface (for connectivity) and perform * necessary cleanup */ private void onClientInterfaceForConnectivityDestroyed(@NonNull Iface iface) { synchronized (mLock) { mWifiMonitor.stopMonitoring(iface.name); if (!unregisterNetworkObserver(iface.networkObserver)) { Log.e(TAG, "Failed to unregister network observer on " + iface); } if (!mSupplicantStaIfaceHal.teardownIface(iface.name)) { Log.e(TAG, "Failed to teardown iface in supplicant on " + iface); } if (!mWifiCondManager.tearDownClientInterface(iface.name)) { Log.e(TAG, "Failed to teardown iface in wificond on " + iface); } stopSupplicantIfNecessary(); stopHalAndWificondIfNecessary(); } } /** Helper method invoked to teardown client iface (for scan) and perform necessary cleanup */ private void onClientInterfaceForScanDestroyed(@NonNull Iface iface) { synchronized (mLock) { mWifiMonitor.stopMonitoring(iface.name); if (!unregisterNetworkObserver(iface.networkObserver)) { Log.e(TAG, "Failed to unregister network observer on " + iface); } if (!mWifiCondManager.tearDownClientInterface(iface.name)) { Log.e(TAG, "Failed to teardown iface in wificond on " + iface); } stopHalAndWificondIfNecessary(); } } /** Helper method invoked to teardown softAp iface and perform necessary cleanup */ private void onSoftApInterfaceDestroyed(@NonNull Iface iface) { synchronized (mLock) { if (!unregisterNetworkObserver(iface.networkObserver)) { Log.e(TAG, "Failed to unregister network observer on " + iface); } if (!mHostapdHal.removeAccessPoint(iface.name)) { Log.e(TAG, "Failed to remove access point on " + iface); } String wificondIface = iface.name; String bridgedApInstance = mWifiCondIfacesForBridgedAp.remove(iface.name); if (bridgedApInstance != null) { wificondIface = bridgedApInstance; } if (!mWifiCondManager.tearDownSoftApInterface(wificondIface)) { Log.e(TAG, "Failed to teardown iface in wificond on " + iface); } stopHostapdIfNecessary(); stopHalAndWificondIfNecessary(); } } /** Helper method invoked to teardown iface and perform necessary cleanup */ private void onInterfaceDestroyed(@NonNull Iface iface) { synchronized (mLock) { if (iface.type == Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY) { onClientInterfaceForConnectivityDestroyed(iface); } else if (iface.type == Iface.IFACE_TYPE_STA_FOR_SCAN) { onClientInterfaceForScanDestroyed(iface); } else if (iface.type == Iface.IFACE_TYPE_AP) { onSoftApInterfaceDestroyed(iface); } // Invoke the external callback only if the iface was not destroyed because of vendor // HAL crash. In case of vendor HAL crash, let the crash recovery destroy the mode // managers. if (mWifiVendorHal.isVendorHalReady()) { iface.externalListener.onDestroyed(iface.name); } } } /** * Callback to be invoked by HalDeviceManager when an interface is destroyed. */ private class InterfaceDestoyedListenerInternal implements HalDeviceManager.InterfaceDestroyedListener { /** Identifier allocated for the interface */ private final int mInterfaceId; InterfaceDestoyedListenerInternal(int ifaceId) { mInterfaceId = ifaceId; } @Override public void onDestroyed(@NonNull String ifaceName) { synchronized (mLock) { final Iface iface = mIfaceMgr.removeIface(mInterfaceId); if (iface == null) { if (mVerboseLoggingEnabled) { Log.v(TAG, "Received iface destroyed notification on an invalid iface=" + ifaceName); } return; } onInterfaceDestroyed(iface); Log.i(TAG, "Successfully torn down " + iface); } } } /** * Helper method invoked to trigger the status changed callback after one of the native * daemon's death. */ private void onNativeDaemonDeath() { synchronized (mLock) { for (StatusListener listener : mStatusListeners) { listener.onStatusChanged(false); } for (StatusListener listener : mStatusListeners) { listener.onStatusChanged(true); } } } /** * Death handler for the Vendor HAL daemon. */ private class VendorHalDeathHandlerInternal implements VendorHalDeathEventHandler { @Override public void onDeath() { mHandler.post(() -> { Log.i(TAG, "Vendor HAL died. Cleaning up internal state."); onNativeDaemonDeath(); mWifiMetrics.incrementNumHalCrashes(); }); } } /** * Death handler for the wificond daemon. */ private class WificondDeathHandlerInternal implements Runnable { @Override public void run() { mHandler.post(() -> { Log.i(TAG, "wificond died. Cleaning up internal state."); onNativeDaemonDeath(); mWifiMetrics.incrementNumWificondCrashes(); }); } } /** * Death handler for the supplicant daemon. */ private class SupplicantDeathHandlerInternal implements SupplicantDeathEventHandler { @Override public void onDeath() { mHandler.post(() -> { Log.i(TAG, "wpa_supplicant died. Cleaning up internal state."); onNativeDaemonDeath(); mWifiMetrics.incrementNumSupplicantCrashes(); }); } } /** * Death handler for the hostapd daemon. */ private class HostapdDeathHandlerInternal implements HostapdDeathEventHandler { @Override public void onDeath() { mHandler.post(() -> { Log.i(TAG, "hostapd died. Cleaning up internal state."); onNativeDaemonDeath(); mWifiMetrics.incrementNumHostapdCrashes(); }); } } /** Helper method invoked to handle interface change. */ private void onInterfaceStateChanged(Iface iface, boolean isUp) { synchronized (mLock) { // Mask multiple notifications with the same state. if (isUp == iface.isUp) { if (mVerboseLoggingEnabled) { Log.v(TAG, "Interface status unchanged on " + iface + " from " + isUp + ", Ignoring..."); } return; } Log.i(TAG, "Interface state changed on " + iface + ", isUp=" + isUp); if (isUp) { iface.externalListener.onUp(iface.name); } else { iface.externalListener.onDown(iface.name); if (iface.type == Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY || iface.type == Iface.IFACE_TYPE_STA_FOR_SCAN) { mWifiMetrics.incrementNumClientInterfaceDown(); } else if (iface.type == Iface.IFACE_TYPE_AP) { mWifiMetrics.incrementNumSoftApInterfaceDown(); } } iface.isUp = isUp; } } /** * Listener for wifi interface events. */ public interface InterfaceEventCallback { /** * Interface physical-layer link state has changed. * * @param ifaceName The interface. * @param isLinkUp True if the physical link-layer connection signal is valid. */ void onInterfaceLinkStateChanged(String ifaceName, boolean isLinkUp); /** * Interface has been added. * * @param ifaceName Name of the interface. */ void onInterfaceAdded(String ifaceName); } /** * Register a listener for wifi interface events. * * @param ifaceEventCallback Listener object. */ public void setWifiNativeInterfaceEventCallback(InterfaceEventCallback ifaceEventCallback) { mInterfaceListener = ifaceEventCallback; Log.d(TAG, "setWifiNativeInterfaceEventCallback"); } private class InterfaceObserverInternal implements NetdEventObserver { private static final String TAG = "InterfaceObserverInternal"; private final String mSelfRecoveryInterfaceName = mContext.getResources().getString( R.string.config_wifiSelfRecoveryInterfaceName); @Override public void interfaceLinkStateChanged(String ifaceName, boolean isLinkUp) { if (!ifaceName.equals(mSelfRecoveryInterfaceName)) { return; } Log.d(TAG, "Received interfaceLinkStateChanged, iface=" + ifaceName + " up=" + isLinkUp); if (mInterfaceListener != null) { mInterfaceListener.onInterfaceLinkStateChanged(ifaceName, isLinkUp); } else { Log.e(TAG, "Received interfaceLinkStateChanged, interfaceListener=null"); } } @Override public void interfaceStatusChanged(String iface, boolean up) { // unused. } @Override public void interfaceAdded(String ifaceName) { if (!ifaceName.equals(mSelfRecoveryInterfaceName)) { return; } Log.d(TAG, "Received interfaceAdded, iface=" + ifaceName); if (mInterfaceListener != null) { mInterfaceListener.onInterfaceAdded(ifaceName); } else { Log.e(TAG, "Received interfaceAdded, interfaceListener=null"); } } } /** * Network observer to use for all interface up/down notifications. */ private class NetworkObserverInternal implements NetdEventObserver { /** Identifier allocated for the interface */ private final int mInterfaceId; NetworkObserverInternal(int id) { mInterfaceId = id; } /** * Note: We should ideally listen to * {@link NetdEventObserver#interfaceStatusChanged(String, boolean)} here. But, that * callback is not working currently (broken in netd). So, instead listen to link state * change callbacks as triggers to query the real interface state. We should get rid of * this workaround if we get the |interfaceStatusChanged| callback to work in netd. * Also, this workaround will not detect an interface up event, if the link state is * still down. */ @Override public void interfaceLinkStateChanged(String ifaceName, boolean unusedIsLinkUp) { // This is invoked from the main system_server thread. Post to our handler. mHandler.post(() -> { synchronized (mLock) { if (mVerboseLoggingEnabled) { Log.d(TAG, "interfaceLinkStateChanged: ifaceName=" + ifaceName + ", mInterfaceId = " + mInterfaceId + ", mIfaceMgr=" + mIfaceMgr.toString()); } final Iface ifaceWithId = mIfaceMgr.getIface(mInterfaceId); if (ifaceWithId == null) { if (mVerboseLoggingEnabled) { Log.v(TAG, "Received iface link up/down notification on an invalid" + " iface=" + mInterfaceId); } return; } final Iface ifaceWithName = mIfaceMgr.getIface(ifaceName); if (ifaceWithName == null || ifaceWithName != ifaceWithId) { if (mVerboseLoggingEnabled) { Log.v(TAG, "Received iface link up/down notification on an invalid" + " iface=" + ifaceName); } return; } onInterfaceStateChanged(ifaceWithName, isInterfaceUp(ifaceName)); } }); } @Override public void interfaceStatusChanged(String ifaceName, boolean unusedIsLinkUp) { // unused currently. Look at note above. } @Override public void interfaceAdded(String iface){ // unused currently. } } /** * Radio mode change handler for the Vendor HAL daemon. */ private class VendorHalRadioModeChangeHandlerInternal implements VendorHalRadioModeChangeEventHandler { @Override public void onMcc(int band) { synchronized (mLock) { Log.i(TAG, "Device is in MCC mode now"); mWifiMetrics.incrementNumRadioModeChangeToMcc(); } } @Override public void onScc(int band) { synchronized (mLock) { Log.i(TAG, "Device is in SCC mode now"); mWifiMetrics.incrementNumRadioModeChangeToScc(); } } @Override public void onSbs(int band) { synchronized (mLock) { Log.i(TAG, "Device is in SBS mode now"); mWifiMetrics.incrementNumRadioModeChangeToSbs(); } } @Override public void onDbs() { synchronized (mLock) { Log.i(TAG, "Device is in DBS mode now"); mWifiMetrics.incrementNumRadioModeChangeToDbs(); } } } // For devices that don't support the vendor HAL, we will not support any concurrency. // So simulate the HalDeviceManager behavior by triggering the destroy listener for // any active interface. private String handleIfaceCreationWhenVendorHalNotSupported(@NonNull Iface newIface) { synchronized (mLock) { Iface existingIface = mIfaceMgr.removeExistingIface(newIface.id); if (existingIface != null) { onInterfaceDestroyed(existingIface); Log.i(TAG, "Successfully torn down " + existingIface); } // Return the interface name directly from the system property. return mPropertyService.getString("wifi.interface", "wlan0"); } } /** * Helper function to handle creation of STA iface. * For devices which do not the support the HAL, this will bypass HalDeviceManager & * teardown any existing iface. */ private String createStaIface(@NonNull Iface iface, @NonNull WorkSource requestorWs, @NonNull ConcreteClientModeManager concreteClientModeManager) { synchronized (mLock) { if (mWifiVendorHal.isVendorHalSupported()) { return mWifiVendorHal.createStaIface( new InterfaceDestoyedListenerInternal(iface.id), requestorWs, concreteClientModeManager); } else { Log.i(TAG, "Vendor Hal not supported, ignoring createStaIface."); return handleIfaceCreationWhenVendorHalNotSupported(iface); } } } /** * Helper function to handle creation of AP iface. * For devices which do not the support the HAL, this will bypass HalDeviceManager & * teardown any existing iface. */ private String createApIface(@NonNull Iface iface, @NonNull WorkSource requestorWs, @SoftApConfiguration.BandType int band, boolean isBridged, @NonNull SoftApManager softApManager, @NonNull List vendorData) { synchronized (mLock) { if (mWifiVendorHal.isVendorHalSupported()) { return mWifiVendorHal.createApIface( new InterfaceDestoyedListenerInternal(iface.id), requestorWs, band, isBridged, softApManager, vendorData); } else { Log.i(TAG, "Vendor Hal not supported, ignoring createApIface."); return handleIfaceCreationWhenVendorHalNotSupported(iface); } } } private String createP2pIfaceFromHalOrGetNameFromProperty( HalDeviceManager.InterfaceDestroyedListener p2pInterfaceDestroyedListener, Handler handler, WorkSource requestorWs) { synchronized (mLock) { if (mWifiVendorHal.isVendorHalSupported()) { return mWifiInjector.getHalDeviceManager().createP2pIface( p2pInterfaceDestroyedListener, handler, requestorWs); } else { Log.i(TAG, "Vendor Hal not supported, ignoring createStaIface."); return mPropertyService.getString(P2P_INTERFACE_PROPERTY, P2P_IFACE_NAME); } } } /** * Helper function to handle creation of P2P iface. * For devices which do not the support the HAL, this will bypass HalDeviceManager & * teardown any existing iface. */ public Iface createP2pIface( HalDeviceManager.InterfaceDestroyedListener p2pInterfaceDestroyedListener, Handler handler, WorkSource requestorWs) { synchronized (mLock) { // Make sure HAL is started for p2p if (!startHal()) { Log.e(TAG, "Failed to start Hal"); mWifiMetrics.incrementNumSetupP2pInterfaceFailureDueToHal(); return null; } // maintain iface status in WifiNative Iface iface = mIfaceMgr.allocateIface(Iface.IFACE_TYPE_P2P); if (iface == null) { Log.e(TAG, "Failed to allocate new P2P iface"); stopHalAndWificondIfNecessary(); return null; } iface.name = createP2pIfaceFromHalOrGetNameFromProperty( p2pInterfaceDestroyedListener, handler, requestorWs); if (TextUtils.isEmpty(iface.name)) { Log.e(TAG, "Failed to create P2p iface in HalDeviceManager"); mIfaceMgr.removeIface(iface.id); mWifiMetrics.incrementNumSetupP2pInterfaceFailureDueToHal(); stopHalAndWificondIfNecessary(); return null; } return iface; } } /** * Teardown P2p iface with input interface Id which was returned by createP2pIface. * * @param interfaceId the interface identify which was gerenated when creating P2p iface. */ public void teardownP2pIface(int interfaceId) { synchronized (mLock) { mIfaceMgr.removeIface(interfaceId); stopHalAndWificondIfNecessary(); stopSupplicantIfNecessary(); } } /** * Helper function to handle creation of Nan iface. */ public Iface createNanIface( HalDeviceManager.InterfaceDestroyedListener nanInterfaceDestroyedListener, Handler handler, WorkSource requestorWs) { synchronized (mLock) { // Make sure HAL is started for Nan if (!startHal()) { Log.e(TAG, "Failed to start Hal"); return null; } // maintain iface status in WifiNative Iface iface = mIfaceMgr.allocateIface(Iface.IFACE_TYPE_NAN); if (iface != null) { WifiNanIface nanIface = mWifiInjector.getHalDeviceManager().createNanIface( nanInterfaceDestroyedListener, handler, requestorWs); if (nanIface != null) { iface.iface = nanIface; return iface; } } Log.e(TAG, "Failed to allocate new Nan iface"); stopHalAndWificondIfNecessary(); return null; } } /** * Teardown Nan iface with input interface Id which was returned by createP2pIface. * * @param interfaceId the interface identify which was gerenated when creating P2p iface. */ public void teardownNanIface(int interfaceId) { synchronized (mLock) { mIfaceMgr.removeIface(interfaceId); stopHalAndWificondIfNecessary(); } } /** * Get list of instance name from this bridged AP iface. * * @param ifaceName Name of the bridged interface. * @return list of instance name when succeed, otherwise null. */ @Nullable public List getBridgedApInstances(@NonNull String ifaceName) { synchronized (mLock) { if (mWifiVendorHal.isVendorHalSupported()) { return mWifiVendorHal.getBridgedApInstances(ifaceName); } else { Log.i(TAG, "Vendor Hal not supported, ignoring getBridgedApInstances."); return null; } } } // For devices that don't support the vendor HAL, we will not support any concurrency. // So simulate the HalDeviceManager behavior by triggering the destroy listener for // the interface. private boolean handleIfaceRemovalWhenVendorHalNotSupported(@NonNull Iface iface) { synchronized (mLock) { mIfaceMgr.removeIface(iface.id); onInterfaceDestroyed(iface); Log.i(TAG, "Successfully torn down " + iface); return true; } } /** * Helper function to handle removal of STA iface. * For devices which do not the support the HAL, this will bypass HalDeviceManager & * teardown any existing iface. */ private boolean removeStaIface(@NonNull Iface iface) { synchronized (mLock) { if (mWifiVendorHal.isVendorHalSupported()) { return mWifiVendorHal.removeStaIface(iface.name); } else { Log.i(TAG, "Vendor Hal not supported, ignoring removeStaIface."); return handleIfaceRemovalWhenVendorHalNotSupported(iface); } } } /** * Helper function to handle removal of STA iface. */ private boolean removeApIface(@NonNull Iface iface) { synchronized (mLock) { if (mWifiVendorHal.isVendorHalSupported()) { return mWifiVendorHal.removeApIface(iface.name); } else { Log.i(TAG, "Vendor Hal not supported, ignoring removeApIface."); return handleIfaceRemovalWhenVendorHalNotSupported(iface); } } } /** * Helper function to remove specific instance in bridged AP iface. * * @param ifaceName Name of the iface. * @param apIfaceInstance The identity of the ap instance. * @return true if the operation succeeded, false if there is an error in Hal. */ public boolean removeIfaceInstanceFromBridgedApIface(@NonNull String ifaceName, @NonNull String apIfaceInstance) { synchronized (mLock) { if (mWifiVendorHal.isVendorHalSupported()) { return mWifiVendorHal.removeIfaceInstanceFromBridgedApIface(ifaceName, apIfaceInstance); } else { return false; } } } /** * Register listener for subsystem restart event * * @param listener SubsystemRestartListener listener object. */ public void registerSubsystemRestartListener( HalDeviceManager.SubsystemRestartListener listener) { if (listener != null) { mWifiVendorHal.registerSubsystemRestartListener(listener); } } /** * Initialize the native modules. * * @return true on success, false otherwise. */ public boolean initialize() { synchronized (mLock) { if (!mWifiVendorHal.initialize(new VendorHalDeathHandlerInternal())) { Log.e(TAG, "Failed to initialize vendor HAL"); return false; } mWifiCondManager.setOnServiceDeadCallback(new WificondDeathHandlerInternal()); mWifiCondManager.tearDownInterfaces(); mWifiVendorHal.registerRadioModeChangeHandler( new VendorHalRadioModeChangeHandlerInternal()); mNetdWrapper = mWifiInjector.makeNetdWrapper(); return true; } } /** * Callback to notify when the status of one of the native daemons * (wificond, wpa_supplicant & vendor HAL) changes. */ public interface StatusListener { /** * @param allReady Indicates if all the native daemons are ready for operation or not. */ void onStatusChanged(boolean allReady); } /** * Register a StatusListener to get notified about any status changes from the native daemons. * * It is safe to re-register the same callback object - duplicates are detected and only a * single copy kept. * * @param listener StatusListener listener object. */ public void registerStatusListener(@NonNull StatusListener listener) { synchronized (mLock) { mStatusListeners.add(listener); } } /** * Callback to notify when the associated interface is destroyed, up or down. */ public interface InterfaceCallback { /** * Interface destroyed by HalDeviceManager. * * @param ifaceName Name of the iface. */ void onDestroyed(String ifaceName); /** * Interface is up. * * @param ifaceName Name of the iface. */ void onUp(String ifaceName); /** * Interface is down. * * @param ifaceName Name of the iface. */ void onDown(String ifaceName); } private void takeBugReportInterfaceFailureIfNeeded(String bugTitle, String bugDetail) { if (mWifiInjector.getDeviceConfigFacade().isInterfaceFailureBugreportEnabled()) { mWifiInjector.getWifiDiagnostics().takeBugReport(bugTitle, bugDetail); } } /** * Setup an interface for client mode (for scan) operations. * * This method configures an interface in STA mode in the native daemons * (wificond, vendor HAL). * * @param interfaceCallback Associated callback for notifying status changes for the iface. * @param requestorWs Requestor worksource. * @param concreteClientModeManager ConcreteClientModeManager requesting the interface. * @return Returns the name of the allocated interface, will be null on failure. */ public String setupInterfaceForClientInScanMode( @NonNull InterfaceCallback interfaceCallback, @NonNull WorkSource requestorWs, @NonNull ConcreteClientModeManager concreteClientModeManager) { synchronized (mLock) { if (!startHal()) { Log.e(TAG, "Failed to start Hal"); mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToHal(); return null; } Iface iface = mIfaceMgr.allocateIface(Iface.IFACE_TYPE_STA_FOR_SCAN); if (iface == null) { Log.e(TAG, "Failed to allocate new STA iface"); return null; } iface.externalListener = interfaceCallback; iface.name = createStaIface(iface, requestorWs, concreteClientModeManager); if (TextUtils.isEmpty(iface.name)) { Log.e(TAG, "Failed to create iface in vendor HAL"); mIfaceMgr.removeIface(iface.id); mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToHal(); return null; } if (!mWifiCondManager.setupInterfaceForClientMode(iface.name, Runnable::run, new NormalScanEventCallback(iface.name), new PnoScanEventCallback(iface.name))) { Log.e(TAG, "Failed to setup iface in wificond=" + iface.name); teardownInterface(iface.name); mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToWificond(); return null; } registerInterfaceObserver(); iface.networkObserver = new NetworkObserverInternal(iface.id); if (!registerNetworkObserver(iface.networkObserver)) { Log.e(TAG, "Failed to register network observer for iface=" + iface.name); teardownInterface(iface.name); return null; } mWifiMonitor.startMonitoring(iface.name); // Just to avoid any race conditions with interface state change callbacks, // update the interface state before we exit. onInterfaceStateChanged(iface, isInterfaceUp(iface.name)); mWifiVendorHal.enableLinkLayerStats(iface.name); Log.i(TAG, "Successfully setup " + iface); iface.featureSet = getSupportedFeatureSetInternal(iface.name); updateSupportedBandForStaInternal(iface); mWifiVendorHal.enableStaChannelForPeerNetwork(mContext.getResources().getBoolean( R.bool.config_wifiEnableStaIndoorChannelForPeerNetwork), mContext.getResources().getBoolean( R.bool.config_wifiEnableStaDfsChannelForPeerNetwork)); return iface.name; } } /** * Setup an interface for Soft AP mode operations. * * This method configures an interface in AP mode in all the native daemons * (wificond, wpa_supplicant & vendor HAL). * * @param interfaceCallback Associated callback for notifying status changes for the iface. * @param requestorWs Requestor worksource. * @param isBridged Whether or not AP interface is a bridge interface. * @param softApManager SoftApManager of the request. * @param vendorData List of {@link OuiKeyedData} containing vendor-provided * configuration data. Empty list indicates no vendor data. * @return Returns the name of the allocated interface, will be null on failure. */ public String setupInterfaceForSoftApMode( @NonNull InterfaceCallback interfaceCallback, @NonNull WorkSource requestorWs, @SoftApConfiguration.BandType int band, boolean isBridged, @NonNull SoftApManager softApManager, @NonNull List vendorData) { synchronized (mLock) { String bugTitle = "Wi-Fi BugReport (softAp interface failure)"; String errorMsg = ""; if (!startHal()) { errorMsg = "Failed to start softAp Hal"; Log.e(TAG, errorMsg); mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHal(); takeBugReportInterfaceFailureIfNeeded(bugTitle, errorMsg); softApManager.writeSoftApStartedEvent(SoftApManager.START_RESULT_FAILURE_START_HAL); return null; } if (!startHostapd()) { errorMsg = "Failed to start softAp hostapd"; Log.e(TAG, errorMsg); mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHostapd(); takeBugReportInterfaceFailureIfNeeded(bugTitle, errorMsg); softApManager.writeSoftApStartedEvent( SoftApManager.START_RESULT_FAILURE_START_HOSTAPD); return null; } Iface iface = mIfaceMgr.allocateIface(Iface.IFACE_TYPE_AP); if (iface == null) { Log.e(TAG, "Failed to allocate new AP iface"); return null; } iface.externalListener = interfaceCallback; iface.name = createApIface(iface, requestorWs, band, isBridged, softApManager, vendorData); if (TextUtils.isEmpty(iface.name)) { errorMsg = "Failed to create softAp iface in vendor HAL"; Log.e(TAG, errorMsg); mIfaceMgr.removeIface(iface.id); mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHal(); takeBugReportInterfaceFailureIfNeeded(bugTitle, errorMsg); return null; } String ifaceInstanceName = iface.name; if (isBridged) { List instances = getBridgedApInstances(iface.name); if (instances == null || instances.size() == 0) { errorMsg = "Failed to get bridged AP instances" + iface.name; Log.e(TAG, errorMsg); teardownInterface(iface.name); mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHal(); takeBugReportInterfaceFailureIfNeeded(bugTitle, errorMsg); return null; } // Always select first instance as wificond interface. ifaceInstanceName = instances.get(0); mWifiCondIfacesForBridgedAp.put(iface.name, ifaceInstanceName); } if (!mWifiCondManager.setupInterfaceForSoftApMode(ifaceInstanceName)) { errorMsg = "Failed to setup softAp iface in wifiCond manager on " + iface; Log.e(TAG, errorMsg); teardownInterface(iface.name); mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToWificond(); takeBugReportInterfaceFailureIfNeeded(bugTitle, errorMsg); return null; } iface.networkObserver = new NetworkObserverInternal(iface.id); if (!registerNetworkObserver(iface.networkObserver)) { Log.e(TAG, "Failed to register network observer on " + iface); teardownInterface(iface.name); return null; } // Just to avoid any race conditions with interface state change callbacks, // update the interface state before we exit. onInterfaceStateChanged(iface, isInterfaceUp(iface.name)); Log.i(TAG, "Successfully setup " + iface); iface.featureSet = getSupportedFeatureSetInternal(iface.name); updateSupportedBandForStaInternal(iface); return iface.name; } } /** * Switches an existing Client mode interface from connectivity * {@link Iface#IFACE_TYPE_STA_FOR_CONNECTIVITY} to scan mode * {@link Iface#IFACE_TYPE_STA_FOR_SCAN}. * * @param ifaceName Name of the interface. * @param requestorWs Requestor worksource. * @return true if the operation succeeded, false if there is an error or the iface is already * in scan mode. */ public boolean switchClientInterfaceToScanMode(@NonNull String ifaceName, @NonNull WorkSource requestorWs) { synchronized (mLock) { final Iface iface = mIfaceMgr.getIface(ifaceName); if (iface == null) { Log.e(TAG, "Trying to switch to scan mode on an invalid iface=" + ifaceName); return false; } if (iface.type == Iface.IFACE_TYPE_STA_FOR_SCAN) { Log.e(TAG, "Already in scan mode on iface=" + ifaceName); return true; } if (mWifiVendorHal.isVendorHalSupported() && !mWifiVendorHal.replaceStaIfaceRequestorWs(iface.name, requestorWs)) { Log.e(TAG, "Failed to replace requestor ws on " + iface); teardownInterface(iface.name); return false; } if (!mSupplicantStaIfaceHal.teardownIface(iface.name)) { Log.e(TAG, "Failed to teardown iface in supplicant on " + iface); teardownInterface(iface.name); return false; } iface.type = Iface.IFACE_TYPE_STA_FOR_SCAN; stopSupplicantIfNecessary(); iface.featureSet = getSupportedFeatureSetInternal(iface.name); updateSupportedBandForStaInternal(iface); iface.phyCapabilities = null; Log.i(TAG, "Successfully switched to scan mode on iface=" + iface); return true; } } /** * Switches an existing Client mode interface from scan mode * {@link Iface#IFACE_TYPE_STA_FOR_SCAN} to connectivity mode * {@link Iface#IFACE_TYPE_STA_FOR_CONNECTIVITY}. * * @param ifaceName Name of the interface. * @param requestorWs Requestor worksource. * @return true if the operation succeeded, false if there is an error or the iface is already * in scan mode. */ public boolean switchClientInterfaceToConnectivityMode(@NonNull String ifaceName, @NonNull WorkSource requestorWs) { synchronized (mLock) { final Iface iface = mIfaceMgr.getIface(ifaceName); if (iface == null) { Log.e(TAG, "Trying to switch to connectivity mode on an invalid iface=" + ifaceName); return false; } if (iface.type == Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY) { Log.e(TAG, "Already in connectivity mode on iface=" + ifaceName); return true; } if (mWifiVendorHal.isVendorHalSupported() && !mWifiVendorHal.replaceStaIfaceRequestorWs(iface.name, requestorWs)) { Log.e(TAG, "Failed to replace requestor ws on " + iface); teardownInterface(iface.name); return false; } if (!startSupplicant()) { Log.e(TAG, "Failed to start supplicant"); teardownInterface(iface.name); mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToSupplicant(); return false; } if (!mSupplicantStaIfaceHal.setupIface(iface.name)) { Log.e(TAG, "Failed to setup iface in supplicant on " + iface); teardownInterface(iface.name); mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToSupplicant(); return false; } if (mContext.getResources().getBoolean( R.bool.config_wifiNetworkCentricQosPolicyFeatureEnabled) && isSupplicantUsingAidlService()) { mQosPolicyFeatureEnabled = mSupplicantStaIfaceHal .setNetworkCentricQosPolicyFeatureEnabled(iface.name, true); if (!mQosPolicyFeatureEnabled) { Log.e(TAG, "Failed to enable QoS policy feature for iface " + iface.name); } } iface.type = Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY; iface.featureSet = getSupportedFeatureSetInternal(iface.name); saveCompleteFeatureSetInConfigStoreIfNecessary(iface.featureSet); updateSupportedBandForStaInternal(iface); mIsEnhancedOpenSupported = (iface.featureSet & WIFI_FEATURE_OWE) != 0; Log.i(TAG, "Successfully switched to connectivity mode on iface=" + iface); return true; } } /** * Change the requestor WorkSource for a given STA iface. * @return true if the operation succeeded, false otherwise. */ public boolean replaceStaIfaceRequestorWs(@NonNull String ifaceName, WorkSource newWorkSource) { final Iface iface = mIfaceMgr.getIface(ifaceName); if (iface == null) { Log.e(TAG, "Called replaceStaIfaceRequestorWs() on an invalid iface=" + ifaceName); return false; } if (!mWifiVendorHal.isVendorHalSupported()) { // if vendor HAL isn't supported, return true since there's nothing to do. return true; } if (!mWifiVendorHal.replaceStaIfaceRequestorWs(iface.name, newWorkSource)) { Log.e(TAG, "Failed to replace requestor ws on " + iface); teardownInterface(iface.name); return false; } return true; } /** * * Check if the interface is up or down. * * @param ifaceName Name of the interface. * @return true if iface is up, false if it's down or on error. */ public boolean isInterfaceUp(@NonNull String ifaceName) { synchronized (mLock) { final Iface iface = mIfaceMgr.getIface(ifaceName); if (iface == null) { Log.e(TAG, "Trying to get iface state on invalid iface=" + ifaceName); return false; } try { return mNetdWrapper.isInterfaceUp(ifaceName); } catch (IllegalStateException e) { Log.e(TAG, "Unable to get interface config", e); return false; } } } /** * Teardown an interface in Client/AP mode. * * This method tears down the associated interface from all the native daemons * (wificond, wpa_supplicant & vendor HAL). * Also, brings down the HAL, supplicant or hostapd as necessary. * * @param ifaceName Name of the interface. */ public void teardownInterface(@NonNull String ifaceName) { synchronized (mLock) { final Iface iface = mIfaceMgr.getIface(ifaceName); if (iface == null) { Log.e(TAG, "Trying to teardown an invalid iface=" + ifaceName); return; } // Trigger the iface removal from HAL. The rest of the cleanup will be triggered // from the interface destroyed callback. if (iface.type == Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY || iface.type == Iface.IFACE_TYPE_STA_FOR_SCAN) { if (!removeStaIface(iface)) { Log.e(TAG, "Failed to remove iface in vendor HAL=" + ifaceName); return; } } else if (iface.type == Iface.IFACE_TYPE_AP) { if (!removeApIface(iface)) { Log.e(TAG, "Failed to remove iface in vendor HAL=" + ifaceName); return; } } Log.i(TAG, "Successfully initiated teardown for iface=" + ifaceName); } } /** * Teardown all the active interfaces. * * This method tears down the associated interfaces from all the native daemons * (wificond, wpa_supplicant & vendor HAL). * Also, brings down the HAL, supplicant or hostapd as necessary. */ public void teardownAllInterfaces() { synchronized (mLock) { Iterator ifaceIdIter = mIfaceMgr.getIfaceIdIter(); while (ifaceIdIter.hasNext()) { Iface iface = mIfaceMgr.getIface(ifaceIdIter.next()); ifaceIdIter.remove(); onInterfaceDestroyed(iface); Log.i(TAG, "Successfully torn down " + iface); } Log.i(TAG, "Successfully torn down all ifaces"); } } /** * Get names of all the client interfaces. * * @return List of interface name of all active client interfaces. */ public Set getClientInterfaceNames() { synchronized (mLock) { return mIfaceMgr.findAllStaIfaceNames(); } } /** * Get names of all the client interfaces. * * @return List of interface name of all active client interfaces. */ public Set getSoftApInterfaceNames() { synchronized (mLock) { return mIfaceMgr.findAllApIfaceNames(); } } /******************************************************** * Wificond operations ********************************************************/ /** * Query the list of valid frequencies for the provided band. * The result depends on the on the country code that has been set. * * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants. * The following bands are supported {@link WifiAnnotations.WifiBandBasic}: * WifiScanner.WIFI_BAND_24_GHZ * WifiScanner.WIFI_BAND_5_GHZ * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY * WifiScanner.WIFI_BAND_6_GHZ * WifiScanner.WIFI_BAND_60_GHZ * @return frequencies vector of valid frequencies (MHz), or null for error. * @throws IllegalArgumentException if band is not recognized. */ public int [] getChannelsForBand(@WifiAnnotations.WifiBandBasic int band) { if (!SdkLevel.isAtLeastS() && band == WifiScanner.WIFI_BAND_60_GHZ) { // 60 GHz band is new in Android S, return empty array on older SDK versions return new int[0]; } return mWifiCondManager.getChannelsMhzForBand(band); } /** * Start a scan using wificond for the given parameters. * @param ifaceName Name of the interface. * @param scanType Type of scan to perform. One of {@link WifiScanner#SCAN_TYPE_LOW_LATENCY}, * {@link WifiScanner#SCAN_TYPE_LOW_POWER} or {@link WifiScanner#SCAN_TYPE_HIGH_ACCURACY}. * @param freqs list of frequencies to scan for, if null scan all supported channels. * @param hiddenNetworkSSIDs List of hidden networks to be scanned for. * @param enable6GhzRnr whether Reduced Neighbor Report should be enabled for 6Ghz scanning. * @param vendorIes Byte array of vendor IEs * @return Returns true on success. */ public int scan( @NonNull String ifaceName, @WifiAnnotations.ScanType int scanType, Set freqs, List hiddenNetworkSSIDs, boolean enable6GhzRnr, byte[] vendorIes) { int scanRequestStatus = WifiScanner.REASON_SUCCEEDED; boolean scanStatus = true; List hiddenNetworkSsidsArrays = new ArrayList<>(); for (String hiddenNetworkSsid : hiddenNetworkSSIDs) { try { hiddenNetworkSsidsArrays.add( NativeUtil.byteArrayFromArrayList( NativeUtil.decodeSsid(hiddenNetworkSsid))); } catch (IllegalArgumentException e) { Log.e(TAG, "Illegal argument " + hiddenNetworkSsid, e); continue; } } if (SdkLevel.isAtLeastS()) { // enable6GhzRnr is a new parameter first introduced in Android S. Bundle extraScanningParams = new Bundle(); extraScanningParams.putBoolean(WifiNl80211Manager.SCANNING_PARAM_ENABLE_6GHZ_RNR, enable6GhzRnr); if (SdkLevel.isAtLeastU()) { extraScanningParams.putByteArray(WifiNl80211Manager.EXTRA_SCANNING_PARAM_VENDOR_IES, vendorIes); scanRequestStatus = mWifiCondManager.startScan2(ifaceName, scanType, freqs, hiddenNetworkSsidsArrays, extraScanningParams); } else { scanStatus = mWifiCondManager.startScan(ifaceName, scanType, freqs, hiddenNetworkSsidsArrays, extraScanningParams); scanRequestStatus = scanStatus ? WifiScanner.REASON_SUCCEEDED : WifiScanner.REASON_UNSPECIFIED; } } else { scanStatus = mWifiCondManager.startScan(ifaceName, scanType, freqs, hiddenNetworkSsidsArrays); scanRequestStatus = scanStatus ? WifiScanner.REASON_SUCCEEDED : WifiScanner.REASON_UNSPECIFIED; } return scanRequestStatus; } /** * Fetch the latest scan result from kernel via wificond. * @param ifaceName Name of the interface. * @return Returns an ArrayList of ScanDetail. * Returns an empty ArrayList on failure. */ public ArrayList getScanResults(@NonNull String ifaceName) { if (mUseFakeScanDetails) { synchronized (mFakeScanDetails) { ArrayList copyList = new ArrayList<>(); for (ScanDetail sd: mFakeScanDetails) { ScanDetail copy = new ScanDetail(sd); copy.getScanResult().ifaceName = ifaceName; // otherwise the fake will be too old copy.getScanResult().timestamp = SystemClock.elapsedRealtime() * 1000; copyList.add(copy); } return copyList; } } if (mMockWifiModem != null && mMockWifiModem.isMethodConfigured( MockWifiServiceUtil.MOCK_NL80211_SERVICE, "getScanResults")) { Log.i(TAG, "getScanResults was called from mock wificond"); return convertNativeScanResults(ifaceName, mMockWifiModem.getWifiNl80211Manager() .getScanResults(ifaceName, WifiNl80211Manager.SCAN_TYPE_SINGLE_SCAN)); } return convertNativeScanResults(ifaceName, mWifiCondManager.getScanResults( ifaceName, WifiNl80211Manager.SCAN_TYPE_SINGLE_SCAN)); } /** * Start faking scan results - using information provided via * {@link #addFakeScanDetail(ScanDetail)}. Stop with {@link #stopFakingScanDetails()}. */ public void startFakingScanDetails() { if (mBuildProperties.isUserBuild()) { Log.wtf(TAG, "Can't fake scan results in a user build!"); return; } Log.d(TAG, "Starting faking scan results - " + mFakeScanDetails); mUseFakeScanDetails = true; } /** * Add fake scan result. Fakes are not used until activated via * {@link #startFakingScanDetails()}. * @param fakeScanDetail */ public void addFakeScanDetail(@NonNull ScanDetail fakeScanDetail) { synchronized (mFakeScanDetails) { mFakeScanDetails.add(fakeScanDetail); } } /** * Reset the fake scan result list updated via {@link #addFakeScanDetail(ScanDetail)} .} */ public void resetFakeScanDetails() { synchronized (mFakeScanDetails) { mFakeScanDetails.clear(); } } /** * Stop faking scan results. Started with {@link #startFakingScanDetails()}. */ public void stopFakingScanDetails() { mUseFakeScanDetails = false; } /** * Fetch the latest scan result from kernel via wificond. * @param ifaceName Name of the interface. * @return Returns an ArrayList of ScanDetail. * Returns an empty ArrayList on failure. */ public ArrayList getPnoScanResults(@NonNull String ifaceName) { if (mMockWifiModem != null && mMockWifiModem.isMethodConfigured( MockWifiServiceUtil.MOCK_NL80211_SERVICE, "getPnoScanResults")) { Log.i(TAG, "getPnoScanResults was called from mock wificond"); return convertNativeScanResults(ifaceName, mMockWifiModem.getWifiNl80211Manager() .getScanResults(ifaceName, WifiNl80211Manager.SCAN_TYPE_PNO_SCAN)); } return convertNativeScanResults(ifaceName, mWifiCondManager.getScanResults(ifaceName, WifiNl80211Manager.SCAN_TYPE_PNO_SCAN)); } /** * Get the max number of SSIDs that the driver supports per scan. * @param ifaceName Name of the interface. */ public int getMaxSsidsPerScan(@NonNull String ifaceName) { if (SdkLevel.isAtLeastT()) { return mWifiCondManager.getMaxSsidsPerScan(ifaceName); } else { return -1; } } private ArrayList convertNativeScanResults(@NonNull String ifaceName, List nativeResults) { ArrayList results = new ArrayList<>(); for (NativeScanResult result : nativeResults) { if (result.getSsid().length > 32) { Log.e(TAG, "Invalid SSID length (> 32 bytes): " + Arrays.toString(result.getSsid())); continue; } WifiSsid originalSsid = WifiSsid.fromBytes(result.getSsid()); MacAddress bssidMac = result.getBssid(); if (bssidMac == null) { Log.e(TAG, "Invalid MAC (BSSID) for SSID " + originalSsid); continue; } String bssid = bssidMac.toString(); ScanResult.InformationElement[] ies = InformationElementUtil.parseInformationElements(result.getInformationElements()); InformationElementUtil.Capabilities capabilities = new InformationElementUtil.Capabilities(); capabilities.from( ies, result.getCapabilities(), mIsEnhancedOpenSupported, result.getFrequencyMhz(), mUnknownAkmMap); String flags = capabilities.generateCapabilitiesString(); NetworkDetail networkDetail; try { networkDetail = new NetworkDetail(bssid, ies, null, result.getFrequencyMhz()); } catch (IllegalArgumentException e) { Log.e(TAG, "Illegal argument for scan result with bssid: " + bssid, e); continue; } WifiSsid translatedSsid = mWifiInjector.getSsidTranslator() .getTranslatedSsidAndRecordBssidCharset(originalSsid, bssidMac); ScanDetail scanDetail = new ScanDetail(networkDetail, translatedSsid, bssid, flags, result.getSignalMbm() / 100, result.getFrequencyMhz(), result.getTsf(), ies, null, result.getInformationElements()); ScanResult scanResult = scanDetail.getScanResult(); scanResult.setWifiStandard(wifiModeToWifiStandard(networkDetail.getWifiMode())); scanResult.ifaceName = ifaceName; // Fill up the radio chain info. scanResult.radioChainInfos = new ScanResult.RadioChainInfo[result.getRadioChainInfos().size()]; int idx = 0; for (RadioChainInfo nativeRadioChainInfo : result.getRadioChainInfos()) { scanResult.radioChainInfos[idx] = new ScanResult.RadioChainInfo(); scanResult.radioChainInfos[idx].id = nativeRadioChainInfo.getChainId(); scanResult.radioChainInfos[idx].level = nativeRadioChainInfo.getLevelDbm(); idx++; } // Fill MLO Attributes scanResult.setApMldMacAddress(networkDetail.getMldMacAddress()); scanResult.setApMloLinkId(networkDetail.getMloLinkId()); scanResult.setAffiliatedMloLinks(networkDetail.getAffiliatedMloLinks()); results.add(scanDetail); } if (mVerboseLoggingEnabled) { Log.d(TAG, "get " + results.size() + " scan results from wificond"); } return results; } @WifiAnnotations.WifiStandard private static int wifiModeToWifiStandard(int wifiMode) { switch (wifiMode) { case InformationElementUtil.WifiMode.MODE_11A: case InformationElementUtil.WifiMode.MODE_11B: case InformationElementUtil.WifiMode.MODE_11G: return ScanResult.WIFI_STANDARD_LEGACY; case InformationElementUtil.WifiMode.MODE_11N: return ScanResult.WIFI_STANDARD_11N; case InformationElementUtil.WifiMode.MODE_11AC: return ScanResult.WIFI_STANDARD_11AC; case InformationElementUtil.WifiMode.MODE_11AX: return ScanResult.WIFI_STANDARD_11AX; case InformationElementUtil.WifiMode.MODE_11BE: return ScanResult.WIFI_STANDARD_11BE; case InformationElementUtil.WifiMode.MODE_UNDEFINED: default: return ScanResult.WIFI_STANDARD_UNKNOWN; } } /** * Start PNO scan. * @param ifaceName Name of the interface. * @param pnoSettings Pno scan configuration. * @return true on success. */ public boolean startPnoScan(@NonNull String ifaceName, PnoSettings pnoSettings) { if (mMockWifiModem != null && mMockWifiModem.isMethodConfigured( MockWifiServiceUtil.MOCK_NL80211_SERVICE, "startPnoScan")) { Log.i(TAG, "startPnoScan was called from mock wificond"); return mMockWifiModem.getWifiNl80211Manager() .startPnoScan(ifaceName, pnoSettings.toNativePnoSettings(), Runnable::run, new WifiNl80211Manager.PnoScanRequestCallback() { @Override public void onPnoRequestSucceeded() { } @Override public void onPnoRequestFailed() { } }); } return mWifiCondManager.startPnoScan(ifaceName, pnoSettings.toNativePnoSettings(), Runnable::run, new WifiNl80211Manager.PnoScanRequestCallback() { @Override public void onPnoRequestSucceeded() { mWifiMetrics.incrementPnoScanStartAttemptCount(); } @Override public void onPnoRequestFailed() { WifiStatsLog.write(WifiStatsLog.PNO_SCAN_STOPPED, WifiStatsLog.PNO_SCAN_STOPPED__STOP_REASON__SCAN_FAILED, 0, false, false, false, false, // default values WifiStatsLog .PNO_SCAN_STOPPED__FAILURE_CODE__WIFICOND_REQUEST_FAILURE); } }); } /** * Stop PNO scan. * @param ifaceName Name of the interface. * @return true on success. */ public boolean stopPnoScan(@NonNull String ifaceName) { return mWifiCondManager.stopPnoScan(ifaceName); } /** * Sends an arbitrary 802.11 management frame on the current channel. * * @param ifaceName Name of the interface. * @param frame Bytes of the 802.11 management frame to be sent, including the header, but not * including the frame check sequence (FCS). * @param callback A callback triggered when the transmitted frame is ACKed or the transmission * fails. * @param mcs The MCS index that the frame will be sent at. If mcs < 0, the driver will select * the rate automatically. If the device does not support sending the frame at a * specified MCS rate, the transmission will be aborted and * {@link WifiNl80211Manager.SendMgmtFrameCallback#onFailure(int)} will be called * with reason {@link WifiNl80211Manager#SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED}. */ public void sendMgmtFrame(@NonNull String ifaceName, @NonNull byte[] frame, @NonNull WifiNl80211Manager.SendMgmtFrameCallback callback, int mcs) { mWifiCondManager.sendMgmtFrame(ifaceName, frame, mcs, Runnable::run, callback); } /** * Sends a probe request to the AP and waits for a response in order to determine whether * there is connectivity between the device and AP. * * @param ifaceName Name of the interface. * @param receiverMac the MAC address of the AP that the probe request will be sent to. * @param callback callback triggered when the probe was ACKed by the AP, or when * an error occurs after the link probe was started. * @param mcs The MCS index that this probe will be sent at. If mcs < 0, the driver will select * the rate automatically. If the device does not support sending the frame at a * specified MCS rate, the transmission will be aborted and * {@link WifiNl80211Manager.SendMgmtFrameCallback#onFailure(int)} will be called * with reason {@link WifiNl80211Manager#SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED}. */ public void probeLink(@NonNull String ifaceName, @NonNull MacAddress receiverMac, @NonNull WifiNl80211Manager.SendMgmtFrameCallback callback, int mcs) { if (callback == null) { Log.e(TAG, "callback cannot be null!"); return; } if (receiverMac == null) { Log.e(TAG, "Receiver MAC address cannot be null!"); callback.onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN); return; } String senderMacStr = getMacAddress(ifaceName); if (senderMacStr == null) { Log.e(TAG, "Failed to get this device's MAC Address"); callback.onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN); return; } byte[] frame = buildProbeRequestFrame( receiverMac.toByteArray(), NativeUtil.macAddressToByteArray(senderMacStr)); sendMgmtFrame(ifaceName, frame, callback, mcs); } // header = 24 bytes, minimal body = 2 bytes, no FCS (will be added by driver) private static final int BASIC_PROBE_REQUEST_FRAME_SIZE = 24 + 2; private byte[] buildProbeRequestFrame(byte[] receiverMac, byte[] transmitterMac) { ByteBuffer frame = ByteBuffer.allocate(BASIC_PROBE_REQUEST_FRAME_SIZE); // ByteBuffer is big endian by default, switch to little endian frame.order(ByteOrder.LITTLE_ENDIAN); // Protocol version = 0, Type = management, Subtype = Probe Request frame.put((byte) 0x40); // no flags set frame.put((byte) 0x00); // duration = 60 microseconds. Note: this is little endian // Note: driver should calculate the duration and replace it before sending, putting a // reasonable default value here just in case. frame.putShort((short) 0x3c); // receiver/destination MAC address byte array frame.put(receiverMac); // sender MAC address byte array frame.put(transmitterMac); // BSSID (same as receiver address since we are sending to the AP) frame.put(receiverMac); // Generate random sequence number, fragment number = 0 // Note: driver should replace the sequence number with the correct number that is // incremented from the last used sequence number. Putting a random sequence number as a // default here just in case. // bit 0 is least significant bit, bit 15 is most significant bit // bits [0, 7] go in byte 0 // bits [8, 15] go in byte 1 // bits [0, 3] represent the fragment number (which is 0) // bits [4, 15] represent the sequence number (which is random) // clear bits [0, 3] to set fragment number = 0 short sequenceAndFragmentNumber = (short) (mRandom.nextInt() & 0xfff0); frame.putShort(sequenceAndFragmentNumber); // NL80211 rejects frames with an empty body, so we just need to put a placeholder // information element. // Tag for SSID frame.put((byte) 0x00); // Represents broadcast SSID. Not accurate, but works as placeholder. frame.put((byte) 0x00); return frame.array(); } private static final int CONNECT_TO_HOSTAPD_RETRY_INTERVAL_MS = 100; private static final int CONNECT_TO_HOSTAPD_RETRY_TIMES = 50; /** * This method is called to wait for establishing connection to hostapd. * * @return true if connection is established, false otherwise. */ private boolean startAndWaitForHostapdConnection() { // Start initialization if not already started. if (!mHostapdHal.isInitializationStarted() && !mHostapdHal.initialize()) { return false; } if (!mHostapdHal.startDaemon()) { Log.e(TAG, "Failed to startup hostapd"); return false; } boolean connected = false; int connectTries = 0; while (!connected && connectTries++ < CONNECT_TO_HOSTAPD_RETRY_TIMES) { // Check if the initialization is complete. connected = mHostapdHal.isInitializationComplete(); if (connected) { break; } try { Thread.sleep(CONNECT_TO_HOSTAPD_RETRY_INTERVAL_MS); } catch (InterruptedException ignore) { } } return connected; } /** * Start Soft AP operation using the provided configuration. * * @param ifaceName Name of the interface. * @param config Configuration to use for the soft ap created. * @param isMetered Indicates the network is metered or not. * @param callback Callback for AP events. * @return one of {@link SoftApManager.StartResult} */ public @SoftApManager.StartResult int startSoftAp( @NonNull String ifaceName, SoftApConfiguration config, boolean isMetered, SoftApHalCallback callback) { if (mHostapdHal.isApInfoCallbackSupported()) { if (!mHostapdHal.registerApCallback(ifaceName, callback)) { Log.e(TAG, "Failed to register ap hal event callback"); return SoftApManager.START_RESULT_FAILURE_REGISTER_AP_CALLBACK_HOSTAPD; } } else { SoftApHalCallbackFromWificond softApHalCallbackFromWificond = new SoftApHalCallbackFromWificond(ifaceName, callback); if (!mWifiCondManager.registerApCallback(ifaceName, Runnable::run, softApHalCallbackFromWificond)) { Log.e(TAG, "Failed to register ap hal event callback from wificond"); return SoftApManager.START_RESULT_FAILURE_REGISTER_AP_CALLBACK_WIFICOND; } } if (!mHostapdHal.addAccessPoint(ifaceName, config, isMetered, callback::onFailure)) { String errorMsg = "Failed to add softAp"; Log.e(TAG, errorMsg); mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHostapd(); takeBugReportInterfaceFailureIfNeeded("Wi-Fi BugReport (softap interface failure)", errorMsg); return SoftApManager.START_RESULT_FAILURE_ADD_AP_HOSTAPD; } return SoftApManager.START_RESULT_SUCCESS; } /** * Force a softap client disconnect with specific reason code. * * @param ifaceName Name of the interface. * @param client Mac address to force disconnect in clients of the SoftAp. * @param reasonCode One of disconnect reason code which defined in {@link ApConfigUtil}. * @return true on success, false otherwise. */ public boolean forceClientDisconnect(@NonNull String ifaceName, @NonNull MacAddress client, int reasonCode) { return mHostapdHal.forceClientDisconnect(ifaceName, client, reasonCode); } /** * Set MAC address of the given interface * @param interfaceName Name of the interface * @param mac Mac address to change into * @return true on success */ public boolean setStaMacAddress(String interfaceName, MacAddress mac) { // TODO(b/72459123): Suppress interface down/up events from this call // Trigger an explicit disconnect to avoid losing the disconnect event reason (if currently // connected) from supplicant if the interface is brought down for MAC address change. disconnect(interfaceName); return mWifiVendorHal.setStaMacAddress(interfaceName, mac); } /** * Set MAC address of the given interface * @param interfaceName Name of the interface * @param mac Mac address to change into * @return true on success */ public boolean setApMacAddress(String interfaceName, MacAddress mac) { return mWifiVendorHal.setApMacAddress(interfaceName, mac); } /** * Returns true if Hal version supports setMacAddress, otherwise false. * * @param interfaceName Name of the interface */ public boolean isApSetMacAddressSupported(@NonNull String interfaceName) { return mWifiVendorHal.isApSetMacAddressSupported(interfaceName); } /** * Get the factory MAC address of the given interface * @param interfaceName Name of the interface. * @return factory MAC address, or null on a failed call or if feature is unavailable. */ public MacAddress getStaFactoryMacAddress(@NonNull String interfaceName) { return mWifiVendorHal.getStaFactoryMacAddress(interfaceName); } /** * Get the factory MAC address of the given interface * @param interfaceName Name of the interface. * @return factory MAC address, or null on a failed call or if feature is unavailable. */ public MacAddress getApFactoryMacAddress(@NonNull String interfaceName) { return mWifiVendorHal.getApFactoryMacAddress(interfaceName); } /** * Reset MAC address to factory MAC address on the given interface * * @param interfaceName Name of the interface * @return true for success */ public boolean resetApMacToFactoryMacAddress(@NonNull String interfaceName) { return mWifiVendorHal.resetApMacToFactoryMacAddress(interfaceName); } /** * Set the unsafe channels and restrictions to avoid for coex. * @param unsafeChannels List of {@link CoexUnsafeChannel} to avoid * @param restrictions Bitmask of WifiManager.COEX_RESTRICTION_ flags */ public void setCoexUnsafeChannels( @NonNull List unsafeChannels, int restrictions) { mCachedCoexUnsafeChannels.clear(); mCachedCoexUnsafeChannels.addAll(unsafeChannels); mCachedCoexRestrictions = restrictions; mWifiVendorHal.setCoexUnsafeChannels(mCachedCoexUnsafeChannels, mCachedCoexRestrictions); } /******************************************************** * Hostapd operations ********************************************************/ /** * Callback to notify hostapd death. */ public interface HostapdDeathEventHandler { /** * Invoked when the supplicant dies. */ void onDeath(); } /******************************************************** * Supplicant operations ********************************************************/ /** * Callback to notify supplicant death. */ public interface SupplicantDeathEventHandler { /** * Invoked when the supplicant dies. */ void onDeath(); } /** * Set supplicant log level * * @param turnOnVerbose Whether to turn on verbose logging or not. */ public void setSupplicantLogLevel(boolean turnOnVerbose) { mSupplicantStaIfaceHal.setLogLevel(turnOnVerbose); } /** * Trigger a reconnection if the iface is disconnected. * * @param ifaceName Name of the interface. * @return true if request is sent successfully, false otherwise. */ public boolean reconnect(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.reconnect(ifaceName); } /** * Trigger a reassociation even if the iface is currently connected. * * @param ifaceName Name of the interface. * @return true if request is sent successfully, false otherwise. */ public boolean reassociate(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.reassociate(ifaceName); } /** * Trigger a disconnection from the currently connected network. * * @param ifaceName Name of the interface. * @return true if request is sent successfully, false otherwise. */ public boolean disconnect(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.disconnect(ifaceName); } /** * Makes a callback to HIDL to getMacAddress from supplicant * * @param ifaceName Name of the interface. * @return string containing the MAC address, or null on a failed call */ public String getMacAddress(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.getMacAddress(ifaceName); } public static final int RX_FILTER_TYPE_V4_MULTICAST = 0; public static final int RX_FILTER_TYPE_V6_MULTICAST = 1; /** * Start filtering out Multicast V4 packets * @param ifaceName Name of the interface. * @return {@code true} if the operation succeeded, {@code false} otherwise * * Multicast filtering rules work as follows: * * The driver can filter multicast (v4 and/or v6) and broadcast packets when in * a power optimized mode (typically when screen goes off). * * In order to prevent the driver from filtering the multicast/broadcast packets, we have to * add a DRIVER RXFILTER-ADD rule followed by DRIVER RXFILTER-START to make the rule effective * * DRIVER RXFILTER-ADD Num * where Num = 0 - Unicast, 1 - Broadcast, 2 - Mutil4 or 3 - Multi6 * * and DRIVER RXFILTER-START * In order to stop the usage of these rules, we do * * DRIVER RXFILTER-STOP * DRIVER RXFILTER-REMOVE Num * where Num is as described for RXFILTER-ADD * * The SETSUSPENDOPT driver command overrides the filtering rules */ public boolean startFilteringMulticastV4Packets(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.stopRxFilter(ifaceName) && mSupplicantStaIfaceHal.removeRxFilter( ifaceName, RX_FILTER_TYPE_V4_MULTICAST) && mSupplicantStaIfaceHal.startRxFilter(ifaceName); } /** * Stop filtering out Multicast V4 packets. * @param ifaceName Name of the interface. * @return {@code true} if the operation succeeded, {@code false} otherwise */ public boolean stopFilteringMulticastV4Packets(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.stopRxFilter(ifaceName) && mSupplicantStaIfaceHal.addRxFilter( ifaceName, RX_FILTER_TYPE_V4_MULTICAST) && mSupplicantStaIfaceHal.startRxFilter(ifaceName); } /** * Start filtering out Multicast V6 packets * @param ifaceName Name of the interface. * @return {@code true} if the operation succeeded, {@code false} otherwise */ public boolean startFilteringMulticastV6Packets(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.stopRxFilter(ifaceName) && mSupplicantStaIfaceHal.removeRxFilter( ifaceName, RX_FILTER_TYPE_V6_MULTICAST) && mSupplicantStaIfaceHal.startRxFilter(ifaceName); } /** * Stop filtering out Multicast V6 packets. * @param ifaceName Name of the interface. * @return {@code true} if the operation succeeded, {@code false} otherwise */ public boolean stopFilteringMulticastV6Packets(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.stopRxFilter(ifaceName) && mSupplicantStaIfaceHal.addRxFilter( ifaceName, RX_FILTER_TYPE_V6_MULTICAST) && mSupplicantStaIfaceHal.startRxFilter(ifaceName); } public static final int BLUETOOTH_COEXISTENCE_MODE_ENABLED = 0; public static final int BLUETOOTH_COEXISTENCE_MODE_DISABLED = 1; public static final int BLUETOOTH_COEXISTENCE_MODE_SENSE = 2; /** * Sets the bluetooth coexistence mode. * * @param ifaceName Name of the interface. * @param mode One of {@link #BLUETOOTH_COEXISTENCE_MODE_DISABLED}, * {@link #BLUETOOTH_COEXISTENCE_MODE_ENABLED}, or * {@link #BLUETOOTH_COEXISTENCE_MODE_SENSE}. * @return Whether the mode was successfully set. */ public boolean setBluetoothCoexistenceMode(@NonNull String ifaceName, int mode) { return mSupplicantStaIfaceHal.setBtCoexistenceMode(ifaceName, mode); } /** * Enable or disable Bluetooth coexistence scan mode. When this mode is on, * some of the low-level scan parameters used by the driver are changed to * reduce interference with A2DP streaming. * * @param ifaceName Name of the interface. * @param setCoexScanMode whether to enable or disable this mode * @return {@code true} if the command succeeded, {@code false} otherwise. */ public boolean setBluetoothCoexistenceScanMode( @NonNull String ifaceName, boolean setCoexScanMode) { return mSupplicantStaIfaceHal.setBtCoexistenceScanModeEnabled( ifaceName, setCoexScanMode); } /** * Enable or disable suspend mode optimizations. * * @param ifaceName Name of the interface. * @param enabled true to enable, false otherwise. * @return true if request is sent successfully, false otherwise. */ public boolean setSuspendOptimizations(@NonNull String ifaceName, boolean enabled) { return mSupplicantStaIfaceHal.setSuspendModeEnabled(ifaceName, enabled); } /** * Set country code for STA interface * * @param ifaceName Name of the STA interface. * @param countryCode 2 byte ASCII string. For ex: US, CA. * @return true if request is sent successfully, false otherwise. */ public boolean setStaCountryCode(@NonNull String ifaceName, String countryCode) { if (mSupplicantStaIfaceHal.setCountryCode(ifaceName, countryCode)) { if (mCountryCodeChangeListener != null) { mCountryCodeChangeListener.onSetCountryCodeSucceeded(countryCode); } return true; } return false; } /** * Flush all previously configured HLPs. * * @return true if request is sent successfully, false otherwise. */ public boolean flushAllHlp(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.flushAllHlp(ifaceName); } /** * Set FILS HLP packet. * * @param dst Destination MAC address. * @param hlpPacket Hlp Packet data in hex. * @return true if request is sent successfully, false otherwise. */ public boolean addHlpReq(@NonNull String ifaceName, MacAddress dst, byte [] hlpPacket) { return mSupplicantStaIfaceHal.addHlpReq(ifaceName, dst.toByteArray(), hlpPacket); } /** * Initiate TDLS discover and setup or teardown with the specified peer. * * @param ifaceName Name of the interface. * @param macAddr MAC Address of the peer. * @param enable true to start discovery and setup, false to teardown. * @return true if request is sent successfully, false otherwise. */ public boolean startTdls(@NonNull String ifaceName, String macAddr, boolean enable) { boolean ret = true; if (enable) { mSupplicantStaIfaceHal.initiateTdlsDiscover(ifaceName, macAddr); ret = mSupplicantStaIfaceHal.initiateTdlsSetup(ifaceName, macAddr); } else { ret = mSupplicantStaIfaceHal.initiateTdlsTeardown(ifaceName, macAddr); } return ret; } /** * Start WPS pin display operation with the specified peer. * * @param ifaceName Name of the interface. * @param bssid BSSID of the peer. * @return true if request is sent successfully, false otherwise. */ public boolean startWpsPbc(@NonNull String ifaceName, String bssid) { return mSupplicantStaIfaceHal.startWpsPbc(ifaceName, bssid); } /** * Start WPS pin keypad operation with the specified pin. * * @param ifaceName Name of the interface. * @param pin Pin to be used. * @return true if request is sent successfully, false otherwise. */ public boolean startWpsPinKeypad(@NonNull String ifaceName, String pin) { return mSupplicantStaIfaceHal.startWpsPinKeypad(ifaceName, pin); } /** * Start WPS pin display operation with the specified peer. * * @param ifaceName Name of the interface. * @param bssid BSSID of the peer. * @return new pin generated on success, null otherwise. */ public String startWpsPinDisplay(@NonNull String ifaceName, String bssid) { return mSupplicantStaIfaceHal.startWpsPinDisplay(ifaceName, bssid); } /** * Sets whether to use external sim for SIM/USIM processing. * * @param ifaceName Name of the interface. * @param external true to enable, false otherwise. * @return true if request is sent successfully, false otherwise. */ public boolean setExternalSim(@NonNull String ifaceName, boolean external) { return mSupplicantStaIfaceHal.setExternalSim(ifaceName, external); } /** * Sim auth response types. */ public static final String SIM_AUTH_RESP_TYPE_GSM_AUTH = "GSM-AUTH"; public static final String SIM_AUTH_RESP_TYPE_UMTS_AUTH = "UMTS-AUTH"; public static final String SIM_AUTH_RESP_TYPE_UMTS_AUTS = "UMTS-AUTS"; /** * EAP-SIM Error Codes */ public static final int EAP_SIM_NOT_SUBSCRIBED = 1031; public static final int EAP_SIM_VENDOR_SPECIFIC_CERT_EXPIRED = 16385; /** * Send the sim auth response for the currently configured network. * * @param ifaceName Name of the interface. * @param type |GSM-AUTH|, |UMTS-AUTH| or |UMTS-AUTS|. * @param response Response params. * @return true if succeeds, false otherwise. */ public boolean simAuthResponse( @NonNull String ifaceName, String type, String response) { if (SIM_AUTH_RESP_TYPE_GSM_AUTH.equals(type)) { return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimGsmAuthResponse( ifaceName, response); } else if (SIM_AUTH_RESP_TYPE_UMTS_AUTH.equals(type)) { return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAuthResponse( ifaceName, response); } else if (SIM_AUTH_RESP_TYPE_UMTS_AUTS.equals(type)) { return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAutsResponse( ifaceName, response); } else { return false; } } /** * Send the eap sim gsm auth failure for the currently configured network. * * @param ifaceName Name of the interface. * @return true if succeeds, false otherwise. */ public boolean simAuthFailedResponse(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimGsmAuthFailure(ifaceName); } /** * Send the eap sim umts auth failure for the currently configured network. * * @param ifaceName Name of the interface. * @return true if succeeds, false otherwise. */ public boolean umtsAuthFailedResponse(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAuthFailure(ifaceName); } /** * Send the eap identity response for the currently configured network. * * @param ifaceName Name of the interface. * @param unencryptedResponse String to send. * @param encryptedResponse String to send. * @return true if succeeds, false otherwise. */ public boolean simIdentityResponse(@NonNull String ifaceName, String unencryptedResponse, String encryptedResponse) { return mSupplicantStaIfaceHal.sendCurrentNetworkEapIdentityResponse(ifaceName, unencryptedResponse, encryptedResponse); } /** * This get anonymous identity from supplicant and returns it as a string. * * @param ifaceName Name of the interface. * @return anonymous identity string if succeeds, null otherwise. */ public String getEapAnonymousIdentity(@NonNull String ifaceName) { String anonymousIdentity = mSupplicantStaIfaceHal .getCurrentNetworkEapAnonymousIdentity(ifaceName); if (TextUtils.isEmpty(anonymousIdentity)) { return anonymousIdentity; } int indexOfDecoration = anonymousIdentity.lastIndexOf('!'); if (indexOfDecoration >= 0) { if (anonymousIdentity.substring(indexOfDecoration).length() < 2) { // Invalid identity, shouldn't happen Log.e(TAG, "Unexpected anonymous identity: " + anonymousIdentity); return null; } // Truncate RFC 7542 decorated prefix, if exists. Keep only the anonymous identity or // pseudonym. anonymousIdentity = anonymousIdentity.substring(indexOfDecoration + 1); } return anonymousIdentity; } /** * Start WPS pin registrar operation with the specified peer and pin. * * @param ifaceName Name of the interface. * @param bssid BSSID of the peer. * @param pin Pin to be used. * @return true if request is sent successfully, false otherwise. */ public boolean startWpsRegistrar(@NonNull String ifaceName, String bssid, String pin) { return mSupplicantStaIfaceHal.startWpsRegistrar(ifaceName, bssid, pin); } /** * Cancels any ongoing WPS requests. * * @param ifaceName Name of the interface. * @return true if request is sent successfully, false otherwise. */ public boolean cancelWps(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.cancelWps(ifaceName); } /** * Set WPS device name. * * @param ifaceName Name of the interface. * @param name String to be set. * @return true if request is sent successfully, false otherwise. */ public boolean setDeviceName(@NonNull String ifaceName, String name) { return mSupplicantStaIfaceHal.setWpsDeviceName(ifaceName, name); } /** * Set WPS device type. * * @param ifaceName Name of the interface. * @param type Type specified as a string. Used format: -- * @return true if request is sent successfully, false otherwise. */ public boolean setDeviceType(@NonNull String ifaceName, String type) { return mSupplicantStaIfaceHal.setWpsDeviceType(ifaceName, type); } /** * Set WPS config methods * * @param cfg List of config methods. * @return true if request is sent successfully, false otherwise. */ public boolean setConfigMethods(@NonNull String ifaceName, String cfg) { return mSupplicantStaIfaceHal.setWpsConfigMethods(ifaceName, cfg); } /** * Set WPS manufacturer. * * @param ifaceName Name of the interface. * @param value String to be set. * @return true if request is sent successfully, false otherwise. */ public boolean setManufacturer(@NonNull String ifaceName, String value) { return mSupplicantStaIfaceHal.setWpsManufacturer(ifaceName, value); } /** * Set WPS model name. * * @param ifaceName Name of the interface. * @param value String to be set. * @return true if request is sent successfully, false otherwise. */ public boolean setModelName(@NonNull String ifaceName, String value) { return mSupplicantStaIfaceHal.setWpsModelName(ifaceName, value); } /** * Set WPS model number. * * @param ifaceName Name of the interface. * @param value String to be set. * @return true if request is sent successfully, false otherwise. */ public boolean setModelNumber(@NonNull String ifaceName, String value) { return mSupplicantStaIfaceHal.setWpsModelNumber(ifaceName, value); } /** * Set WPS serial number. * * @param ifaceName Name of the interface. * @param value String to be set. * @return true if request is sent successfully, false otherwise. */ public boolean setSerialNumber(@NonNull String ifaceName, String value) { return mSupplicantStaIfaceHal.setWpsSerialNumber(ifaceName, value); } /** * Enable or disable power save mode. * * @param ifaceName Name of the interface. * @param enabled true to enable, false to disable. */ public void setPowerSave(@NonNull String ifaceName, boolean enabled) { mSupplicantStaIfaceHal.setPowerSave(ifaceName, enabled); } /** * Enable or disable low latency mode. * * @param enabled true to enable, false to disable. * @return true on success, false on failure */ public boolean setLowLatencyMode(boolean enabled) { return mWifiVendorHal.setLowLatencyMode(enabled); } /** * Set concurrency priority between P2P & STA operations. * * @param isStaHigherPriority Set to true to prefer STA over P2P during concurrency operations, * false otherwise. * @return true if request is sent successfully, false otherwise. */ public boolean setConcurrencyPriority(boolean isStaHigherPriority) { return mSupplicantStaIfaceHal.setConcurrencyPriority(isStaHigherPriority); } /** * Enable/Disable auto reconnect functionality in wpa_supplicant. * * @param ifaceName Name of the interface. * @param enable true to enable auto reconnecting, false to disable. * @return true if request is sent successfully, false otherwise. */ public boolean enableStaAutoReconnect(@NonNull String ifaceName, boolean enable) { return mSupplicantStaIfaceHal.enableAutoReconnect(ifaceName, enable); } /** * Add the provided network configuration to wpa_supplicant and initiate connection to it. * This method does the following: * 1. Abort any ongoing scan to unblock the connection request. * 2. Remove any existing network in wpa_supplicant(This implicitly triggers disconnect). * 3. Add a new network to wpa_supplicant. * 4. Save the provided configuration to wpa_supplicant. * 5. Select the new network in wpa_supplicant. * 6. Triggers reconnect command to wpa_supplicant. * * @param ifaceName Name of the interface. * @param configuration WifiConfiguration parameters for the provided network. * @return {@code true} if it succeeds, {@code false} otherwise */ public boolean connectToNetwork(@NonNull String ifaceName, WifiConfiguration configuration) { // Abort ongoing scan before connect() to unblock connection request. mWifiCondManager.abortScan(ifaceName); return mSupplicantStaIfaceHal.connectToNetwork(ifaceName, configuration); } /** * Initiates roaming to the already configured network in wpa_supplicant. If the network * configuration provided does not match the already configured network, then this triggers * a new connection attempt (instead of roam). * 1. Abort any ongoing scan to unblock the roam request. * 2. First check if we're attempting to connect to the same network as we currently have * configured. * 3. Set the new bssid for the network in wpa_supplicant. * 4. Triggers reassociate command to wpa_supplicant. * * @param ifaceName Name of the interface. * @param configuration WifiConfiguration parameters for the provided network. * @return {@code true} if it succeeds, {@code false} otherwise */ public boolean roamToNetwork(@NonNull String ifaceName, WifiConfiguration configuration) { // Abort ongoing scan before connect() to unblock roaming request. mWifiCondManager.abortScan(ifaceName); return mSupplicantStaIfaceHal.roamToNetwork(ifaceName, configuration); } /** * Remove all the networks. * * @param ifaceName Name of the interface. * @return {@code true} if it succeeds, {@code false} otherwise */ public boolean removeAllNetworks(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.removeAllNetworks(ifaceName); } /** * Disable the currently configured network in supplicant * * @param ifaceName Name of the interface. */ public boolean disableNetwork(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.disableCurrentNetwork(ifaceName); } /** * Set the BSSID for the currently configured network in wpa_supplicant. * * @param ifaceName Name of the interface. * @return true if successful, false otherwise. */ public boolean setNetworkBSSID(@NonNull String ifaceName, String bssid) { return mSupplicantStaIfaceHal.setCurrentNetworkBssid(ifaceName, bssid); } /** * Initiate ANQP query. * * @param ifaceName Name of the interface. * @param bssid BSSID of the AP to be queried * @param anqpIds Set of anqp IDs. * @param hs20Subtypes Set of HS20 subtypes. * @return true on success, false otherwise. */ public boolean requestAnqp( @NonNull String ifaceName, String bssid, Set anqpIds, Set hs20Subtypes) { if (bssid == null || ((anqpIds == null || anqpIds.isEmpty()) && (hs20Subtypes == null || hs20Subtypes.isEmpty()))) { Log.e(TAG, "Invalid arguments for ANQP request."); return false; } ArrayList anqpIdList = new ArrayList<>(); for (Integer anqpId : anqpIds) { anqpIdList.add(anqpId.shortValue()); } ArrayList hs20SubtypeList = new ArrayList<>(); hs20SubtypeList.addAll(hs20Subtypes); return mSupplicantStaIfaceHal.initiateAnqpQuery( ifaceName, bssid, anqpIdList, hs20SubtypeList); } /** * Request a passpoint icon file |filename| from the specified AP |bssid|. * * @param ifaceName Name of the interface. * @param bssid BSSID of the AP * @param fileName name of the icon file * @return true if request is sent successfully, false otherwise */ public boolean requestIcon(@NonNull String ifaceName, String bssid, String fileName) { if (bssid == null || fileName == null) { Log.e(TAG, "Invalid arguments for Icon request."); return false; } return mSupplicantStaIfaceHal.initiateHs20IconQuery(ifaceName, bssid, fileName); } /** * Initiate Venue URL ANQP query. * * @param ifaceName Name of the interface. * @param bssid BSSID of the AP to be queried * @return true on success, false otherwise. */ public boolean requestVenueUrlAnqp( @NonNull String ifaceName, String bssid) { if (bssid == null) { Log.e(TAG, "Invalid arguments for Venue URL ANQP request."); return false; } return mSupplicantStaIfaceHal.initiateVenueUrlAnqpQuery(ifaceName, bssid); } /** * Get the currently configured network's WPS NFC token. * * @param ifaceName Name of the interface. * @return Hex string corresponding to the WPS NFC token. */ public String getCurrentNetworkWpsNfcConfigurationToken(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.getCurrentNetworkWpsNfcConfigurationToken(ifaceName); } /** * Clean HAL cached data for |networkId|. * * @param networkId network id of the network to be removed from supplicant. */ public void removeNetworkCachedData(int networkId) { mSupplicantStaIfaceHal.removeNetworkCachedData(networkId); } /** Clear HAL cached data for |networkId| if MAC address is changed. * * @param networkId network id of the network to be checked. * @param curMacAddress current MAC address */ public void removeNetworkCachedDataIfNeeded(int networkId, MacAddress curMacAddress) { mSupplicantStaIfaceHal.removeNetworkCachedDataIfNeeded(networkId, curMacAddress); } /* * DPP */ /** * Adds a DPP peer URI to the URI list. * * @param ifaceName Interface name * @param uri Bootstrap (URI) string (e.g. DPP:....) * @return ID, or -1 for failure */ public int addDppPeerUri(@NonNull String ifaceName, @NonNull String uri) { return mSupplicantStaIfaceHal.addDppPeerUri(ifaceName, uri); } /** * Removes a DPP URI to the URI list given an ID. * * @param ifaceName Interface name * @param bootstrapId Bootstrap (URI) ID * @return true when operation is successful, or false for failure */ public boolean removeDppUri(@NonNull String ifaceName, int bootstrapId) { return mSupplicantStaIfaceHal.removeDppUri(ifaceName, bootstrapId); } /** * Stops/aborts DPP Initiator request * * @param ifaceName Interface name * @return true when operation is successful, or false for failure */ public boolean stopDppInitiator(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.stopDppInitiator(ifaceName); } /** * Starts DPP Configurator-Initiator request * * @param ifaceName Interface name * @param peerBootstrapId Peer's bootstrap (URI) ID * @param ownBootstrapId Own bootstrap (URI) ID - Optional, 0 for none * @param ssid SSID of the selected network * @param password Password of the selected network, or * @param psk PSK of the selected network in hexadecimal representation * @param netRole The network role of the enrollee (STA or AP) * @param securityAkm Security AKM to use: PSK, SAE * @return true when operation is successful, or false for failure */ public boolean startDppConfiguratorInitiator(@NonNull String ifaceName, int peerBootstrapId, int ownBootstrapId, @NonNull String ssid, String password, String psk, int netRole, int securityAkm, byte[] privEcKey) { return mSupplicantStaIfaceHal.startDppConfiguratorInitiator(ifaceName, peerBootstrapId, ownBootstrapId, ssid, password, psk, netRole, securityAkm, privEcKey); } /** * Starts DPP Enrollee-Initiator request * * @param ifaceName Interface name * @param peerBootstrapId Peer's bootstrap (URI) ID * @param ownBootstrapId Own bootstrap (URI) ID - Optional, 0 for none * @return true when operation is successful, or false for failure */ public boolean startDppEnrolleeInitiator(@NonNull String ifaceName, int peerBootstrapId, int ownBootstrapId) { return mSupplicantStaIfaceHal.startDppEnrolleeInitiator(ifaceName, peerBootstrapId, ownBootstrapId); } /** * Callback to notify about DPP success, failure and progress events. */ public interface DppEventCallback { /** * Called when local DPP Enrollee successfully receives a new Wi-Fi configuration from the * peer DPP configurator. * * @param newWifiConfiguration New Wi-Fi configuration received from the configurator * @param connStatusRequested Flag to indicate that the configurator requested * connection status */ void onSuccessConfigReceived(WifiConfiguration newWifiConfiguration, boolean connStatusRequested); /** * DPP Success event. * * @param dppStatusCode Status code of the success event. */ void onSuccess(int dppStatusCode); /** * DPP Progress event. * * @param dppStatusCode Status code of the progress event. */ void onProgress(int dppStatusCode); /** * DPP Failure event. * * @param dppStatusCode Status code of the failure event. * @param ssid SSID of the network the Enrollee tried to connect to. * @param channelList List of channels the Enrollee scanned for the network. * @param bandList List of bands the Enrollee supports. */ void onFailure(int dppStatusCode, String ssid, String channelList, int[] bandList); /** * DPP Configurator Private keys update. * * @param key Configurator's private EC key. */ void onDppConfiguratorKeyUpdate(byte[] key); /** * Indicates that DPP connection status result frame is sent * * @param result DPP Status value indicating the result of a connection attempt. */ void onConnectionStatusResultSent(int result); } /** * Class to get generated bootstrap info for DPP responder operation. */ public static class DppBootstrapQrCodeInfo { public int bootstrapId; public int listenChannel; public String uri = new String(); DppBootstrapQrCodeInfo() { bootstrapId = -1; listenChannel = -1; } } /** * Generate DPP bootstrap Information:Bootstrap ID, DPP URI and the listen channel. * * @param ifaceName Interface name * @param deviceInfo Device specific info to attach in DPP URI. * @param dppCurve Elliptic curve cryptography type used to generate DPP * public/private key pair. * @return ID, or -1 for failure */ public DppBootstrapQrCodeInfo generateDppBootstrapInfoForResponder(@NonNull String ifaceName, String deviceInfo, int dppCurve) { return mSupplicantStaIfaceHal.generateDppBootstrapInfoForResponder(ifaceName, getMacAddress(ifaceName), deviceInfo, dppCurve); } /** * start DPP Enrollee responder mode. * * @param ifaceName Interface name * @param listenChannel Listen channel to wait for DPP authentication request. * @return ID, or -1 for failure */ public boolean startDppEnrolleeResponder(@NonNull String ifaceName, int listenChannel) { return mSupplicantStaIfaceHal.startDppEnrolleeResponder(ifaceName, listenChannel); } /** * Stops/aborts DPP Responder request * * @param ifaceName Interface name * @param ownBootstrapId Bootstrap (URI) ID * @return true when operation is successful, or false for failure */ public boolean stopDppResponder(@NonNull String ifaceName, int ownBootstrapId) { return mSupplicantStaIfaceHal.stopDppResponder(ifaceName, ownBootstrapId); } /** * Registers DPP event callbacks. * * @param dppEventCallback Callback object. */ public void registerDppEventCallback(DppEventCallback dppEventCallback) { mSupplicantStaIfaceHal.registerDppCallback(dppEventCallback); } /** * Check whether Supplicant is using the AIDL HAL service. * * @return true if the Supplicant is using the AIDL service, false otherwise. */ public boolean isSupplicantUsingAidlService() { return mSupplicantStaIfaceHal.isAidlService(); } /** * Check whether the Supplicant AIDL service is running at least the expected version. * * @param expectedVersion Version number to check. * @return true if the AIDL service is available and >= the expected version, false otherwise. */ public boolean isSupplicantAidlServiceVersionAtLeast(int expectedVersion) { return mSupplicantStaIfaceHal.isAidlServiceVersionAtLeast(expectedVersion); } /******************************************************** * Vendor HAL operations ********************************************************/ /** * Callback to notify vendor HAL death. */ public interface VendorHalDeathEventHandler { /** * Invoked when the vendor HAL dies. */ void onDeath(); } /** * Callback to notify when vendor HAL detects that a change in radio mode. */ public interface VendorHalRadioModeChangeEventHandler { /** * Invoked when the vendor HAL detects a change to MCC mode. * MCC (Multi channel concurrency) = Multiple interfaces are active on the same band, * different channels, same radios. * * @param band Band on which MCC is detected (specified by one of the * WifiScanner.WIFI_BAND_* constants) */ void onMcc(int band); /** * Invoked when the vendor HAL detects a change to SCC mode. * SCC (Single channel concurrency) = Multiple interfaces are active on the same band, same * channels, same radios. * * @param band Band on which SCC is detected (specified by one of the * WifiScanner.WIFI_BAND_* constants) */ void onScc(int band); /** * Invoked when the vendor HAL detects a change to SBS mode. * SBS (Single Band Simultaneous) = Multiple interfaces are active on the same band, * different channels, different radios. * * @param band Band on which SBS is detected (specified by one of the * WifiScanner.WIFI_BAND_* constants) */ void onSbs(int band); /** * Invoked when the vendor HAL detects a change to DBS mode. * DBS (Dual Band Simultaneous) = Multiple interfaces are active on the different bands, * different channels, different radios. */ void onDbs(); } /** * Tests whether the HAL is running or not */ public boolean isHalStarted() { return mWifiVendorHal.isHalStarted(); } /** * Tests whether the HAL is supported or not */ public boolean isHalSupported() { return mWifiVendorHal.isVendorHalSupported(); } // TODO: Change variable names to camel style. public static class ScanCapabilities { public int max_scan_cache_size; public int max_scan_buckets; public int max_ap_cache_per_scan; public int max_rssi_sample_size; public int max_scan_reporting_threshold; } /** * Gets the scan capabilities * * @param ifaceName Name of the interface. * @param capabilities object to be filled in * @return true for success. false for failure */ public boolean getBgScanCapabilities( @NonNull String ifaceName, ScanCapabilities capabilities) { return mWifiVendorHal.getBgScanCapabilities(ifaceName, capabilities); } public static class ChannelSettings { public int frequency; public int dwell_time_ms; public boolean passive; } public static class BucketSettings { public int bucket; public int band; public int period_ms; public int max_period_ms; public int step_count; public int report_events; public int num_channels; public ChannelSettings[] channels; } /** * Network parameters for hidden networks to be scanned for. */ public static class HiddenNetwork { public String ssid; @Override public boolean equals(Object otherObj) { if (this == otherObj) { return true; } else if (otherObj == null || getClass() != otherObj.getClass()) { return false; } HiddenNetwork other = (HiddenNetwork) otherObj; return Objects.equals(ssid, other.ssid); } @Override public int hashCode() { return Objects.hash(ssid); } } public static class ScanSettings { /** * Type of scan to perform. One of {@link WifiScanner#SCAN_TYPE_LOW_LATENCY}, * {@link WifiScanner#SCAN_TYPE_LOW_POWER} or {@link WifiScanner#SCAN_TYPE_HIGH_ACCURACY}. */ @WifiAnnotations.ScanType public int scanType; public int base_period_ms; public int max_ap_per_scan; public int report_threshold_percent; public int report_threshold_num_scans; public int num_buckets; public boolean enable6GhzRnr; /* Not used for bg scans. Only works for single scans. */ public HiddenNetwork[] hiddenNetworks; public BucketSettings[] buckets; public byte[] vendorIes; } /** * Network parameters to start PNO scan. */ public static class PnoNetwork { public String ssid; public byte flags; public byte auth_bit_field; public int[] frequencies; @Override public boolean equals(Object otherObj) { if (this == otherObj) { return true; } else if (otherObj == null || getClass() != otherObj.getClass()) { return false; } PnoNetwork other = (PnoNetwork) otherObj; return ((Objects.equals(ssid, other.ssid)) && (flags == other.flags) && (auth_bit_field == other.auth_bit_field)) && Arrays.equals(frequencies, other.frequencies); } @Override public int hashCode() { return Objects.hash(ssid, flags, auth_bit_field, Arrays.hashCode(frequencies)); } android.net.wifi.nl80211.PnoNetwork toNativePnoNetwork() { android.net.wifi.nl80211.PnoNetwork nativePnoNetwork = new android.net.wifi.nl80211.PnoNetwork(); nativePnoNetwork.setHidden( (flags & WifiScanner.PnoSettings.PnoNetwork.FLAG_DIRECTED_SCAN) != 0); try { nativePnoNetwork.setSsid( NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(ssid))); } catch (IllegalArgumentException e) { Log.e(TAG, "Illegal argument " + ssid, e); return null; } nativePnoNetwork.setFrequenciesMhz(frequencies); return nativePnoNetwork; } } /** * Parameters to start PNO scan. This holds the list of networks which are going to used for * PNO scan. */ public static class PnoSettings { public int min5GHzRssi; public int min24GHzRssi; public int min6GHzRssi; public int periodInMs; public int scanIterations; public int scanIntervalMultiplier; public boolean isConnected; public PnoNetwork[] networkList; android.net.wifi.nl80211.PnoSettings toNativePnoSettings() { android.net.wifi.nl80211.PnoSettings nativePnoSettings = new android.net.wifi.nl80211.PnoSettings(); nativePnoSettings.setIntervalMillis(periodInMs); nativePnoSettings.setMin2gRssiDbm(min24GHzRssi); nativePnoSettings.setMin5gRssiDbm(min5GHzRssi); nativePnoSettings.setMin6gRssiDbm(min6GHzRssi); if (SdkLevel.isAtLeastU()) { nativePnoSettings.setScanIterations(scanIterations); nativePnoSettings.setScanIntervalMultiplier(scanIntervalMultiplier); } List pnoNetworks = new ArrayList<>(); if (networkList != null) { for (PnoNetwork network : networkList) { android.net.wifi.nl80211.PnoNetwork nativeNetwork = network.toNativePnoNetwork(); if (nativeNetwork != null) { pnoNetworks.add(nativeNetwork); } } } nativePnoSettings.setPnoNetworks(pnoNetworks); return nativePnoSettings; } } public static interface ScanEventHandler { /** * Called for each AP as it is found with the entire contents of the beacon/probe response. * Only called when WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT is specified. */ void onFullScanResult(ScanResult fullScanResult, int bucketsScanned); /** * Callback on an event during a gscan scan. * See WifiNative.WIFI_SCAN_* for possible values. */ void onScanStatus(int event); /** * Called with the current cached scan results when gscan is paused. */ void onScanPaused(WifiScanner.ScanData[] data); /** * Called with the current cached scan results when gscan is resumed. */ void onScanRestarted(); /** * Callback to notify when the scan request fails. * See WifiScanner.REASON_* for possible values. */ void onScanRequestFailed(int errorCode); } /** * Handler to notify the occurrence of various events during PNO scan. */ public interface PnoEventHandler { /** * Callback to notify when one of the shortlisted networks is found during PNO scan. * @param results List of Scan results received. */ void onPnoNetworkFound(ScanResult[] results); /** * Callback to notify when the PNO scan schedule fails. */ void onPnoScanFailed(); } public static final int WIFI_SCAN_RESULTS_AVAILABLE = 0; public static final int WIFI_SCAN_THRESHOLD_NUM_SCANS = 1; public static final int WIFI_SCAN_THRESHOLD_PERCENT = 2; public static final int WIFI_SCAN_FAILED = 3; /** * Starts a background scan. * Any ongoing scan will be stopped first * * @param ifaceName Name of the interface. * @param settings to control the scan * @param eventHandler to call with the results * @return true for success */ public boolean startBgScan( @NonNull String ifaceName, ScanSettings settings, ScanEventHandler eventHandler) { return mWifiVendorHal.startBgScan(ifaceName, settings, eventHandler); } /** * Stops any ongoing backgound scan * @param ifaceName Name of the interface. */ public void stopBgScan(@NonNull String ifaceName) { mWifiVendorHal.stopBgScan(ifaceName); } /** * Pauses an ongoing backgound scan * @param ifaceName Name of the interface. */ public void pauseBgScan(@NonNull String ifaceName) { mWifiVendorHal.pauseBgScan(ifaceName); } /** * Restarts a paused scan * @param ifaceName Name of the interface. */ public void restartBgScan(@NonNull String ifaceName) { mWifiVendorHal.restartBgScan(ifaceName); } /** * Gets the latest scan results received. * @param ifaceName Name of the interface. */ public WifiScanner.ScanData[] getBgScanResults(@NonNull String ifaceName) { return mWifiVendorHal.getBgScanResults(ifaceName); } /** * Sets whether global location mode is enabled. */ public void setLocationModeEnabled(boolean enabled) { if (!mIsLocationModeEnabled && enabled) { mLastLocationModeEnabledTimeMs = SystemClock.elapsedRealtime(); } Log.d(TAG, "mIsLocationModeEnabled " + enabled + " mLastLocationModeEnabledTimeMs " + mLastLocationModeEnabledTimeMs); mIsLocationModeEnabled = enabled; } @NonNull private ScanResult[] getCachedScanResultsFilteredByLocationModeEnabled( @NonNull ScanResult[] scanResults) { List resultList = new ArrayList(); for (ScanResult scanResult : scanResults) { if (mIsLocationModeEnabled && scanResult.timestamp >= mLastLocationModeEnabledTimeMs * 1000) { resultList.add(scanResult); } } return resultList.toArray(new ScanResult[0]); } /** * Gets the cached scan data from the given client interface */ @Nullable ScanData getCachedScanResults(String ifaceName) { ScanData scanData = mWifiVendorHal.getCachedScanData(ifaceName); if (scanData == null || scanData.getResults() == null) { return null; } ScanResult[] results = getCachedScanResultsFilteredByLocationModeEnabled( scanData.getResults()); return new ScanData(0, 0, 0, scanData.getScannedBands(), results); } /** * Gets the cached scan data from all client interfaces */ @NonNull public ScanData getCachedScanResultsFromAllClientIfaces() { ScanData consolidatedScanData = new ScanData(); Set ifaceNames = getClientInterfaceNames(); for (String ifaceName : ifaceNames) { ScanData scanData = getCachedScanResults(ifaceName); if (scanData == null) { continue; } consolidatedScanData.addResults(scanData.getResults()); } return consolidatedScanData; } /** * Gets the latest link layer stats * @param ifaceName Name of the interface. */ public WifiLinkLayerStats getWifiLinkLayerStats(@NonNull String ifaceName) { WifiLinkLayerStats stats = mWifiVendorHal.getWifiLinkLayerStats(ifaceName); if (stats != null) { stats.aggregateLinkLayerStats(); } return stats; } /** * Gets the usable channels * @param band one of the {@code WifiScanner#WIFI_BAND_*} constants. * @param mode bitmask of {@code WifiAvailablechannel#OP_MODE_*} constants. * @param filter bitmask of filters (regulatory, coex, concurrency). * * @return list of channels */ public List getUsableChannels( @WifiScanner.WifiBand int band, @WifiAvailableChannel.OpMode int mode, @WifiAvailableChannel.Filter int filter) { return mWifiVendorHal.getUsableChannels(band, mode, filter); } /** * Returns whether the device supports the requested * {@link HalDeviceManager.HdmIfaceTypeForCreation} combo. */ public boolean canDeviceSupportCreateTypeCombo(SparseArray combo) { synchronized (mLock) { return mWifiVendorHal.canDeviceSupportCreateTypeCombo(combo); } } /** * Returns whether STA + AP concurrency is supported or not. */ public boolean isStaApConcurrencySupported() { synchronized (mLock) { return mWifiVendorHal.canDeviceSupportCreateTypeCombo( new SparseArray() {{ put(HDM_CREATE_IFACE_STA, 1); put(HDM_CREATE_IFACE_AP, 1); }}); } } /** * Returns whether STA + STA concurrency is supported or not. */ public boolean isStaStaConcurrencySupported() { synchronized (mLock) { return mWifiVendorHal.canDeviceSupportCreateTypeCombo( new SparseArray() {{ put(HDM_CREATE_IFACE_STA, 2); }}); } } /** * Returns whether P2p + STA concurrency is supported or not. */ public boolean isP2pStaConcurrencySupported() { synchronized (mLock) { return mWifiVendorHal.canDeviceSupportCreateTypeCombo( new SparseArray() {{ put(HDM_CREATE_IFACE_STA, 1); put(HDM_CREATE_IFACE_P2P, 1); }}); } } /** * Returns whether Nan + STA concurrency is supported or not. */ public boolean isNanStaConcurrencySupported() { synchronized (mLock) { return mWifiVendorHal.canDeviceSupportCreateTypeCombo( new SparseArray() {{ put(HDM_CREATE_IFACE_STA, 1); put(HDM_CREATE_IFACE_NAN, 1); }}); } } /** * Returns whether a new AP iface can be created or not. */ public boolean isItPossibleToCreateApIface(@NonNull WorkSource requestorWs) { synchronized (mLock) { if (!isHalStarted()) { return canDeviceSupportCreateTypeCombo( new SparseArray() {{ put(HDM_CREATE_IFACE_AP, 1); }}); } return mWifiVendorHal.isItPossibleToCreateApIface(requestorWs); } } /** * Returns whether a new AP iface can be created or not. */ public boolean isItPossibleToCreateBridgedApIface(@NonNull WorkSource requestorWs) { synchronized (mLock) { if (!isHalStarted()) { return canDeviceSupportCreateTypeCombo( new SparseArray() {{ put(HDM_CREATE_IFACE_AP_BRIDGE, 1); }}); } return mWifiVendorHal.isItPossibleToCreateBridgedApIface(requestorWs); } } /** * Returns whether creating a single AP does not require destroying an existing iface, but * creating a bridged AP does. */ public boolean shouldDowngradeToSingleApForConcurrency(@NonNull WorkSource requestorWs) { synchronized (mLock) { if (!mWifiVendorHal.isHalStarted()) { return false; } return !mWifiVendorHal.canDeviceSupportAdditionalIface(HDM_CREATE_IFACE_AP_BRIDGE, requestorWs) && mWifiVendorHal.canDeviceSupportAdditionalIface(HDM_CREATE_IFACE_AP, requestorWs); } } /** * Returns whether a new STA iface can be created or not. */ public boolean isItPossibleToCreateStaIface(@NonNull WorkSource requestorWs) { synchronized (mLock) { if (!isHalStarted()) { return canDeviceSupportCreateTypeCombo( new SparseArray() {{ put(HDM_CREATE_IFACE_STA, 1); }}); } return mWifiVendorHal.isItPossibleToCreateStaIface(requestorWs); } } /** * Set primary connection when multiple STA ifaces are active. * * @param ifaceName Name of the interface. * @return true for success */ public boolean setMultiStaPrimaryConnection(@NonNull String ifaceName) { synchronized (mLock) { return mWifiVendorHal.setMultiStaPrimaryConnection(ifaceName); } } /** * Multi STA use case flags. */ public static final int DUAL_STA_TRANSIENT_PREFER_PRIMARY = 0; public static final int DUAL_STA_NON_TRANSIENT_UNBIASED = 1; @IntDef({DUAL_STA_TRANSIENT_PREFER_PRIMARY, DUAL_STA_NON_TRANSIENT_UNBIASED}) @Retention(RetentionPolicy.SOURCE) public @interface MultiStaUseCase{} /** * Set use-case when multiple STA ifaces are active. * * @param useCase one of the use cases. * @return true for success */ public boolean setMultiStaUseCase(@MultiStaUseCase int useCase) { synchronized (mLock) { return mWifiVendorHal.setMultiStaUseCase(useCase); } } /** * Get the supported features * * @param ifaceName Name of the interface. * @return bitmask defined by WifiManager.WIFI_FEATURE_* */ public long getSupportedFeatureSet(String ifaceName) { synchronized (mLock) { long featureSet = 0; // First get the complete feature set stored in config store when supplicant was // started featureSet = getCompleteFeatureSetFromConfigStore(); // Include the feature set saved in interface class. This is to make sure that // framework is returning the feature set for SoftAp only products and multi-chip // products. if (ifaceName != null) { Iface iface = mIfaceMgr.getIface(ifaceName); if (iface != null) { featureSet |= iface.featureSet; } } return featureSet; } } /** * Get the supported bands for STA mode. * @return supported bands */ public @WifiScanner.WifiBand int getSupportedBandsForSta(String ifaceName) { synchronized (mLock) { if (ifaceName != null) { Iface iface = mIfaceMgr.getIface(ifaceName); if (iface != null) { return iface.bandsSupported; } } return WifiScanner.WIFI_BAND_UNSPECIFIED; } } /** * Get the supported features * * @param ifaceName Name of the interface. * @return bitmask defined by WifiManager.WIFI_FEATURE_* */ private long getSupportedFeatureSetInternal(@NonNull String ifaceName) { long featureSet = mSupplicantStaIfaceHal.getAdvancedCapabilities(ifaceName) | mWifiVendorHal.getSupportedFeatureSet(ifaceName) | mSupplicantStaIfaceHal.getWpaDriverFeatureSet(ifaceName); if (SdkLevel.isAtLeastT()) { if (((featureSet & WifiManager.WIFI_FEATURE_DPP) != 0) && mContext.getResources().getBoolean(R.bool.config_wifiDppAkmSupported)) { // Set if DPP is filled by supplicant and DPP AKM is enabled by overlay. featureSet |= WifiManager.WIFI_FEATURE_DPP_AKM; Log.v(TAG, ": DPP AKM supported"); } } Bundle twtCapabilities = mWifiVendorHal.getTwtCapabilities(ifaceName); if (twtCapabilities != null) mCachedTwtCapabilities.put(ifaceName, twtCapabilities); return featureSet; } private void updateSupportedBandForStaInternal(Iface iface) { List usableChannelList = mWifiVendorHal.getUsableChannels(WifiScanner.WIFI_BAND_24_5_WITH_DFS_6_60_GHZ, WifiAvailableChannel.OP_MODE_STA, WifiAvailableChannel.FILTER_REGULATORY); int bands = 0; if (usableChannelList == null) { // If HAL doesn't support getUsableChannels then check wificond if (getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ).length > 0) { bands |= WifiScanner.WIFI_BAND_24_GHZ; } if (getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ).length > 0) { bands |= WifiScanner.WIFI_BAND_5_GHZ; } if (getChannelsForBand(WifiScanner.WIFI_BAND_6_GHZ).length > 0) { bands |= WifiScanner.WIFI_BAND_6_GHZ; } if (getChannelsForBand(WifiScanner.WIFI_BAND_60_GHZ).length > 0) { bands |= WifiScanner.WIFI_BAND_60_GHZ; } } else { for (int i = 0; i < usableChannelList.size(); i++) { int frequency = usableChannelList.get(i).getFrequencyMhz(); if (ScanResult.is24GHz(frequency)) { bands |= WifiScanner.WIFI_BAND_24_GHZ; } else if (ScanResult.is5GHz(frequency)) { bands |= WifiScanner.WIFI_BAND_5_GHZ; } else if (ScanResult.is6GHz(frequency)) { bands |= WifiScanner.WIFI_BAND_6_GHZ; } else if (ScanResult.is60GHz(frequency)) { bands |= WifiScanner.WIFI_BAND_60_GHZ; } } } if (mVerboseLoggingEnabled) { Log.i(TAG, "updateSupportedBandForStaInternal " + iface.name + " : 0x" + Integer.toHexString(bands)); } iface.bandsSupported = bands; } /** * Class to retrieve connection capability parameters after association */ public static class ConnectionCapabilities { public @WifiAnnotations.WifiStandard int wifiStandard; public int channelBandwidth; public int maxNumberTxSpatialStreams; public int maxNumberRxSpatialStreams; public boolean is11bMode; /** Indicates the AP support for TID-to-link mapping negotiation. */ public boolean apTidToLinkMapNegotiationSupported; public @NonNull List vendorData; ConnectionCapabilities() { wifiStandard = ScanResult.WIFI_STANDARD_UNKNOWN; channelBandwidth = ScanResult.CHANNEL_WIDTH_20MHZ; maxNumberTxSpatialStreams = 1; maxNumberRxSpatialStreams = 1; is11bMode = false; vendorData = Collections.emptyList(); } } /** * Returns connection capabilities of the current network * * @param ifaceName Name of the interface. * @return connection capabilities of the current network */ public ConnectionCapabilities getConnectionCapabilities(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.getConnectionCapabilities(ifaceName); } /** * Request signal polling to supplicant. * * @param ifaceName Name of the interface. * Returns an array of SignalPollResult objects. * Returns null on failure. */ @Nullable public WifiSignalPollResults signalPoll(@NonNull String ifaceName) { if (mMockWifiModem != null && mMockWifiModem.isMethodConfigured( MockWifiServiceUtil.MOCK_NL80211_SERVICE, "signalPoll")) { Log.i(TAG, "signalPoll was called from mock wificond"); WifiNl80211Manager.SignalPollResult result = mMockWifiModem.getWifiNl80211Manager().signalPoll(ifaceName); if (result != null) { // Convert WifiNl80211Manager#SignalPollResult to WifiSignalPollResults. // Assume single link and linkId = 0. WifiSignalPollResults results = new WifiSignalPollResults(); results.addEntry(0, result.currentRssiDbm, result.txBitrateMbps, result.rxBitrateMbps, result.associationFrequencyMHz); return results; } } // Query supplicant. WifiSignalPollResults results = mSupplicantStaIfaceHal.getSignalPollResults( ifaceName); if (results == null) { // Fallback to WifiCond. WifiNl80211Manager.SignalPollResult result = mWifiCondManager.signalPoll(ifaceName); if (result != null) { // Convert WifiNl80211Manager#SignalPollResult to WifiSignalPollResults. // Assume single link and linkId = 0. results = new WifiSignalPollResults(); results.addEntry(0, result.currentRssiDbm, result.txBitrateMbps, result.rxBitrateMbps, result.associationFrequencyMHz); } } return results; } /** * Class to represent a connection MLO Link */ public static class ConnectionMloLink { private final int mLinkId; private final MacAddress mStaMacAddress; private final BitSet mTidsUplinkMap; private final BitSet mTidsDownlinkMap; private final MacAddress mApMacAddress; private final int mFrequencyMHz; ConnectionMloLink(int id, MacAddress staMacAddress, MacAddress apMacAddress, byte tidsUplink, byte tidsDownlink, int frequencyMHz) { mLinkId = id; mStaMacAddress = staMacAddress; mApMacAddress = apMacAddress; mTidsDownlinkMap = BitSet.valueOf(new byte[] { tidsDownlink }); mTidsUplinkMap = BitSet.valueOf(new byte[] { tidsUplink }); mFrequencyMHz = frequencyMHz; }; /** * Check if there is any TID mapped to this link in uplink of downlink direction. * * @return true if there is any TID mapped to this link, otherwise false. */ public boolean isAnyTidMapped() { if (mTidsDownlinkMap.isEmpty() && mTidsUplinkMap.isEmpty()) { return false; } return true; } /** * Check if a TID is mapped to this link in uplink direction. * * @param tid TID value. * @return true if the TID is mapped in uplink direction. Otherwise, false. */ public boolean isTidMappedToUplink(byte tid) { if (tid < mTidsUplinkMap.length()) { return mTidsUplinkMap.get(tid); } return false; } /** * Check if a TID is mapped to this link in downlink direction. Otherwise, false. * * @param tid TID value * @return true if the TID is mapped in downlink direction. Otherwise, false. */ public boolean isTidMappedtoDownlink(byte tid) { if (tid < mTidsDownlinkMap.length()) { return mTidsDownlinkMap.get(tid); } return false; } /** * Get link id for the link. * * @return link id. */ public int getLinkId() { return mLinkId; } /** * Get link STA MAC address. * * @return link mac address. */ public MacAddress getStaMacAddress() { return mStaMacAddress; } /** * Get link AP MAC address. * * @return MAC address. */ public MacAddress getApMacAddress() { return mApMacAddress; } /** * Get link frequency in MHz. * * @return frequency in Mhz. */ public int getFrequencyMHz() { return mFrequencyMHz; } } /** * Class to represent the MLO links info for a connection that is collected after association */ public static class ConnectionMloLinksInfo { public ConnectionMloLink[] links; public MacAddress apMldMacAddress; public int apMloLinkId; ConnectionMloLinksInfo() { // Nothing for now } } /** * Returns connection MLO Links Info. * * @param ifaceName Name of the interface. * @return connection MLO Links Info */ public ConnectionMloLinksInfo getConnectionMloLinksInfo(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.getConnectionMloLinksInfo(ifaceName); } /** * Get the APF (Android Packet Filter) capabilities of the device * @param ifaceName Name of the interface. */ public ApfCapabilities getApfCapabilities(@NonNull String ifaceName) { return mWifiVendorHal.getApfCapabilities(ifaceName); } /** * Installs an APF program on this iface, replacing any existing program. * * @param ifaceName Name of the interface * @param filter is the android packet filter program * @return true for success */ public boolean installPacketFilter(@NonNull String ifaceName, byte[] filter) { return mWifiVendorHal.installPacketFilter(ifaceName, filter); } /** * Reads the APF program and data buffer for this iface. * * @param ifaceName Name of the interface * @return the buffer returned by the driver, or null in case of an error */ public byte[] readPacketFilter(@NonNull String ifaceName) { return mWifiVendorHal.readPacketFilter(ifaceName); } /** * Set country code for this AP iface. * @param ifaceName Name of the AP interface. * @param countryCode - two-letter country code (as ISO 3166) * @return true for success */ public boolean setApCountryCode(@NonNull String ifaceName, String countryCode) { if (mWifiVendorHal.setApCountryCode(ifaceName, countryCode)) { if (mCountryCodeChangeListener != null) { mCountryCodeChangeListener.onSetCountryCodeSucceeded(countryCode); } return true; } return false; } /** * Set country code for this chip * @param countryCode - two-letter country code (as ISO 3166) * @return true for success */ public boolean setChipCountryCode(String countryCode) { if (mWifiVendorHal.setChipCountryCode(countryCode)) { if (mCountryCodeChangeListener != null) { mCountryCodeChangeListener.onSetCountryCodeSucceeded(countryCode); } return true; } return false; } //--------------------------------------------------------------------------------- /* Wifi Logger commands/events */ public static interface WifiLoggerEventHandler { void onRingBufferData(RingBufferStatus status, byte[] buffer); void onWifiAlert(int errorCode, byte[] buffer); } /** * Registers the logger callback and enables alerts. * Ring buffer data collection is only triggered when |startLoggingRingBuffer| is invoked. * * @param handler Callback to be invoked. * @return true on success, false otherwise. */ public boolean setLoggingEventHandler(WifiLoggerEventHandler handler) { return mWifiVendorHal.setLoggingEventHandler(handler); } /** * Control debug data collection * * @param verboseLevel 0 to 3, inclusive. 0 stops logging. * @param flags Ignored. * @param maxInterval Maximum interval between reports; ignore if 0. * @param minDataSize Minimum data size in buffer for report; ignore if 0. * @param ringName Name of the ring for which data collection is to start. * @return true for success, false otherwise. */ public boolean startLoggingRingBuffer(int verboseLevel, int flags, int maxInterval, int minDataSize, String ringName){ return mWifiVendorHal.startLoggingRingBuffer( verboseLevel, flags, maxInterval, minDataSize, ringName); } /** * Logger features exposed. * This is a no-op now, will always return -1. * * @return true on success, false otherwise. */ public int getSupportedLoggerFeatureSet() { return mWifiVendorHal.getSupportedLoggerFeatureSet(); } /** * Stops all logging and resets the logger callback. * This stops both the alerts and ring buffer data collection. * @return true on success, false otherwise. */ public boolean resetLogHandler() { return mWifiVendorHal.resetLogHandler(); } /** * Vendor-provided wifi driver version string * * @return String returned from the HAL. */ public String getDriverVersion() { return mWifiVendorHal.getDriverVersion(); } /** * Vendor-provided wifi firmware version string * * @return String returned from the HAL. */ public String getFirmwareVersion() { return mWifiVendorHal.getFirmwareVersion(); } public static class RingBufferStatus{ public String name; public int flag; public int ringBufferId; public int ringBufferByteSize; public int verboseLevel; int writtenBytes; int readBytes; int writtenRecords; // Bit masks for interpreting |flag| public static final int HAS_BINARY_ENTRIES = (1 << 0); public static final int HAS_ASCII_ENTRIES = (1 << 1); public static final int HAS_PER_PACKET_ENTRIES = (1 << 2); @Override public String toString() { return "name: " + name + " flag: " + flag + " ringBufferId: " + ringBufferId + " ringBufferByteSize: " +ringBufferByteSize + " verboseLevel: " +verboseLevel + " writtenBytes: " + writtenBytes + " readBytes: " + readBytes + " writtenRecords: " + writtenRecords; } } /** * API to get the status of all ring buffers supported by driver */ public RingBufferStatus[] getRingBufferStatus() { return mWifiVendorHal.getRingBufferStatus(); } /** * Indicates to driver that all the data has to be uploaded urgently * * @param ringName Name of the ring buffer requested. * @return true on success, false otherwise. */ public boolean getRingBufferData(String ringName) { return mWifiVendorHal.getRingBufferData(ringName); } /** * Request hal to flush ring buffers to files * * @return true on success, false otherwise. */ public boolean flushRingBufferData() { return mWifiVendorHal.flushRingBufferData(); } /** * Request vendor debug info from the firmware * * @return Raw data obtained from the HAL. */ public byte[] getFwMemoryDump() { return mWifiVendorHal.getFwMemoryDump(); } /** * Request vendor debug info from the driver * * @return Raw data obtained from the HAL. */ public byte[] getDriverStateDump() { return mWifiVendorHal.getDriverStateDump(); } /** * Dump information about the internal state * * @param pw PrintWriter to write dump to */ protected void dump(PrintWriter pw) { pw.println("Dump of " + TAG); pw.println("mIsLocationModeEnabled: " + mIsLocationModeEnabled); pw.println("mLastLocationModeEnabledTimeMs: " + mLastLocationModeEnabledTimeMs); mHostapdHal.dump(pw); } //--------------------------------------------------------------------------------- /* Packet fate API */ @Immutable public abstract static class FateReport { final static int USEC_PER_MSEC = 1000; // The driver timestamp is a 32-bit counter, in microseconds. This field holds the // maximal value of a driver timestamp in milliseconds. final static int MAX_DRIVER_TIMESTAMP_MSEC = (int) (0xffffffffL / 1000); final static SimpleDateFormat dateFormatter = new SimpleDateFormat("HH:mm:ss.SSS"); public final byte mFate; public final long mDriverTimestampUSec; public final byte mFrameType; public final byte[] mFrameBytes; public final long mEstimatedWallclockMSec; FateReport(byte fate, long driverTimestampUSec, byte frameType, byte[] frameBytes) { mFate = fate; mDriverTimestampUSec = driverTimestampUSec; mEstimatedWallclockMSec = convertDriverTimestampUSecToWallclockMSec(mDriverTimestampUSec); mFrameType = frameType; mFrameBytes = frameBytes; } public String toTableRowString() { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); FrameParser parser = new FrameParser(mFrameType, mFrameBytes); dateFormatter.setTimeZone(TimeZone.getDefault()); pw.format("%-15s %12s %-9s %-32s %-12s %-23s %s\n", mDriverTimestampUSec, dateFormatter.format(new Date(mEstimatedWallclockMSec)), directionToString(), fateToString(), parser.mMostSpecificProtocolString, parser.mTypeString, parser.mResultString); return sw.toString(); } public String toVerboseStringWithPiiAllowed() { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); FrameParser parser = new FrameParser(mFrameType, mFrameBytes); pw.format("Frame direction: %s\n", directionToString()); pw.format("Frame timestamp: %d\n", mDriverTimestampUSec); pw.format("Frame fate: %s\n", fateToString()); pw.format("Frame type: %s\n", frameTypeToString(mFrameType)); pw.format("Frame protocol: %s\n", parser.mMostSpecificProtocolString); pw.format("Frame protocol type: %s\n", parser.mTypeString); pw.format("Frame length: %d\n", mFrameBytes.length); pw.append("Frame bytes"); pw.append(HexDump.dumpHexString(mFrameBytes)); // potentially contains PII pw.append("\n"); return sw.toString(); } /* Returns a header to match the output of toTableRowString(). */ public static String getTableHeader() { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); pw.format("\n%-15s %-12s %-9s %-32s %-12s %-23s %s\n", "Time usec", "Walltime", "Direction", "Fate", "Protocol", "Type", "Result"); pw.format("%-15s %-12s %-9s %-32s %-12s %-23s %s\n", "---------", "--------", "---------", "----", "--------", "----", "------"); return sw.toString(); } protected abstract String directionToString(); protected abstract String fateToString(); private static String frameTypeToString(byte frameType) { switch (frameType) { case WifiLoggerHal.FRAME_TYPE_UNKNOWN: return "unknown"; case WifiLoggerHal.FRAME_TYPE_ETHERNET_II: return "data"; case WifiLoggerHal.FRAME_TYPE_80211_MGMT: return "802.11 management"; default: return Byte.toString(frameType); } } /** * Converts a driver timestamp to a wallclock time, based on the current * BOOTTIME to wallclock mapping. The driver timestamp is a 32-bit counter of * microseconds, with the same base as BOOTTIME. */ private static long convertDriverTimestampUSecToWallclockMSec(long driverTimestampUSec) { final long wallclockMillisNow = System.currentTimeMillis(); final long boottimeMillisNow = SystemClock.elapsedRealtime(); final long driverTimestampMillis = driverTimestampUSec / USEC_PER_MSEC; long boottimeTimestampMillis = boottimeMillisNow % MAX_DRIVER_TIMESTAMP_MSEC; if (boottimeTimestampMillis < driverTimestampMillis) { // The 32-bit microsecond count has wrapped between the time that the driver // recorded the packet, and the call to this function. Adjust the BOOTTIME // timestamp, to compensate. // // Note that overflow is not a concern here, since the result is less than // 2 * MAX_DRIVER_TIMESTAMP_MSEC. (Given the modulus operation above, // boottimeTimestampMillis must be less than MAX_DRIVER_TIMESTAMP_MSEC.) And, since // MAX_DRIVER_TIMESTAMP_MSEC is an int, 2 * MAX_DRIVER_TIMESTAMP_MSEC must fit // within a long. boottimeTimestampMillis += MAX_DRIVER_TIMESTAMP_MSEC; } final long millisSincePacketTimestamp = boottimeTimestampMillis - driverTimestampMillis; return wallclockMillisNow - millisSincePacketTimestamp; } } /** * Represents the fate information for one outbound packet. */ @Immutable public static final class TxFateReport extends FateReport { public TxFateReport(byte fate, long driverTimestampUSec, byte frameType, byte[] frameBytes) { super(fate, driverTimestampUSec, frameType, frameBytes); } @Override protected String directionToString() { return "TX"; } @Override protected String fateToString() { switch (mFate) { case WifiLoggerHal.TX_PKT_FATE_ACKED: return "acked"; case WifiLoggerHal.TX_PKT_FATE_SENT: return "sent"; case WifiLoggerHal.TX_PKT_FATE_FW_QUEUED: return "firmware queued"; case WifiLoggerHal.TX_PKT_FATE_FW_DROP_INVALID: return "firmware dropped (invalid frame)"; case WifiLoggerHal.TX_PKT_FATE_FW_DROP_NOBUFS: return "firmware dropped (no bufs)"; case WifiLoggerHal.TX_PKT_FATE_FW_DROP_OTHER: return "firmware dropped (other)"; case WifiLoggerHal.TX_PKT_FATE_DRV_QUEUED: return "driver queued"; case WifiLoggerHal.TX_PKT_FATE_DRV_DROP_INVALID: return "driver dropped (invalid frame)"; case WifiLoggerHal.TX_PKT_FATE_DRV_DROP_NOBUFS: return "driver dropped (no bufs)"; case WifiLoggerHal.TX_PKT_FATE_DRV_DROP_OTHER: return "driver dropped (other)"; default: return Byte.toString(mFate); } } } /** * Represents the fate information for one inbound packet. */ @Immutable public static final class RxFateReport extends FateReport { public RxFateReport(byte fate, long driverTimestampUSec, byte frameType, byte[] frameBytes) { super(fate, driverTimestampUSec, frameType, frameBytes); } @Override protected String directionToString() { return "RX"; } @Override protected String fateToString() { switch (mFate) { case WifiLoggerHal.RX_PKT_FATE_SUCCESS: return "success"; case WifiLoggerHal.RX_PKT_FATE_FW_QUEUED: return "firmware queued"; case WifiLoggerHal.RX_PKT_FATE_FW_DROP_FILTER: return "firmware dropped (filter)"; case WifiLoggerHal.RX_PKT_FATE_FW_DROP_INVALID: return "firmware dropped (invalid frame)"; case WifiLoggerHal.RX_PKT_FATE_FW_DROP_NOBUFS: return "firmware dropped (no bufs)"; case WifiLoggerHal.RX_PKT_FATE_FW_DROP_OTHER: return "firmware dropped (other)"; case WifiLoggerHal.RX_PKT_FATE_DRV_QUEUED: return "driver queued"; case WifiLoggerHal.RX_PKT_FATE_DRV_DROP_FILTER: return "driver dropped (filter)"; case WifiLoggerHal.RX_PKT_FATE_DRV_DROP_INVALID: return "driver dropped (invalid frame)"; case WifiLoggerHal.RX_PKT_FATE_DRV_DROP_NOBUFS: return "driver dropped (no bufs)"; case WifiLoggerHal.RX_PKT_FATE_DRV_DROP_OTHER: return "driver dropped (other)"; default: return Byte.toString(mFate); } } } /** * Ask the HAL to enable packet fate monitoring. Fails unless HAL is started. * * @param ifaceName Name of the interface. * @return true for success, false otherwise. */ public boolean startPktFateMonitoring(@NonNull String ifaceName) { return mWifiVendorHal.startPktFateMonitoring(ifaceName); } /** * Fetch the most recent TX packet fates from the HAL. Fails unless HAL is started. * * @param ifaceName Name of the interface. * @return TxFateReport list on success, empty list on failure. Never returns null. */ @NonNull public List getTxPktFates(@NonNull String ifaceName) { return mWifiVendorHal.getTxPktFates(ifaceName); } /** * Fetch the most recent RX packet fates from the HAL. Fails unless HAL is started. * @param ifaceName Name of the interface. * @return RxFateReport list on success, empty list on failure. Never returns null. */ @NonNull public List getRxPktFates(@NonNull String ifaceName) { return mWifiVendorHal.getRxPktFates(ifaceName); } /** * Get the tx packet counts for the interface. * * @param ifaceName Name of the interface. * @return tx packet counts */ public long getTxPackets(@NonNull String ifaceName) { return TrafficStats.getTxPackets(ifaceName); } /** * Get the rx packet counts for the interface. * * @param ifaceName Name of the interface * @return rx packet counts */ public long getRxPackets(@NonNull String ifaceName) { return TrafficStats.getRxPackets(ifaceName); } /** * Start sending the specified keep alive packets periodically. * * @param ifaceName Name of the interface. * @param slot Integer used to identify each request. * @param dstMac Destination MAC Address * @param packet Raw packet contents to send. * @param protocol The ethernet protocol type * @param period Period to use for sending these packets. * @return 0 for success, -1 for error */ public int startSendingOffloadedPacket(@NonNull String ifaceName, int slot, byte[] dstMac, byte[] packet, int protocol, int period) { byte[] srcMac = NativeUtil.macAddressToByteArray(getMacAddress(ifaceName)); return mWifiVendorHal.startSendingOffloadedPacket( ifaceName, slot, srcMac, dstMac, packet, protocol, period); } /** * Stop sending the specified keep alive packets. * * @param ifaceName Name of the interface. * @param slot id - same as startSendingOffloadedPacket call. * @return 0 for success, -1 for error */ public int stopSendingOffloadedPacket(@NonNull String ifaceName, int slot) { return mWifiVendorHal.stopSendingOffloadedPacket(ifaceName, slot); } public static interface WifiRssiEventHandler { void onRssiThresholdBreached(byte curRssi); } /** * Start RSSI monitoring on the currently connected access point. * * @param ifaceName Name of the interface. * @param maxRssi Maximum RSSI threshold. * @param minRssi Minimum RSSI threshold. * @param rssiEventHandler Called when RSSI goes above maxRssi or below minRssi * @return 0 for success, -1 for failure */ public int startRssiMonitoring( @NonNull String ifaceName, byte maxRssi, byte minRssi, WifiRssiEventHandler rssiEventHandler) { return mWifiVendorHal.startRssiMonitoring( ifaceName, maxRssi, minRssi, rssiEventHandler); } /** * Stop RSSI monitoring on the currently connected access point. * * @param ifaceName Name of the interface. * @return 0 for success, -1 for failure */ public int stopRssiMonitoring(@NonNull String ifaceName) { return mWifiVendorHal.stopRssiMonitoring(ifaceName); } /** * Fetch the host wakeup reasons stats from wlan driver. * * @return the |WlanWakeReasonAndCounts| object retrieved from the wlan driver. */ public WlanWakeReasonAndCounts getWlanWakeReasonCount() { return mWifiVendorHal.getWlanWakeReasonCount(); } /** * Enable/Disable Neighbour discovery offload functionality in the firmware. * * @param ifaceName Name of the interface. * @param enabled true to enable, false to disable. * @return true for success, false otherwise. */ public boolean configureNeighborDiscoveryOffload(@NonNull String ifaceName, boolean enabled) { return mWifiVendorHal.configureNeighborDiscoveryOffload(ifaceName, enabled); } // Firmware roaming control. /** * Class to retrieve firmware roaming capability parameters. */ public static class RoamingCapabilities { public int maxBlocklistSize; public int maxAllowlistSize; } /** * Query the firmware roaming capabilities. * @param ifaceName Name of the interface. * @return capabilities object on success, null otherwise. */ @Nullable public RoamingCapabilities getRoamingCapabilities(@NonNull String ifaceName) { return mWifiVendorHal.getRoamingCapabilities(ifaceName); } /** * Macros for controlling firmware roaming. */ public static final int DISABLE_FIRMWARE_ROAMING = 0; public static final int ENABLE_FIRMWARE_ROAMING = 1; @IntDef({ENABLE_FIRMWARE_ROAMING, DISABLE_FIRMWARE_ROAMING}) @Retention(RetentionPolicy.SOURCE) public @interface RoamingEnableState {} /** * Indicates success for enableFirmwareRoaming */ public static final int SET_FIRMWARE_ROAMING_SUCCESS = 0; /** * Indicates failure for enableFirmwareRoaming */ public static final int SET_FIRMWARE_ROAMING_FAILURE = 1; /** * Indicates temporary failure for enableFirmwareRoaming - try again later */ public static final int SET_FIRMWARE_ROAMING_BUSY = 2; @IntDef({SET_FIRMWARE_ROAMING_SUCCESS, SET_FIRMWARE_ROAMING_FAILURE, SET_FIRMWARE_ROAMING_BUSY}) @Retention(RetentionPolicy.SOURCE) public @interface RoamingEnableStatus {} /** * Enable/disable firmware roaming. * * @param ifaceName Name of the interface. * @return SET_FIRMWARE_ROAMING_SUCCESS, SET_FIRMWARE_ROAMING_FAILURE, * or SET_FIRMWARE_ROAMING_BUSY */ public @RoamingEnableStatus int enableFirmwareRoaming(@NonNull String ifaceName, @RoamingEnableState int state) { return mWifiVendorHal.enableFirmwareRoaming(ifaceName, state); } /** * Class for specifying the roaming configurations. */ public static class RoamingConfig { public ArrayList blocklistBssids; public ArrayList allowlistSsids; } /** * Set firmware roaming configurations. * @param ifaceName Name of the interface. */ public boolean configureRoaming(@NonNull String ifaceName, RoamingConfig config) { return mWifiVendorHal.configureRoaming(ifaceName, config); } /** * Reset firmware roaming configuration. * @param ifaceName Name of the interface. */ public boolean resetRoamingConfiguration(@NonNull String ifaceName) { // Pass in an empty RoamingConfig object which translates to zero size // blacklist and whitelist to reset the firmware roaming configuration. return mWifiVendorHal.configureRoaming(ifaceName, new RoamingConfig()); } /** * Select one of the pre-configured transmit power level scenarios or reset it back to normal. * Primarily used for meeting SAR requirements. * * @param sarInfo The collection of inputs used to select the SAR scenario. * @return true for success; false for failure or if the HAL version does not support this API. */ public boolean selectTxPowerScenario(SarInfo sarInfo) { return mWifiVendorHal.selectTxPowerScenario(sarInfo); } /** * Set MBO cellular data status * * @param ifaceName Name of the interface. * @param available cellular data status, * true means cellular data available, false otherwise. */ public void setMboCellularDataStatus(@NonNull String ifaceName, boolean available) { mSupplicantStaIfaceHal.setMboCellularDataStatus(ifaceName, available); } /** * Query of support of Wi-Fi standard * * @param ifaceName name of the interface to check support on * @param standard the wifi standard to check on * @return true if the wifi standard is supported on this interface, false otherwise. */ public boolean isWifiStandardSupported(@NonNull String ifaceName, @WifiAnnotations.WifiStandard int standard) { synchronized (mLock) { Iface iface = mIfaceMgr.getIface(ifaceName); if (iface == null || iface.phyCapabilities == null) { return false; } return iface.phyCapabilities.isWifiStandardSupported(standard); } } /** * Get the Wiphy capabilities of a device for a given interface * If the interface is not associated with one, * it will be read from the device through wificond * * @param ifaceName name of the interface * @return the device capabilities for this interface */ public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) { return getDeviceWiphyCapabilities(ifaceName, false); } /** * Get the Wiphy capabilities of a device for a given interface * If the interface is not associated with one, * it will be read from the device through wificond * * @param ifaceName name of the interface * @param isBridgedAp If the iface is bridge AP iface or not. * @return the device capabilities for this interface */ public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName, boolean isBridgedAp) { synchronized (mLock) { Iface iface = mIfaceMgr.getIface(ifaceName); if (iface == null) { Log.e(TAG, "Failed to get device capabilities, interface not found: " + ifaceName); return null; } if (iface.phyCapabilities == null) { if (isBridgedAp) { List instances = getBridgedApInstances(ifaceName); if (instances != null && instances.size() != 0) { iface.phyCapabilities = mWifiCondManager.getDeviceWiphyCapabilities( instances.get(0)); } } else { iface.phyCapabilities = mWifiCondManager.getDeviceWiphyCapabilities(ifaceName); } } if (iface.phyCapabilities != null && iface.phyCapabilities.isWifiStandardSupported(ScanResult.WIFI_STANDARD_11BE) != mWifiInjector.getSettingsConfigStore() .get(WifiSettingsConfigStore.WIFI_WIPHY_11BE_SUPPORTED)) { mWifiInjector.getSettingsConfigStore().put( WifiSettingsConfigStore.WIFI_WIPHY_11BE_SUPPORTED, iface.phyCapabilities.isWifiStandardSupported( ScanResult.WIFI_STANDARD_11BE)); } return iface.phyCapabilities; } } /** * Set the Wiphy capabilities of a device for a given interface * * @param ifaceName name of the interface * @param capabilities the wiphy capabilities to set for this interface */ public void setDeviceWiphyCapabilities(@NonNull String ifaceName, DeviceWiphyCapabilities capabilities) { synchronized (mLock) { Iface iface = mIfaceMgr.getIface(ifaceName); if (iface == null) { Log.e(TAG, "Failed to set device capabilities, interface not found: " + ifaceName); return; } iface.phyCapabilities = capabilities; } } /** * Notify scan mode state to driver to save power in scan-only mode. * * @param ifaceName Name of the interface. * @param enable whether is in scan-only mode * @return true for success */ public boolean setScanMode(String ifaceName, boolean enable) { return mWifiVendorHal.setScanMode(ifaceName, enable); } /** updates linked networks of the |networkId| in supplicant if it's the current network, * if the current configured network matches |networkId|. * * @param ifaceName Name of the interface. * @param networkId network id of the network to be updated from supplicant. * @param linkedNetworks Map of config profile key and config for linking. */ public boolean updateLinkedNetworks(@NonNull String ifaceName, int networkId, Map linkedNetworks) { return mSupplicantStaIfaceHal.updateLinkedNetworks(ifaceName, networkId, linkedNetworks); } /** * Start Subsystem Restart * @return true on success */ public boolean startSubsystemRestart() { return mWifiVendorHal.startSubsystemRestart(); } /** * Register the provided listener for country code event. * * @param listener listener for country code changed events. */ public void registerCountryCodeEventListener(WifiCountryCode.ChangeListener listener) { registerWificondListenerIfNecessary(); if (mCountryCodeChangeListener != null) { mCountryCodeChangeListener.setChangeListener(listener); } } /** * Gets the security params of the current network associated with this interface * * @param ifaceName Name of the interface * @return Security params of the current network associated with the interface */ public SecurityParams getCurrentNetworkSecurityParams(@NonNull String ifaceName) { return mSupplicantStaIfaceHal.getCurrentNetworkSecurityParams(ifaceName); } /** * Check if the network-centric QoS policy feature was successfully enabled. */ public boolean isQosPolicyFeatureEnabled() { return mQosPolicyFeatureEnabled; } /** * Sends a QoS policy response. * * @param ifaceName Name of the interface. * @param qosPolicyRequestId Dialog token to identify the request. * @param morePolicies Flag to indicate more QoS policies can be accommodated. * @param qosPolicyStatusList List of framework QosPolicyStatus objects. * @return true if response is sent successfully, false otherwise. */ public boolean sendQosPolicyResponse(String ifaceName, int qosPolicyRequestId, boolean morePolicies, @NonNull List qosPolicyStatusList) { if (!mQosPolicyFeatureEnabled) { Log.e(TAG, "Unable to send QoS policy response, feature is not enabled"); return false; } return mSupplicantStaIfaceHal.sendQosPolicyResponse(ifaceName, qosPolicyRequestId, morePolicies, qosPolicyStatusList); } /** * Indicates the removal of all active QoS policies configured by the AP. * * @param ifaceName Name of the interface. */ public boolean removeAllQosPolicies(String ifaceName) { if (!mQosPolicyFeatureEnabled) { Log.e(TAG, "Unable to remove all QoS policies, feature is not enabled"); return false; } return mSupplicantStaIfaceHal.removeAllQosPolicies(ifaceName); } /** * Send a set of QoS SCS policy add requests to the AP. * * Immediate response will indicate which policies were sent to the AP, and which were * rejected immediately by the supplicant. If any requests were sent to the AP, the AP's * response will arrive later in the onQosPolicyResponseForScs callback. * * @param ifaceName Name of the interface. * @param policies List of policies that the caller is requesting to add. * @return List of responses for each policy in the request, or null if an error occurred. * Status code will be one of * {@link SupplicantStaIfaceHal.QosPolicyScsRequestStatusCode}. */ List addQosPolicyRequestForScs( @NonNull String ifaceName, @NonNull List policies) { return mSupplicantStaIfaceHal.addQosPolicyRequestForScs(ifaceName, policies); } /** * Request the removal of specific QoS policies for SCS. * * Immediate response will indicate which policies were sent to the AP, and which were * rejected immediately by the supplicant. If any requests were sent to the AP, the AP's * response will arrive later in the onQosPolicyResponseForScs callback. * * @param ifaceName Name of the interface. * @param policyIds List of policy IDs for policies that should be removed. * @return List of responses for each policy in the request, or null if an error occurred. * Status code will be one of * {@link SupplicantStaIfaceHal.QosPolicyScsRequestStatusCode}. */ List removeQosPolicyForScs( @NonNull String ifaceName, @NonNull List policyIds) { return mSupplicantStaIfaceHal.removeQosPolicyForScs(ifaceName, policyIds); } /** * Register a callback to receive notifications for QoS SCS transactions. * Callback should only be registered once. * * @param callback {@link SupplicantStaIfaceHal.QosScsResponseCallback} to register. */ public void registerQosScsResponseCallback( @NonNull SupplicantStaIfaceHal.QosScsResponseCallback callback) { mSupplicantStaIfaceHal.registerQosScsResponseCallback(callback); } /** * Generate DPP credential for network access * * @param ifaceName Name of the interface. * @param ssid ssid of the network * @param privEcKey Private EC Key for DPP Configurator * Returns true when operation is successful. On error, false is returned. */ public boolean generateSelfDppConfiguration(@NonNull String ifaceName, @NonNull String ssid, byte[] privEcKey) { return mSupplicantStaIfaceHal.generateSelfDppConfiguration(ifaceName, ssid, privEcKey); } /** * This set anonymous identity to supplicant. * * @param ifaceName Name of the interface. * @param anonymousIdentity the anonymouns identity. * @param updateToNativeService write the data to the native service. * @return true if succeeds, false otherwise. */ public boolean setEapAnonymousIdentity(@NonNull String ifaceName, String anonymousIdentity, boolean updateToNativeService) { if (null == anonymousIdentity) { Log.e(TAG, "Cannot set null anonymous identity."); return false; } return mSupplicantStaIfaceHal.setEapAnonymousIdentity(ifaceName, anonymousIdentity, updateToNativeService); } /** * Notify wificond daemon of country code have changed. */ public void countryCodeChanged(String countryCode) { if (SdkLevel.isAtLeastT()) { try { mWifiCondManager.notifyCountryCodeChanged(countryCode); } catch (RuntimeException re) { Log.e(TAG, "Fail to notify wificond country code changed to " + countryCode + "because exception happened:" + re); } } } /** * Return the maximum number of concurrent TDLS sessions supported by the device. * @return -1 if the information is not available on the device */ public int getMaxSupportedConcurrentTdlsSessions(@NonNull String ifaceName) { return mWifiVendorHal.getMaxSupportedConcurrentTdlsSessions(ifaceName); } /** * Save the complete list of features retrieved from WiFi HAL and Supplicant HAL in * config store. */ private void saveCompleteFeatureSetInConfigStoreIfNecessary(long featureSet) { long cachedFeatureSet = getCompleteFeatureSetFromConfigStore(); if (cachedFeatureSet != featureSet) { mCachedFeatureSet = featureSet; mWifiInjector.getSettingsConfigStore() .put(WIFI_NATIVE_SUPPORTED_FEATURES, mCachedFeatureSet); Log.i(TAG, "Supported features is updated in config store: " + mCachedFeatureSet); } } /** * Get the feature set from cache/config store */ private long getCompleteFeatureSetFromConfigStore() { if (mCachedFeatureSet == 0) { mCachedFeatureSet = mWifiInjector.getSettingsConfigStore() .get(WIFI_NATIVE_SUPPORTED_FEATURES); } return mCachedFeatureSet; } /** * Returns whether or not the hostapd HAL supports reporting single instance died event. */ public boolean isSoftApInstanceDiedHandlerSupported() { return mHostapdHal.isSoftApInstanceDiedHandlerSupported(); } /** Checks if there are any STA (for connectivity) iface active. */ @VisibleForTesting boolean hasAnyStaIfaceForConnectivity() { return mIfaceMgr.hasAnyStaIfaceForConnectivity(); } /** Checks if there are any STA (for scan) iface active. */ @VisibleForTesting boolean hasAnyStaIfaceForScan() { return mIfaceMgr.hasAnyStaIfaceForScan(); } /** Checks if there are any AP iface active. */ @VisibleForTesting boolean hasAnyApIface() { return mIfaceMgr.hasAnyApIface(); } /** Checks if there are any iface active. */ @VisibleForTesting boolean hasAnyIface() { return mIfaceMgr.hasAnyIface(); } /** Checks if there are any P2P iface active. */ @VisibleForTesting boolean hasAnyP2pIface() { return mIfaceMgr.hasAnyP2pIface(); } /** * Sets or clean mock wifi service * * @param serviceName the service name of mock wifi service. When service name is empty, the * framework will clean mock wifi service. */ public void setMockWifiService(String serviceName) { Log.d(TAG, "set MockWifiModemService to " + serviceName); if (TextUtils.isEmpty(serviceName)) { mMockWifiModem.unbindMockModemService(); mMockWifiModem = null; mWifiInjector.setMockWifiServiceUtil(null); return; } mMockWifiModem = new MockWifiServiceUtil(mContext, serviceName, mWifiMonitor); mWifiInjector.setMockWifiServiceUtil(mMockWifiModem); if (mMockWifiModem == null) { Log.e(TAG, "MockWifiServiceUtil creation failed."); return; } // mock wifi modem service is set, try to bind all supported mock HAL services mMockWifiModem.bindAllMockModemService(); for (int service = MockWifiServiceUtil.MIN_SERVICE_IDX; service < MockWifiServiceUtil.NUM_SERVICES; service++) { int retryCount = 0; IBinder binder; do { binder = mMockWifiModem.getServiceBinder(service); retryCount++; if (binder == null) { Log.d(TAG, "Retry(" + retryCount + ") for " + mMockWifiModem.getModuleName(service)); try { Thread.sleep(MockWifiServiceUtil.BINDER_RETRY_MILLIS); } catch (InterruptedException e) { } } } while ((binder == null) && (retryCount < MockWifiServiceUtil.BINDER_MAX_RETRY)); if (binder == null) { Log.e(TAG, "Mock " + mMockWifiModem.getModuleName(service) + " bind fail"); } } } /** * Returns mock wifi service name. */ public String getMockWifiServiceName() { String serviceName = mMockWifiModem != null ? mMockWifiModem.getServiceName() : null; Log.d(TAG, "getMockWifiServiceName - service name is " + serviceName); return serviceName; } /** * Sets mocked methods which like to be called. * * @param methods the methods string with formats HAL name - method name, ... */ public boolean setMockWifiMethods(String methods) { if (mMockWifiModem == null || methods == null) { return false; } return mMockWifiModem.setMockedMethods(methods); } /** * Set maximum acceptable DTIM multiplier to hardware driver. Any multiplier larger than the * maximum value must not be accepted, it will cause packet loss higher than what the system * can accept, which will cause unexpected behavior for apps, and may interrupt the network * connection. * * @param ifaceName Name of the interface. * @param multiplier integer maximum DTIM multiplier value to set. * @return true for success */ public boolean setDtimMultiplier(String ifaceName, int multiplier) { return mWifiVendorHal.setDtimMultiplier(ifaceName, multiplier); } /** * Set Multi-Link Operation mode. * * @param mode Multi-Link Operation mode {@link android.net.wifi.WifiManager.MloMode}. * @return {@link WifiStatusCode#SUCCESS} if success, otherwise error code. */ public @WifiStatusCode int setMloMode(@WifiManager.MloMode int mode) { @WifiStatusCode int errorCode = mWifiVendorHal.setMloMode(mode); // If set is success, cache it. if (errorCode == WifiStatusCode.SUCCESS) mCachedMloMode = mode; return errorCode; } /** * Get Multi-Link Operation mode. * * @return Current Multi-Link Operation mode {@link android.net.wifi.WifiManager.MloMode}. */ public @WifiManager.MloMode int getMloMode() { return mCachedMloMode; } /** * Get the maximum number of links supported by the chip for MLO association. * * e.g. if the chip supports eMLSR (Enhanced Multi-Link Single Radio) and STR (Simultaneous * Transmit and Receive) with following capabilities, * - Maximum MLO association link count = 3 * - Maximum MLO STR link count = 2 See {@link WifiNative#getMaxMloStrLinkCount(String)} * One of the possible configuration is - STR (2.4 , eMLSR(5, 6)), provided the radio * combination of the chip supports it. * * Note: This is an input to MLO aware network scoring logic to predict maximum multi-link * throughput. * * @param ifaceName Name of the interface. * @return maximum number of association links or -1 if error or not available. */ public int getMaxMloAssociationLinkCount(@NonNull String ifaceName) { return mWifiVendorHal.getMaxMloAssociationLinkCount(ifaceName); } /** * Get the maximum number of STR links used in Multi-Link Operation. The maximum number of STR * links used for MLO can be different from the number of radios supported by the chip. * * e.g. if the chip supports eMLSR (Enhanced Multi-Link Single Radio) and STR (Simultaneous * Transmit and Receive) with following capabilities, * - Maximum MLO association link count = 3 * See {@link WifiNative#getMaxMloAssociationLinkCount(String)} * - Maximum MLO STR link count = 2 * One of the possible configuration is - STR (2.4, eMLSR(5, 6)), provided the radio * combination of the chip supports it. * * Note: This is an input to MLO aware network scoring logic to predict maximum multi-link * throughput. * * @param ifaceName Name of the interface. * @return maximum number of MLO STR links or -1 if error or not available. */ public int getMaxMloStrLinkCount(@NonNull String ifaceName) { return mWifiVendorHal.getMaxMloStrLinkCount(ifaceName); } /** * Check the given band combination is supported simultaneously by the Wi-Fi chip. * * Note: This method is for checking simultaneous band operations and not for multichannel * concurrent operation (MCC). * * @param ifaceName Name of the interface. * @param bands A list of bands in the combination. See {@link WifiScanner.WifiBand} * for the band enums. List of bands can be in any order. * @return true if the provided band combination is supported by the chip, otherwise false. */ public boolean isBandCombinationSupported(@NonNull String ifaceName, List bands) { return mWifiVendorHal.isBandCombinationSupported(ifaceName, bands); } /** * Get the set of band combinations supported simultaneously by the Wi-Fi Chip. * * Note: This method returns simultaneous band operation combination and not multichannel * concurrent operation (MCC) combination. * * @param ifaceName Name of the interface. * @return An unmodifiable set of supported band combinations. */ public Set> getSupportedBandCombinations(@NonNull String ifaceName) { return mWifiVendorHal.getSupportedBandCombinations(ifaceName); } /** * Sends the AFC allowed channels and frequencies to the driver. * * @param afcChannelAllowance the allowed frequencies and channels received from * querying the AFC server. * @return whether the channel allowance was set successfully. */ public boolean setAfcChannelAllowance(WifiChip.AfcChannelAllowance afcChannelAllowance) { return mWifiVendorHal.setAfcChannelAllowance(afcChannelAllowance); } /** * Enable Mirrored Stream Classification Service (MSCS) and configure using * the provided configuration values. * * @param mscsParams {@link MscsParams} object containing the configuration parameters. * @param ifaceName Name of the interface. */ public void enableMscs(@NonNull MscsParams mscsParams, String ifaceName) { mSupplicantStaIfaceHal.enableMscs(mscsParams, ifaceName); } /** * Resend the previously configured MSCS parameters on this interface, if any exist. * * @param ifaceName Name of the interface. */ public void resendMscs(String ifaceName) { mSupplicantStaIfaceHal.resendMscs(ifaceName); } /** * Disable Mirrored Stream Classification Service (MSCS). * * @param ifaceName Name of the interface. */ public void disableMscs(String ifaceName) { mSupplicantStaIfaceHal.disableMscs(ifaceName); } /** * Set the roaming mode value. * * @param ifaceName Name of the interface. * @param roamingMode {@link android.net.wifi.WifiManager.RoamingMode}. * @return {@link WifiStatusCode#SUCCESS} if success, otherwise error code. */ public @WifiStatusCode int setRoamingMode(@NonNull String ifaceName, @RoamingMode int roamingMode) { return mWifiVendorHal.setRoamingMode(ifaceName, roamingMode); } /* * TWT callback events */ public interface WifiTwtEvents { /** * Called when a TWT operation fails * * @param cmdId Unique command id. * @param twtErrorCode Error code */ void onTwtFailure(int cmdId, @TwtSessionCallback.TwtErrorCode int twtErrorCode); /** * Called when {@link #setupTwtSession(int, String, TwtRequest)} succeeds. * * @param cmdId Unique command id used in {@link #setupTwtSession(int, String, TwtRequest)} * @param wakeDurationUs TWT wake duration for the session in microseconds * @param wakeIntervalUs TWT wake interval for the session in microseconds * @param linkId Multi link operation link id * @param sessionId TWT session id */ void onTwtSessionCreate(int cmdId, int wakeDurationUs, long wakeIntervalUs, int linkId, int sessionId); /** * Called when TWT session is torn down by {@link #tearDownTwtSession(int, String, int)}. * Can also be called unsolicitedly by the vendor software with proper reason code. * * @param cmdId Unique command id used in {@link #tearDownTwtSession(int, String, int)} * @param twtSessionId TWT session Id * @param twtReasonCode Reason code for teardown */ void onTwtSessionTeardown(int cmdId, int twtSessionId, @TwtSessionCallback.TwtReasonCode int twtReasonCode); /** * Called as a response to {@link #getStatsTwtSession(int, String, int)} * * @param cmdId Unique command id used in {@link #getStatsTwtSession(int, String, int)} * @param twtSessionId TWT session Id * @param twtStats TWT stats object */ void onTwtSessionStats(int cmdId, int twtSessionId, Bundle twtStats); } /** * Sets up a TWT session for the interface * * @param commandId A unique command id to identify this command * @param interfaceName Interface name * @param twtRequest TWT request parameters * @return true if successful, otherwise false */ public boolean setupTwtSession(int commandId, String interfaceName, TwtRequest twtRequest) { return mWifiVendorHal.setupTwtSession(commandId, interfaceName, twtRequest); } /** * Registers TWT callbacks * * @param wifiTwtCallback TWT callbacks */ public void registerTwtCallbacks(WifiTwtEvents wifiTwtCallback) { mWifiVendorHal.registerTwtCallbacks(wifiTwtCallback); } /** * Teardown the TWT session * * @param commandId A unique command id to identify this command * @param interfaceName Interface name * @param sessionId TWT session id * @return true if successful, otherwise false */ public boolean tearDownTwtSession(int commandId, String interfaceName, int sessionId) { return mWifiVendorHal.tearDownTwtSession(commandId, interfaceName, sessionId); } /** * Gets stats of the TWT session * * @param commandId A unique command id to identify this command * @param interfaceName Interface name * @param sessionId TWT session id * @return true if successful, otherwise false */ public boolean getStatsTwtSession(int commandId, String interfaceName, int sessionId) { return mWifiVendorHal.getStatsTwtSession(commandId, interfaceName, sessionId); } /** * Sets the wifi VoIP mode. * * @param mode Voip mode as defined by the enum |WifiVoipMode| * @return true if successful, false otherwise. */ public boolean setVoipMode(@WifiChip.WifiVoipMode int mode) { return mWifiVendorHal.setVoipMode(mode); } }