diff options
Diffstat (limited to 'wifi/java/src')
13 files changed, 3946 insertions, 0 deletions
diff --git a/wifi/java/src/android/net/wifi/SoftApConfToXmlMigrationUtil.java b/wifi/java/src/android/net/wifi/SoftApConfToXmlMigrationUtil.java new file mode 100755 index 000000000000..c5472ce34478 --- /dev/null +++ b/wifi/java/src/android/net/wifi/SoftApConfToXmlMigrationUtil.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +import static android.os.Environment.getDataMiscDirectory; + +import android.annotation.Nullable; +import android.net.MacAddress; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +/** + * Utility class to convert the legacy softap.conf file format to the new XML format. + * Note: + * <li>This should be modified by the OEM if they want to migrate configuration for existing + * devices for new softap features supported by AOSP in Android 11. + * For ex: client allowlist/blocklist feature was already supported by some OEM's before Android 10 + * while AOSP only supported it in Android 11. </li> + * <li>Most of this class was copied over from WifiApConfigStore class in Android 10 and + * SoftApStoreData class in Android 11</li> + * @hide + */ +public final class SoftApConfToXmlMigrationUtil { + private static final String TAG = "SoftApConfToXmlMigrationUtil"; + + /** + * Directory to read the wifi config store files from under. + */ + private static final String LEGACY_WIFI_STORE_DIRECTORY_NAME = "wifi"; + /** + * The legacy Softap config file which contained key/value pairs. + */ + private static final String LEGACY_AP_CONFIG_FILE = "softap.conf"; + + /** + * Pre-apex wifi shared folder. + */ + private static File getLegacyWifiSharedDirectory() { + return new File(getDataMiscDirectory(), LEGACY_WIFI_STORE_DIRECTORY_NAME); + } + + /* @hide constants copied from WifiConfiguration */ + /** + * 2GHz band. + */ + private static final int WIFICONFIG_AP_BAND_2GHZ = 0; + /** + * 5GHz band. + */ + private static final int WIFICONFIG_AP_BAND_5GHZ = 1; + /** + * Device is allowed to choose the optimal band (2Ghz or 5Ghz) based on device capability, + * operating country code and current radio conditions. + */ + private static final int WIFICONFIG_AP_BAND_ANY = -1; + /** + * Convert band from WifiConfiguration into SoftApConfiguration + * + * @param wifiConfigBand band encoded as WIFICONFIG_AP_BAND_xxxx + * @return band as encoded as SoftApConfiguration.BAND_xxx + */ + @VisibleForTesting + public static int convertWifiConfigBandToSoftApConfigBand(int wifiConfigBand) { + switch (wifiConfigBand) { + case WIFICONFIG_AP_BAND_2GHZ: + return SoftApConfiguration.BAND_2GHZ; + case WIFICONFIG_AP_BAND_5GHZ: + return SoftApConfiguration.BAND_5GHZ; + case WIFICONFIG_AP_BAND_ANY: + return SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ; + default: + return SoftApConfiguration.BAND_2GHZ; + } + } + + /** + * Load AP configuration from legacy persistent storage. + * Note: This is deprecated and only used for migrating data once on reboot. + */ + private static SoftApConfiguration loadFromLegacyFile(InputStream fis) { + SoftApConfiguration config = null; + DataInputStream in = null; + try { + SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(); + in = new DataInputStream(new BufferedInputStream(fis)); + + int version = in.readInt(); + if (version < 1 || version > 3) { + Log.e(TAG, "Bad version on hotspot configuration file"); + return null; + } + configBuilder.setSsid(in.readUTF()); + + if (version >= 2) { + int band = in.readInt(); + int channel = in.readInt(); + if (channel == 0) { + configBuilder.setBand( + convertWifiConfigBandToSoftApConfigBand(band)); + } else { + configBuilder.setChannel(channel, + convertWifiConfigBandToSoftApConfigBand(band)); + } + } + if (version >= 3) { + configBuilder.setHiddenSsid(in.readBoolean()); + } + int authType = in.readInt(); + if (authType == WifiConfiguration.KeyMgmt.WPA2_PSK) { + configBuilder.setPassphrase(in.readUTF(), + SoftApConfiguration.SECURITY_TYPE_WPA2_PSK); + } + config = configBuilder.build(); + } catch (IOException e) { + Log.e(TAG, "Error reading hotspot configuration ", e); + config = null; + } catch (IllegalArgumentException ie) { + Log.e(TAG, "Invalid hotspot configuration ", ie); + config = null; + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + Log.e(TAG, "Error closing hotspot configuration during read", e); + } + } + } + // NOTE: OEM's should add their customized parsing code here. + return config; + } + + // This is the version that Android 11 released with. + private static final int CONFIG_STORE_DATA_VERSION = 3; + + private static final String XML_TAG_DOCUMENT_HEADER = "WifiConfigStoreData"; + private static final String XML_TAG_VERSION = "Version"; + private static final String XML_TAG_SECTION_HEADER_SOFTAP = "SoftAp"; + private static final String XML_TAG_SSID = "SSID"; + private static final String XML_TAG_BSSID = "Bssid"; + private static final String XML_TAG_CHANNEL = "Channel"; + private static final String XML_TAG_HIDDEN_SSID = "HiddenSSID"; + private static final String XML_TAG_SECURITY_TYPE = "SecurityType"; + private static final String XML_TAG_AP_BAND = "ApBand"; + private static final String XML_TAG_PASSPHRASE = "Passphrase"; + private static final String XML_TAG_MAX_NUMBER_OF_CLIENTS = "MaxNumberOfClients"; + private static final String XML_TAG_AUTO_SHUTDOWN_ENABLED = "AutoShutdownEnabled"; + private static final String XML_TAG_SHUTDOWN_TIMEOUT_MILLIS = "ShutdownTimeoutMillis"; + private static final String XML_TAG_CLIENT_CONTROL_BY_USER = "ClientControlByUser"; + private static final String XML_TAG_BLOCKED_CLIENT_LIST = "BlockedClientList"; + private static final String XML_TAG_ALLOWED_CLIENT_LIST = "AllowedClientList"; + public static final String XML_TAG_CLIENT_MACADDRESS = "ClientMacAddress"; + + private static byte[] convertConfToXml(SoftApConfiguration softApConf) { + try { + final XmlSerializer out = new FastXmlSerializer(); + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + out.setOutput(outputStream, StandardCharsets.UTF_8.name()); + + // Header for the XML file. + out.startDocument(null, true); + out.startTag(null, XML_TAG_DOCUMENT_HEADER); + XmlUtils.writeValueXml(CONFIG_STORE_DATA_VERSION, XML_TAG_VERSION, out); + out.startTag(null, XML_TAG_SECTION_HEADER_SOFTAP); + + // SoftAp conf + XmlUtils.writeValueXml(softApConf.getSsid(), XML_TAG_SSID, out); + if (softApConf.getBssid() != null) { + XmlUtils.writeValueXml(softApConf.getBssid().toString(), XML_TAG_BSSID, out); + } + XmlUtils.writeValueXml(softApConf.getBand(), XML_TAG_AP_BAND, out); + XmlUtils.writeValueXml(softApConf.getChannel(), XML_TAG_CHANNEL, out); + XmlUtils.writeValueXml(softApConf.isHiddenSsid(), XML_TAG_HIDDEN_SSID, out); + XmlUtils.writeValueXml(softApConf.getSecurityType(), XML_TAG_SECURITY_TYPE, out); + if (softApConf.getSecurityType() != SoftApConfiguration.SECURITY_TYPE_OPEN) { + XmlUtils.writeValueXml(softApConf.getPassphrase(), XML_TAG_PASSPHRASE, out); + } + XmlUtils.writeValueXml(softApConf.getMaxNumberOfClients(), + XML_TAG_MAX_NUMBER_OF_CLIENTS, out); + XmlUtils.writeValueXml(softApConf.isClientControlByUserEnabled(), + XML_TAG_CLIENT_CONTROL_BY_USER, out); + XmlUtils.writeValueXml(softApConf.isAutoShutdownEnabled(), + XML_TAG_AUTO_SHUTDOWN_ENABLED, out); + XmlUtils.writeValueXml(softApConf.getShutdownTimeoutMillis(), + XML_TAG_SHUTDOWN_TIMEOUT_MILLIS, out); + out.startTag(null, XML_TAG_BLOCKED_CLIENT_LIST); + for (MacAddress mac: softApConf.getBlockedClientList()) { + XmlUtils.writeValueXml(mac.toString(), XML_TAG_CLIENT_MACADDRESS, out); + } + out.endTag(null, XML_TAG_BLOCKED_CLIENT_LIST); + out.startTag(null, XML_TAG_ALLOWED_CLIENT_LIST); + for (MacAddress mac: softApConf.getAllowedClientList()) { + XmlUtils.writeValueXml(mac.toString(), XML_TAG_CLIENT_MACADDRESS, out); + } + out.endTag(null, XML_TAG_ALLOWED_CLIENT_LIST); + + // Footer for the XML file. + out.endTag(null, XML_TAG_SECTION_HEADER_SOFTAP); + out.endTag(null, XML_TAG_DOCUMENT_HEADER); + out.endDocument(); + + return outputStream.toByteArray(); + } catch (IOException | XmlPullParserException e) { + Log.e(TAG, "Failed to convert softap conf to XML", e); + return null; + } + } + + private SoftApConfToXmlMigrationUtil() { } + + /** + * Read the legacy /data/misc/wifi/softap.conf file format and convert to the new XML + * format understood by WifiConfigStore. + * Note: Used for unit testing. + */ + @VisibleForTesting + @Nullable + public static InputStream convert(InputStream fis) { + SoftApConfiguration softApConf = loadFromLegacyFile(fis); + if (softApConf == null) return null; + + byte[] xmlBytes = convertConfToXml(softApConf); + if (xmlBytes == null) return null; + + return new ByteArrayInputStream(xmlBytes); + } + + /** + * Read the legacy /data/misc/wifi/softap.conf file format and convert to the new XML + * format understood by WifiConfigStore. + */ + @Nullable + public static InputStream convert() { + File file = new File(getLegacyWifiSharedDirectory(), LEGACY_AP_CONFIG_FILE); + FileInputStream fis = null; + try { + fis = new FileInputStream(file); + } catch (FileNotFoundException e) { + return null; + } + if (fis == null) return null; + return convert(fis); + } + + /** + * Remove the legacy /data/misc/wifi/softap.conf file. + */ + @Nullable + public static void remove() { + File file = new File(getLegacyWifiSharedDirectory(), LEGACY_AP_CONFIG_FILE); + file.delete(); + } +} diff --git a/wifi/java/src/android/net/wifi/WifiMigration.java b/wifi/java/src/android/net/wifi/WifiMigration.java new file mode 100755 index 000000000000..4fabc0b0babc --- /dev/null +++ b/wifi/java/src/android/net/wifi/WifiMigration.java @@ -0,0 +1,558 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +import static android.os.Environment.getDataMiscCeDirectory; +import static android.os.Environment.getDataMiscDirectory; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.AtomicFile; +import android.util.SparseArray; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Class used to provide one time hooks for existing OEM devices to migrate their config store + * data and other settings to the wifi apex. + * @hide + */ +@SystemApi +public final class WifiMigration { + /** + * Directory to read the wifi config store files from under. + */ + private static final String LEGACY_WIFI_STORE_DIRECTORY_NAME = "wifi"; + /** + * Config store file for general shared store file. + * AOSP Path on Android 10: /data/misc/wifi/WifiConfigStore.xml + */ + public static final int STORE_FILE_SHARED_GENERAL = 0; + /** + * Config store file for softap shared store file. + * AOSP Path on Android 10: /data/misc/wifi/softap.conf + */ + public static final int STORE_FILE_SHARED_SOFTAP = 1; + /** + * Config store file for general user store file. + * AOSP Path on Android 10: /data/misc_ce/<userId>/wifi/WifiConfigStore.xml + */ + public static final int STORE_FILE_USER_GENERAL = 2; + /** + * Config store file for network suggestions user store file. + * AOSP Path on Android 10: /data/misc_ce/<userId>/wifi/WifiConfigStoreNetworkSuggestions.xml + */ + public static final int STORE_FILE_USER_NETWORK_SUGGESTIONS = 3; + + /** @hide */ + @IntDef(prefix = { "STORE_FILE_SHARED_" }, value = { + STORE_FILE_SHARED_GENERAL, + STORE_FILE_SHARED_SOFTAP, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SharedStoreFileId { } + + /** @hide */ + @IntDef(prefix = { "STORE_FILE_USER_" }, value = { + STORE_FILE_USER_GENERAL, + STORE_FILE_USER_NETWORK_SUGGESTIONS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface UserStoreFileId { } + + /** + * Mapping of Store file Id to Store file names. + * + * NOTE: This is the default path for the files on AOSP devices. If the OEM has modified + * the path or renamed the files, please edit this appropriately. + */ + private static final SparseArray<String> STORE_ID_TO_FILE_NAME = + new SparseArray<String>() {{ + put(STORE_FILE_SHARED_GENERAL, "WifiConfigStore.xml"); + put(STORE_FILE_SHARED_SOFTAP, "WifiConfigStoreSoftAp.xml"); + put(STORE_FILE_USER_GENERAL, "WifiConfigStore.xml"); + put(STORE_FILE_USER_NETWORK_SUGGESTIONS, "WifiConfigStoreNetworkSuggestions.xml"); + }}; + + /** + * Pre-apex wifi shared folder. + */ + private static File getLegacyWifiSharedDirectory() { + return new File(getDataMiscDirectory(), LEGACY_WIFI_STORE_DIRECTORY_NAME); + } + + /** + * Pre-apex wifi user folder. + */ + private static File getLegacyWifiUserDirectory(int userId) { + return new File(getDataMiscCeDirectory(userId), LEGACY_WIFI_STORE_DIRECTORY_NAME); + } + + /** + * Legacy files were stored as AtomicFile. So, always use AtomicFile to operate on it to ensure + * data integrity. + */ + private static AtomicFile getSharedAtomicFile(@SharedStoreFileId int storeFileId) { + return new AtomicFile(new File( + getLegacyWifiSharedDirectory(), + STORE_ID_TO_FILE_NAME.get(storeFileId))); + } + + /** + * Legacy files were stored as AtomicFile. So, always use AtomicFile to operate on it to ensure + * data integrity. + */ + private static AtomicFile getUserAtomicFile(@UserStoreFileId int storeFileId, int userId) { + return new AtomicFile(new File( + getLegacyWifiUserDirectory(userId), + STORE_ID_TO_FILE_NAME.get(storeFileId))); + } + + private WifiMigration() { } + + /** + * Load data from legacy shared wifi config store file. + * <p> + * Expected AOSP format is available in the sample files under {@code + * frameworks/base/wifi/non-updatable/migration_samples/}. + * </p> + * <p> + * Note: + * <li>OEMs need to change the implementation of + * {@link #convertAndRetrieveSharedConfigStoreFile(int)} only if their existing config store + * format or file locations differs from the vanilla AOSP implementation.</li> + * <li>The wifi apex will invoke + * {@link #convertAndRetrieveSharedConfigStoreFile(int)} + * method on every bootup, it is the responsibility of the OEM implementation to ensure that + * they perform the necessary in place conversion of their config store file to conform to the + * AOSP format. The OEM should ensure that the method should only return the + * {@link InputStream} stream for the data to be migrated only on the first bootup.</li> + * <li>Once the migration is done, the apex will invoke + * {@link #removeSharedConfigStoreFile(int)} to delete the store file.</li> + * <li>The only relevant invocation of {@link #convertAndRetrieveSharedConfigStoreFile(int)} + * occurs when a previously released device upgrades to the wifi apex from an OEM + * implementation of the wifi stack. + * <li>Ensure that the legacy file paths are accessible to the wifi module (sepolicy rules, file + * permissions, etc). Since the wifi service continues to run inside system_server process, this + * method will be called from the same context (so ideally the file should still be accessible). + * </li> + * + * @param storeFileId Identifier for the config store file. One of + * {@link #STORE_FILE_SHARED_GENERAL} or {@link #STORE_FILE_SHARED_GENERAL} + * @return Instance of {@link InputStream} for migrating data, null if no migration is + * necessary. + * @throws IllegalArgumentException on invalid storeFileId. + */ + @Nullable + public static InputStream convertAndRetrieveSharedConfigStoreFile( + @SharedStoreFileId int storeFileId) { + if (storeFileId != STORE_FILE_SHARED_GENERAL && storeFileId != STORE_FILE_SHARED_SOFTAP) { + throw new IllegalArgumentException("Invalid shared store file id"); + } + try { + // OEMs should do conversions necessary here before returning the stream. + return getSharedAtomicFile(storeFileId).openRead(); + } catch (FileNotFoundException e) { + // Special handling for softap.conf. + // Note: OEM devices upgrading from Q -> R will only have the softap.conf file. + // Test devices running previous R builds however may have already migrated to the + // XML format. So, check for that above before falling back to check for legacy file. + if (storeFileId == STORE_FILE_SHARED_SOFTAP) { + return SoftApConfToXmlMigrationUtil.convert(); + } + return null; + } + } + + /** + * Remove the legacy shared wifi config store file. + * + * @param storeFileId Identifier for the config store file. One of + * {@link #STORE_FILE_SHARED_GENERAL} or {@link #STORE_FILE_SHARED_GENERAL} + * @throws IllegalArgumentException on invalid storeFileId. + */ + public static void removeSharedConfigStoreFile(@SharedStoreFileId int storeFileId) { + if (storeFileId != STORE_FILE_SHARED_GENERAL && storeFileId != STORE_FILE_SHARED_SOFTAP) { + throw new IllegalArgumentException("Invalid shared store file id"); + } + AtomicFile file = getSharedAtomicFile(storeFileId); + if (file.exists()) { + file.delete(); + return; + } + // Special handling for softap.conf. + // Note: OEM devices upgrading from Q -> R will only have the softap.conf file. + // Test devices running previous R builds however may have already migrated to the + // XML format. So, check for that above before falling back to check for legacy file. + if (storeFileId == STORE_FILE_SHARED_SOFTAP) { + SoftApConfToXmlMigrationUtil.remove(); + } + } + + /** + * Load data from legacy user wifi config store file. + * <p> + * Expected AOSP format is available in the sample files under {@code + * frameworks/base/wifi/non-updatable/migration_samples/}. + * </p> + * <p> + * Note: + * <li>OEMs need to change the implementation of + * {@link #convertAndRetrieveUserConfigStoreFile(int, UserHandle)} only if their existing config + * store format or file locations differs from the vanilla AOSP implementation.</li> + * <li>The wifi apex will invoke + * {@link #convertAndRetrieveUserConfigStoreFile(int, UserHandle)} + * method on every bootup, it is the responsibility of the OEM implementation to ensure that + * they perform the necessary in place conversion of their config store file to conform to the + * AOSP format. The OEM should ensure that the method should only return the + * {@link InputStream} stream for the data to be migrated only on the first bootup.</li> + * <li>Once the migration is done, the apex will invoke + * {@link #removeUserConfigStoreFile(int, UserHandle)} to delete the store file.</li> + * <li>The only relevant invocation of + * {@link #convertAndRetrieveUserConfigStoreFile(int, UserHandle)} occurs when a previously + * released device upgrades to the wifi apex from an OEM implementation of the wifi + * stack. + * </li> + * <li>Ensure that the legacy file paths are accessible to the wifi module (sepolicy rules, file + * permissions, etc). Since the wifi service continues to run inside system_server process, this + * method will be called from the same context (so ideally the file should still be accessible). + * </li> + * + * @param storeFileId Identifier for the config store file. One of + * {@link #STORE_FILE_USER_GENERAL} or {@link #STORE_FILE_USER_NETWORK_SUGGESTIONS} + * @param userHandle User handle. + * @return Instance of {@link InputStream} for migrating data, null if no migration is + * necessary. + * @throws IllegalArgumentException on invalid storeFileId or userHandle. + */ + @Nullable + public static InputStream convertAndRetrieveUserConfigStoreFile( + @UserStoreFileId int storeFileId, @NonNull UserHandle userHandle) { + if (storeFileId != STORE_FILE_USER_GENERAL + && storeFileId != STORE_FILE_USER_NETWORK_SUGGESTIONS) { + throw new IllegalArgumentException("Invalid user store file id"); + } + Objects.requireNonNull(userHandle); + try { + // OEMs should do conversions necessary here before returning the stream. + return getUserAtomicFile(storeFileId, userHandle.getIdentifier()).openRead(); + } catch (FileNotFoundException e) { + return null; + } + } + + /** + * Remove the legacy user wifi config store file. + * + * @param storeFileId Identifier for the config store file. One of + * {@link #STORE_FILE_USER_GENERAL} or {@link #STORE_FILE_USER_NETWORK_SUGGESTIONS} + * @param userHandle User handle. + * @throws IllegalArgumentException on invalid storeFileId or userHandle. + */ + public static void removeUserConfigStoreFile( + @UserStoreFileId int storeFileId, @NonNull UserHandle userHandle) { + if (storeFileId != STORE_FILE_USER_GENERAL + && storeFileId != STORE_FILE_USER_NETWORK_SUGGESTIONS) { + throw new IllegalArgumentException("Invalid user store file id"); + } + Objects.requireNonNull(userHandle); + AtomicFile file = getUserAtomicFile(storeFileId, userHandle.getIdentifier()); + if (file.exists()) { + file.delete(); + } + } + + /** + * Container for all the wifi settings data to migrate. + */ + public static final class SettingsMigrationData implements Parcelable { + private final boolean mScanAlwaysAvailable; + private final boolean mP2pFactoryResetPending; + private final String mP2pDeviceName; + private final boolean mSoftApTimeoutEnabled; + private final boolean mWakeupEnabled; + private final boolean mScanThrottleEnabled; + private final boolean mVerboseLoggingEnabled; + + private SettingsMigrationData(boolean scanAlwaysAvailable, boolean p2pFactoryResetPending, + @Nullable String p2pDeviceName, boolean softApTimeoutEnabled, boolean wakeupEnabled, + boolean scanThrottleEnabled, boolean verboseLoggingEnabled) { + mScanAlwaysAvailable = scanAlwaysAvailable; + mP2pFactoryResetPending = p2pFactoryResetPending; + mP2pDeviceName = p2pDeviceName; + mSoftApTimeoutEnabled = softApTimeoutEnabled; + mWakeupEnabled = wakeupEnabled; + mScanThrottleEnabled = scanThrottleEnabled; + mVerboseLoggingEnabled = verboseLoggingEnabled; + } + + public static final @NonNull Parcelable.Creator<SettingsMigrationData> CREATOR = + new Parcelable.Creator<SettingsMigrationData>() { + @Override + public SettingsMigrationData createFromParcel(Parcel in) { + boolean scanAlwaysAvailable = in.readBoolean(); + boolean p2pFactoryResetPending = in.readBoolean(); + String p2pDeviceName = in.readString(); + boolean softApTimeoutEnabled = in.readBoolean(); + boolean wakeupEnabled = in.readBoolean(); + boolean scanThrottleEnabled = in.readBoolean(); + boolean verboseLoggingEnabled = in.readBoolean(); + return new SettingsMigrationData( + scanAlwaysAvailable, p2pFactoryResetPending, + p2pDeviceName, softApTimeoutEnabled, wakeupEnabled, + scanThrottleEnabled, verboseLoggingEnabled); + } + + @Override + public SettingsMigrationData[] newArray(int size) { + return new SettingsMigrationData[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeBoolean(mScanAlwaysAvailable); + dest.writeBoolean(mP2pFactoryResetPending); + dest.writeString(mP2pDeviceName); + dest.writeBoolean(mSoftApTimeoutEnabled); + dest.writeBoolean(mWakeupEnabled); + dest.writeBoolean(mScanThrottleEnabled); + dest.writeBoolean(mVerboseLoggingEnabled); + } + + /** + * @return True if scans are allowed even when wifi is toggled off, false otherwise. + */ + public boolean isScanAlwaysAvailable() { + return mScanAlwaysAvailable; + } + + /** + * @return indicate whether factory reset request is pending. + */ + public boolean isP2pFactoryResetPending() { + return mP2pFactoryResetPending; + } + + /** + * @return the Wi-Fi peer-to-peer device name + */ + public @Nullable String getP2pDeviceName() { + return mP2pDeviceName; + } + + /** + * @return Whether soft AP will shut down after a timeout period when no devices are + * connected. + */ + public boolean isSoftApTimeoutEnabled() { + return mSoftApTimeoutEnabled; + } + + /** + * @return whether Wi-Fi Wakeup feature is enabled. + */ + public boolean isWakeUpEnabled() { + return mWakeupEnabled; + } + + /** + * @return Whether wifi scan throttle is enabled or not. + */ + public boolean isScanThrottleEnabled() { + return mScanThrottleEnabled; + } + + /** + * @return Whether to enable verbose logging in Wi-Fi. + */ + public boolean isVerboseLoggingEnabled() { + return mVerboseLoggingEnabled; + } + + /** + * Builder to create instance of {@link SettingsMigrationData}. + */ + public static final class Builder { + private boolean mScanAlwaysAvailable; + private boolean mP2pFactoryResetPending; + private String mP2pDeviceName; + private boolean mSoftApTimeoutEnabled; + private boolean mWakeupEnabled; + private boolean mScanThrottleEnabled; + private boolean mVerboseLoggingEnabled; + + public Builder() { + } + + /** + * Setting to allow scans even when wifi is toggled off. + * + * @param available true if available, false otherwise. + * @return Instance of {@link Builder} to enable chaining of the builder method. + */ + public @NonNull Builder setScanAlwaysAvailable(boolean available) { + mScanAlwaysAvailable = available; + return this; + } + + /** + * Indicate whether factory reset request is pending. + * + * @param pending true if pending, false otherwise. + * @return Instance of {@link Builder} to enable chaining of the builder method. + */ + public @NonNull Builder setP2pFactoryResetPending(boolean pending) { + mP2pFactoryResetPending = pending; + return this; + } + + /** + * The Wi-Fi peer-to-peer device name + * + * @param name Name if set, null otherwise. + * @return Instance of {@link Builder} to enable chaining of the builder method. + */ + public @NonNull Builder setP2pDeviceName(@Nullable String name) { + mP2pDeviceName = name; + return this; + } + + /** + * Whether soft AP will shut down after a timeout period when no devices are connected. + * + * @param enabled true if enabled, false otherwise. + * @return Instance of {@link Builder} to enable chaining of the builder method. + */ + public @NonNull Builder setSoftApTimeoutEnabled(boolean enabled) { + mSoftApTimeoutEnabled = enabled; + return this; + } + + /** + * Value to specify if Wi-Fi Wakeup feature is enabled. + * + * @param enabled true if enabled, false otherwise. + * @return Instance of {@link Builder} to enable chaining of the builder method. + */ + public @NonNull Builder setWakeUpEnabled(boolean enabled) { + mWakeupEnabled = enabled; + return this; + } + + /** + * Whether wifi scan throttle is enabled or not. + * + * @param enabled true if enabled, false otherwise. + * @return Instance of {@link Builder} to enable chaining of the builder method. + */ + public @NonNull Builder setScanThrottleEnabled(boolean enabled) { + mScanThrottleEnabled = enabled; + return this; + } + + /** + * Setting to enable verbose logging in Wi-Fi. + * + * @param enabled true if enabled, false otherwise. + * @return Instance of {@link Builder} to enable chaining of the builder method. + */ + public @NonNull Builder setVerboseLoggingEnabled(boolean enabled) { + mVerboseLoggingEnabled = enabled; + return this; + } + + /** + * Build an instance of {@link SettingsMigrationData}. + * + * @return Instance of {@link SettingsMigrationData}. + */ + public @NonNull SettingsMigrationData build() { + return new SettingsMigrationData(mScanAlwaysAvailable, mP2pFactoryResetPending, + mP2pDeviceName, mSoftApTimeoutEnabled, mWakeupEnabled, mScanThrottleEnabled, + mVerboseLoggingEnabled); + } + } + } + + /** + * Load data from Settings.Global values. + * + * <p> + * Note: + * <li> This is method is invoked once on the first bootup. OEM can safely delete these settings + * once the migration is complete. The first & only relevant invocation of + * {@link #loadFromSettings(Context)} ()} occurs when a previously released + * device upgrades to the wifi apex from an OEM implementation of the wifi stack. + * </li> + * + * @param context Context to use for loading the settings provider. + * @return Instance of {@link SettingsMigrationData} for migrating data. + */ + @NonNull + public static SettingsMigrationData loadFromSettings(@NonNull Context context) { + if (Settings.Global.getInt( + context.getContentResolver(), Settings.Global.WIFI_MIGRATION_COMPLETED, 0) == 1) { + // migration already complete, ignore. + return null; + } + SettingsMigrationData data = new SettingsMigrationData.Builder() + .setScanAlwaysAvailable( + Settings.Global.getInt(context.getContentResolver(), + Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1) + .setP2pFactoryResetPending( + Settings.Global.getInt(context.getContentResolver(), + Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET, 0) == 1) + .setP2pDeviceName( + Settings.Global.getString(context.getContentResolver(), + Settings.Global.WIFI_P2P_DEVICE_NAME)) + .setSoftApTimeoutEnabled( + Settings.Global.getInt(context.getContentResolver(), + Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1) == 1) + .setWakeUpEnabled( + Settings.Global.getInt(context.getContentResolver(), + Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1) + .setScanThrottleEnabled( + Settings.Global.getInt(context.getContentResolver(), + Settings.Global.WIFI_SCAN_THROTTLE_ENABLED, 1) == 1) + .setVerboseLoggingEnabled( + Settings.Global.getInt(context.getContentResolver(), + Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, 0) == 1) + .build(); + Settings.Global.putInt( + context.getContentResolver(), Settings.Global.WIFI_MIGRATION_COMPLETED, 1); + return data; + + } +} diff --git a/wifi/java/src/android/net/wifi/WifiNetworkScoreCache.java b/wifi/java/src/android/net/wifi/WifiNetworkScoreCache.java new file mode 100755 index 000000000000..39036580e2ef --- /dev/null +++ b/wifi/java/src/android/net/wifi/WifiNetworkScoreCache.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +import android.Manifest.permission; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.net.INetworkScoreCache; +import android.net.NetworkKey; +import android.net.ScoredNetwork; +import android.os.Handler; +import android.os.Process; +import android.util.Log; +import android.util.LruCache; + +import com.android.internal.annotations.GuardedBy; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; +import java.util.Objects; + +/** + * {@link INetworkScoreCache} implementation for Wifi Networks. + * + * TODO: This should not be part of wifi mainline module. + * @hide + */ +public class WifiNetworkScoreCache extends INetworkScoreCache.Stub { + private static final String TAG = "WifiNetworkScoreCache"; + private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); + + // A Network scorer returns a score in the range [-128, +127] + // We treat the lowest possible score as though there were no score, effectively allowing the + // scorer to provide an RSSI threshold below which a network should not be used. + public static final int INVALID_NETWORK_SCORE = Byte.MIN_VALUE; + + /** Default number entries to be stored in the {@link LruCache}. */ + private static final int DEFAULT_MAX_CACHE_SIZE = 100; + + // See {@link #CacheListener}. + @Nullable + @GuardedBy("mLock") + private CacheListener mListener; + + private final Context mContext; + private final Object mLock = new Object(); + + // The key is of the form "<ssid>"<bssid> + // TODO: What about SSIDs that can't be encoded as UTF-8? + @GuardedBy("mLock") + private final LruCache<String, ScoredNetwork> mCache; + + public WifiNetworkScoreCache(Context context) { + this(context, null /* listener */); + } + + /** + * Instantiates a WifiNetworkScoreCache. + * + * @param context Application context + * @param listener CacheListener for cache updates + */ + public WifiNetworkScoreCache(Context context, @Nullable CacheListener listener) { + this(context, listener, DEFAULT_MAX_CACHE_SIZE); + } + + public WifiNetworkScoreCache( + Context context, @Nullable CacheListener listener, int maxCacheSize) { + mContext = context.getApplicationContext(); + mListener = listener; + mCache = new LruCache<>(maxCacheSize); + } + + @Override public final void updateScores(List<ScoredNetwork> networks) { + if (networks == null || networks.isEmpty()) { + return; + } + if (DBG) { + Log.d(TAG, "updateScores list size=" + networks.size()); + } + + boolean changed = false; + + synchronized (mLock) { + for (ScoredNetwork network : networks) { + String networkKey = buildNetworkKey(network); + if (networkKey == null) { + if (DBG) { + Log.d(TAG, "Failed to build network key for ScoredNetwork" + network); + } + continue; + } + mCache.put(networkKey, network); + changed = true; + } + + if (mListener != null && changed) { + mListener.post(networks); + } + } + } + + @Override public final void clearScores() { + synchronized (mLock) { + mCache.evictAll(); + } + } + + /** + * Returns whether there is any score info for the given ScanResult. + * + * This includes null-score info, so it should only be used when determining whether to request + * scores from the network scorer. + */ + public boolean isScoredNetwork(ScanResult result) { + return getScoredNetwork(result) != null; + } + + /** + * Returns whether there is a non-null score curve for the given ScanResult. + * + * A null score curve has special meaning - we should never connect to an ephemeral network if + * the score curve is null. + */ + public boolean hasScoreCurve(ScanResult result) { + ScoredNetwork network = getScoredNetwork(result); + return network != null && network.rssiCurve != null; + } + + public int getNetworkScore(ScanResult result) { + int score = INVALID_NETWORK_SCORE; + + ScoredNetwork network = getScoredNetwork(result); + if (network != null && network.rssiCurve != null) { + score = network.rssiCurve.lookupScore(result.level); + if (DBG) { + Log.d(TAG, "getNetworkScore found scored network " + network.networkKey + + " score " + Integer.toString(score) + + " RSSI " + result.level); + } + } + return score; + } + + /** + * Returns the ScoredNetwork metered hint for a given ScanResult. + * + * If there is no ScoredNetwork associated with the ScanResult then false will be returned. + */ + public boolean getMeteredHint(ScanResult result) { + ScoredNetwork network = getScoredNetwork(result); + return network != null && network.meteredHint; + } + + public int getNetworkScore(ScanResult result, boolean isActiveNetwork) { + int score = INVALID_NETWORK_SCORE; + + ScoredNetwork network = getScoredNetwork(result); + if (network != null && network.rssiCurve != null) { + score = network.rssiCurve.lookupScore(result.level, isActiveNetwork); + if (DBG) { + Log.d(TAG, "getNetworkScore found scored network " + network.networkKey + + " score " + Integer.toString(score) + + " RSSI " + result.level + + " isActiveNetwork " + isActiveNetwork); + } + } + return score; + } + + @Nullable + public ScoredNetwork getScoredNetwork(ScanResult result) { + String key = buildNetworkKey(result); + if (key == null) return null; + + synchronized (mLock) { + ScoredNetwork network = mCache.get(key); + return network; + } + } + + /** Returns the ScoredNetwork for the given key. */ + @Nullable + public ScoredNetwork getScoredNetwork(NetworkKey networkKey) { + String key = buildNetworkKey(networkKey); + if (key == null) { + if (DBG) { + Log.d(TAG, "Could not build key string for Network Key: " + networkKey); + } + return null; + } + synchronized (mLock) { + return mCache.get(key); + } + } + + private String buildNetworkKey(ScoredNetwork network) { + if (network == null) { + return null; + } + return buildNetworkKey(network.networkKey); + } + + private String buildNetworkKey(NetworkKey networkKey) { + if (networkKey == null) { + return null; + } + if (networkKey.wifiKey == null) return null; + if (networkKey.type == NetworkKey.TYPE_WIFI) { + String key = networkKey.wifiKey.ssid; + if (key == null) return null; + if (networkKey.wifiKey.bssid != null) { + key = key + networkKey.wifiKey.bssid; + } + return key; + } + return null; + } + + private String buildNetworkKey(ScanResult result) { + if (result == null || result.SSID == null) { + return null; + } + StringBuilder key = new StringBuilder("\""); + key.append(result.SSID); + key.append("\""); + if (result.BSSID != null) { + key.append(result.BSSID); + } + return key.toString(); + } + + @Override protected final void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG); + String header = String.format("WifiNetworkScoreCache (%s/%d)", + mContext.getPackageName(), Process.myUid()); + writer.println(header); + writer.println(" All score curves:"); + synchronized (mLock) { + for (ScoredNetwork score : mCache.snapshot().values()) { + writer.println(" " + score); + } + writer.println(" Network scores for latest ScanResults:"); + WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + for (ScanResult scanResult : wifiManager.getScanResults()) { + writer.println( + " " + buildNetworkKey(scanResult) + ": " + getNetworkScore(scanResult)); + } + } + } + + /** Registers a CacheListener instance, replacing the previous listener if it existed. */ + public void registerListener(CacheListener listener) { + synchronized (mLock) { + mListener = listener; + } + } + + /** Removes the registered CacheListener. */ + public void unregisterListener() { + synchronized (mLock) { + mListener = null; + } + } + + /** Listener for updates to the cache inside WifiNetworkScoreCache. */ + public abstract static class CacheListener { + private Handler mHandler; + + /** + * Constructor for CacheListener. + * + * @param handler the Handler on which to invoke the {@link #networkCacheUpdated} method. + * This cannot be null. + */ + public CacheListener(@NonNull Handler handler) { + Objects.requireNonNull(handler); + mHandler = handler; + } + + /** Invokes the {@link #networkCacheUpdated(List<ScoredNetwork>)} method on the handler. */ + void post(List<ScoredNetwork> updatedNetworks) { + mHandler.post(new Runnable() { + @Override + public void run() { + networkCacheUpdated(updatedNetworks); + } + }); + } + + /** + * Invoked whenever the cache is updated. + * + * <p>Clearing the cache does not invoke this method. + * + * @param updatedNetworks the networks that were updated + */ + public abstract void networkCacheUpdated(List<ScoredNetwork> updatedNetworks); + } +} diff --git a/wifi/java/src/android/net/wifi/nl80211/ChannelSettings.java b/wifi/java/src/android/net/wifi/nl80211/ChannelSettings.java new file mode 100644 index 000000000000..4c14fd499c28 --- /dev/null +++ b/wifi/java/src/android/net/wifi/nl80211/ChannelSettings.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.nl80211; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.util.Objects; + +/** + * ChannelSettings for wificond + * + * @hide + */ +public class ChannelSettings implements Parcelable { + private static final String TAG = "ChannelSettings"; + + public int frequency; + + /** public constructor */ + public ChannelSettings() { } + + /** override comparator */ + @Override + public boolean equals(Object rhs) { + if (this == rhs) return true; + if (!(rhs instanceof ChannelSettings)) { + return false; + } + ChannelSettings channel = (ChannelSettings) rhs; + if (channel == null) { + return false; + } + return frequency == channel.frequency; + } + + /** override hash code */ + @Override + public int hashCode() { + return Objects.hash(frequency); + } + + /** implement Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** + * implement Parcelable interface + * |flags| is ignored. + **/ + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(frequency); + } + + /** implement Parcelable interface */ + public static final Parcelable.Creator<ChannelSettings> CREATOR = + new Parcelable.Creator<ChannelSettings>() { + /** + * Caller is responsible for providing a valid parcel. + */ + @Override + public ChannelSettings createFromParcel(Parcel in) { + ChannelSettings result = new ChannelSettings(); + result.frequency = in.readInt(); + if (in.dataAvail() != 0) { + Log.e(TAG, "Found trailing data after parcel parsing."); + } + + return result; + } + + @Override + public ChannelSettings[] newArray(int size) { + return new ChannelSettings[size]; + } + }; +} diff --git a/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java b/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java new file mode 100644 index 000000000000..bb0cc975a3db --- /dev/null +++ b/wifi/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.nl80211; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiAnnotations.ChannelWidth; +import android.net.wifi.WifiAnnotations.WifiStandard; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.util.Objects; + +/** + * DeviceWiphyCapabilities for wificond + * + * Contains the WiFi physical layer attributes and capabilities of the device. + * It is used to collect these attributes from the device driver via wificond. + * + * @hide + */ +@SystemApi +public final class DeviceWiphyCapabilities implements Parcelable { + private static final String TAG = "DeviceWiphyCapabilities"; + + private boolean m80211nSupported; + private boolean m80211acSupported; + private boolean m80211axSupported; + private boolean mChannelWidth160MhzSupported; + private boolean mChannelWidth80p80MhzSupported; + private int mMaxNumberTxSpatialStreams; + private int mMaxNumberRxSpatialStreams; + + + /** public constructor */ + public DeviceWiphyCapabilities() { + m80211nSupported = false; + m80211acSupported = false; + m80211axSupported = false; + mChannelWidth160MhzSupported = false; + mChannelWidth80p80MhzSupported = false; + mMaxNumberTxSpatialStreams = 1; + mMaxNumberRxSpatialStreams = 1; + } + + /** + * Get the IEEE 802.11 standard support + * + * @param standard the IEEE 802.11 standard to check on its support. + * valid values from {@link ScanResult}'s {@code WIFI_STANDARD_} + * @return {@code true} if supported, {@code false} otherwise. + */ + public boolean isWifiStandardSupported(@WifiStandard int standard) { + switch (standard) { + case ScanResult.WIFI_STANDARD_LEGACY: + return true; + case ScanResult.WIFI_STANDARD_11N: + return m80211nSupported; + case ScanResult.WIFI_STANDARD_11AC: + return m80211acSupported; + case ScanResult.WIFI_STANDARD_11AX: + return m80211axSupported; + default: + Log.e(TAG, "isWifiStandardSupported called with invalid standard: " + standard); + return false; + } + } + + /** + * Set the IEEE 802.11 standard support + * + * @param standard the IEEE 802.11 standard to set its support. + * valid values from {@link ScanResult}'s {@code WIFI_STANDARD_} + * @param support {@code true} if supported, {@code false} otherwise. + */ + public void setWifiStandardSupport(@WifiStandard int standard, boolean support) { + switch (standard) { + case ScanResult.WIFI_STANDARD_11N: + m80211nSupported = support; + break; + case ScanResult.WIFI_STANDARD_11AC: + m80211acSupported = support; + break; + case ScanResult.WIFI_STANDARD_11AX: + m80211axSupported = support; + break; + default: + Log.e(TAG, "setWifiStandardSupport called with invalid standard: " + standard); + } + } + + /** + * Get the support for channel bandwidth + * + * @param chWidth valid values from {@link ScanResult}'s {@code CHANNEL_WIDTH_} + * + * @return {@code true} if supported, {@code false} otherwise. + */ + public boolean isChannelWidthSupported(@ChannelWidth int chWidth) { + switch (chWidth) { + case ScanResult.CHANNEL_WIDTH_20MHZ: + return true; + case ScanResult.CHANNEL_WIDTH_40MHZ: + return (m80211nSupported || m80211acSupported || m80211axSupported); + case ScanResult.CHANNEL_WIDTH_80MHZ: + return (m80211acSupported || m80211axSupported); + case ScanResult.CHANNEL_WIDTH_160MHZ: + return mChannelWidth160MhzSupported; + case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ: + return mChannelWidth80p80MhzSupported; + default: + Log.e(TAG, "isChannelWidthSupported called with invalid channel width: " + chWidth); + } + return false; + } + + /** + * Set support for channel bandwidth + * + * @param chWidth valid values are {@link ScanResult#CHANNEL_WIDTH_160MHZ} and + * {@link ScanResult#CHANNEL_WIDTH_80MHZ_PLUS_MHZ} + * @param support {@code true} if supported, {@code false} otherwise. + * + * @hide + */ + public void setChannelWidthSupported(@ChannelWidth int chWidth, boolean support) { + switch (chWidth) { + case ScanResult.CHANNEL_WIDTH_160MHZ: + mChannelWidth160MhzSupported = support; + break; + case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ: + mChannelWidth80p80MhzSupported = support; + break; + default: + Log.e(TAG, "setChannelWidthSupported called with Invalid channel width: " + + chWidth); + } + } + + /** + * Get maximum number of transmit spatial streams + * + * @return number of spatial streams + */ + public int getMaxNumberTxSpatialStreams() { + return mMaxNumberTxSpatialStreams; + } + + /** + * Set maximum number of transmit spatial streams + * + * @param streams number of spatial streams + * + * @hide + */ + public void setMaxNumberTxSpatialStreams(int streams) { + mMaxNumberTxSpatialStreams = streams; + } + + /** + * Get maximum number of receive spatial streams + * + * @return number of streams + */ + public int getMaxNumberRxSpatialStreams() { + return mMaxNumberRxSpatialStreams; + } + + /** + * Set maximum number of receive spatial streams + * + * @param streams number of streams + * + * @hide + */ + public void setMaxNumberRxSpatialStreams(int streams) { + mMaxNumberRxSpatialStreams = streams; + } + + /** override comparator */ + @Override + public boolean equals(Object rhs) { + if (this == rhs) return true; + if (!(rhs instanceof DeviceWiphyCapabilities)) { + return false; + } + DeviceWiphyCapabilities capa = (DeviceWiphyCapabilities) rhs; + + return m80211nSupported == capa.m80211nSupported + && m80211acSupported == capa.m80211acSupported + && m80211axSupported == capa.m80211axSupported + && mChannelWidth160MhzSupported == capa.mChannelWidth160MhzSupported + && mChannelWidth80p80MhzSupported == capa.mChannelWidth80p80MhzSupported + && mMaxNumberTxSpatialStreams == capa.mMaxNumberTxSpatialStreams + && mMaxNumberRxSpatialStreams == capa.mMaxNumberRxSpatialStreams; + } + + /** override hash code */ + @Override + public int hashCode() { + return Objects.hash(m80211nSupported, m80211acSupported, m80211axSupported, + mChannelWidth160MhzSupported, mChannelWidth80p80MhzSupported, + mMaxNumberTxSpatialStreams, mMaxNumberRxSpatialStreams); + } + + /** implement Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** + * implement Parcelable interface + * |flags| is ignored. + */ + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeBoolean(m80211nSupported); + out.writeBoolean(m80211acSupported); + out.writeBoolean(m80211axSupported); + out.writeBoolean(mChannelWidth160MhzSupported); + out.writeBoolean(mChannelWidth80p80MhzSupported); + out.writeInt(mMaxNumberTxSpatialStreams); + out.writeInt(mMaxNumberRxSpatialStreams); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("m80211nSupported:").append(m80211nSupported ? "Yes" : "No"); + sb.append("m80211acSupported:").append(m80211acSupported ? "Yes" : "No"); + sb.append("m80211axSupported:").append(m80211axSupported ? "Yes" : "No"); + sb.append("mChannelWidth160MhzSupported: ") + .append(mChannelWidth160MhzSupported ? "Yes" : "No"); + sb.append("mChannelWidth80p80MhzSupported: ") + .append(mChannelWidth80p80MhzSupported ? "Yes" : "No"); + sb.append("mMaxNumberTxSpatialStreams: ").append(mMaxNumberTxSpatialStreams); + sb.append("mMaxNumberRxSpatialStreams: ").append(mMaxNumberRxSpatialStreams); + + return sb.toString(); + } + + /** implement Parcelable interface */ + public static final @NonNull Parcelable.Creator<DeviceWiphyCapabilities> CREATOR = + new Parcelable.Creator<DeviceWiphyCapabilities>() { + /** + * Caller is responsible for providing a valid parcel. + */ + @Override + public DeviceWiphyCapabilities createFromParcel(Parcel in) { + DeviceWiphyCapabilities capabilities = new DeviceWiphyCapabilities(); + capabilities.m80211nSupported = in.readBoolean(); + capabilities.m80211acSupported = in.readBoolean(); + capabilities.m80211axSupported = in.readBoolean(); + capabilities.mChannelWidth160MhzSupported = in.readBoolean(); + capabilities.mChannelWidth80p80MhzSupported = in.readBoolean(); + capabilities.mMaxNumberTxSpatialStreams = in.readInt(); + capabilities.mMaxNumberRxSpatialStreams = in.readInt(); + return capabilities; + } + + @Override + public DeviceWiphyCapabilities[] newArray(int size) { + return new DeviceWiphyCapabilities[size]; + } + }; +} diff --git a/wifi/java/src/android/net/wifi/nl80211/HiddenNetwork.java b/wifi/java/src/android/net/wifi/nl80211/HiddenNetwork.java new file mode 100644 index 000000000000..b1475b2c7b43 --- /dev/null +++ b/wifi/java/src/android/net/wifi/nl80211/HiddenNetwork.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.nl80211; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; + +/** + * HiddenNetwork for wificond + * + * @hide + */ +public class HiddenNetwork implements Parcelable { + private static final String TAG = "HiddenNetwork"; + + public byte[] ssid; + + /** public constructor */ + public HiddenNetwork() { } + + /** override comparator */ + @Override + public boolean equals(Object rhs) { + if (this == rhs) return true; + if (!(rhs instanceof HiddenNetwork)) { + return false; + } + HiddenNetwork network = (HiddenNetwork) rhs; + return Arrays.equals(ssid, network.ssid); + } + + /** override hash code */ + @Override + public int hashCode() { + return Arrays.hashCode(ssid); + } + + /** implement Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** + * implement Parcelable interface + * |flags| is ignored. + */ + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeByteArray(ssid); + } + + /** implement Parcelable interface */ + public static final Parcelable.Creator<HiddenNetwork> CREATOR = + new Parcelable.Creator<HiddenNetwork>() { + /** + * Caller is responsible for providing a valid parcel. + */ + @Override + public HiddenNetwork createFromParcel(Parcel in) { + HiddenNetwork result = new HiddenNetwork(); + result.ssid = in.createByteArray(); + return result; + } + + @Override + public HiddenNetwork[] newArray(int size) { + return new HiddenNetwork[size]; + } + }; +} diff --git a/wifi/java/src/android/net/wifi/nl80211/NativeScanResult.java b/wifi/java/src/android/net/wifi/nl80211/NativeScanResult.java new file mode 100644 index 000000000000..a8e999973fe8 --- /dev/null +++ b/wifi/java/src/android/net/wifi/nl80211/NativeScanResult.java @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.nl80211; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.net.MacAddress; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Raw scan result data from the wificond daemon. + * + * @hide + */ +@SystemApi +public final class NativeScanResult implements Parcelable { + private static final String TAG = "NativeScanResult"; + + /** @hide */ + @VisibleForTesting + public byte[] ssid; + /** @hide */ + @VisibleForTesting + public byte[] bssid; + /** @hide */ + @VisibleForTesting + public byte[] infoElement; + /** @hide */ + @VisibleForTesting + public int frequency; + /** @hide */ + @VisibleForTesting + public int signalMbm; + /** @hide */ + @VisibleForTesting + public long tsf; + /** @hide */ + @VisibleForTesting + @BssCapabilityBits public int capability; + /** @hide */ + @VisibleForTesting + public boolean associated; + /** @hide */ + @VisibleForTesting + public List<RadioChainInfo> radioChainInfos; + + /** + * Returns the SSID raw byte array of the AP represented by this scan result. + * + * @return A byte array. + */ + @NonNull public byte[] getSsid() { + return ssid; + } + + /** + * Returns the MAC address (BSSID) of the AP represented by this scan result. + * + * @return a MacAddress or null on error. + */ + @Nullable public MacAddress getBssid() { + try { + return MacAddress.fromBytes(bssid); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument " + Arrays.toString(bssid), e); + return null; + } + } + + /** + * Returns the raw bytes of the information element advertised by the AP represented by this + * scan result. + * + * @return A byte array, possibly null or containing an invalid TLV configuration. + */ + @NonNull public byte[] getInformationElements() { + return infoElement; + } + + /** + * Returns the frequency (in MHz) on which the AP represented by this scan result was observed. + * + * @return The frequency in MHz. + */ + public int getFrequencyMhz() { + return frequency; + } + + /** + * Return the signal strength of probe response/beacon in (100 * dBm). + * + * @return Signal strenght in (100 * dBm). + */ + public int getSignalMbm() { + return signalMbm; + } + + /** + * Return the TSF (Timing Synchronization Function) of the received probe response/beacon. + * @return + */ + public long getTsf() { + return tsf; + } + + /** + * Return a boolean indicating whether or not we're associated to the AP represented by this + * scan result. + * + * @return A boolean indicating association. + */ + public boolean isAssociated() { + return associated; + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = {"BSS_CAPABILITY_"}, + value = {BSS_CAPABILITY_ESS, + BSS_CAPABILITY_IBSS, + BSS_CAPABILITY_CF_POLLABLE, + BSS_CAPABILITY_CF_POLL_REQUEST, + BSS_CAPABILITY_PRIVACY, + BSS_CAPABILITY_SHORT_PREAMBLE, + BSS_CAPABILITY_PBCC, + BSS_CAPABILITY_CHANNEL_AGILITY, + BSS_CAPABILITY_SPECTRUM_MANAGEMENT, + BSS_CAPABILITY_QOS, + BSS_CAPABILITY_SHORT_SLOT_TIME, + BSS_CAPABILITY_APSD, + BSS_CAPABILITY_RADIO_MANAGEMENT, + BSS_CAPABILITY_DSSS_OFDM, + BSS_CAPABILITY_DELAYED_BLOCK_ACK, + BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK + }) + public @interface BssCapabilityBits { } + + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): ESS. + */ + public static final int BSS_CAPABILITY_ESS = 0x1 << 0; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): IBSS. + */ + public static final int BSS_CAPABILITY_IBSS = 0x1 << 1; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): CF Pollable. + */ + public static final int BSS_CAPABILITY_CF_POLLABLE = 0x1 << 2; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): CF-Poll Request. + */ + public static final int BSS_CAPABILITY_CF_POLL_REQUEST = 0x1 << 3; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Privacy. + */ + public static final int BSS_CAPABILITY_PRIVACY = 0x1 << 4; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Short Preamble. + */ + public static final int BSS_CAPABILITY_SHORT_PREAMBLE = 0x1 << 5; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): PBCC. + */ + public static final int BSS_CAPABILITY_PBCC = 0x1 << 6; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Channel Agility. + */ + public static final int BSS_CAPABILITY_CHANNEL_AGILITY = 0x1 << 7; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Spectrum Management. + */ + public static final int BSS_CAPABILITY_SPECTRUM_MANAGEMENT = 0x1 << 8; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): QoS. + */ + public static final int BSS_CAPABILITY_QOS = 0x1 << 9; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Short Slot Time. + */ + public static final int BSS_CAPABILITY_SHORT_SLOT_TIME = 0x1 << 10; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): APSD. + */ + public static final int BSS_CAPABILITY_APSD = 0x1 << 11; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Radio Management. + */ + public static final int BSS_CAPABILITY_RADIO_MANAGEMENT = 0x1 << 12; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): DSSS-OFDM. + */ + public static final int BSS_CAPABILITY_DSSS_OFDM = 0x1 << 13; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Delayed Block Ack. + */ + public static final int BSS_CAPABILITY_DELAYED_BLOCK_ACK = 0x1 << 14; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Immediate Block Ack. + */ + public static final int BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK = 0x1 << 15; + + /** + * Returns the capabilities of the AP repseresented by this scan result as advertised in the + * received probe response or beacon. + * + * This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 9.4.1.4: one + * of the {@code BSS_CAPABILITY_*} flags. + * + * @return a bit mask of capabilities. + */ + @BssCapabilityBits public int getCapabilities() { + return capability; + } + + /** + * Returns details of the signal received on each radio chain for the AP represented by this + * scan result in a list of {@link RadioChainInfo} elements. + * + * @return A list of {@link RadioChainInfo} - possibly empty in case of error. + */ + @NonNull public List<RadioChainInfo> getRadioChainInfos() { + return radioChainInfos; + } + + /** + * Construct an empty native scan result. + */ + public NativeScanResult() { } + + /** implement Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** implement Parcelable interface */ + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeByteArray(ssid); + out.writeByteArray(bssid); + out.writeByteArray(infoElement); + out.writeInt(frequency); + out.writeInt(signalMbm); + out.writeLong(tsf); + out.writeInt(capability); + out.writeInt(associated ? 1 : 0); + out.writeTypedList(radioChainInfos); + } + + /** implement Parcelable interface */ + @NonNull public static final Parcelable.Creator<NativeScanResult> CREATOR = + new Parcelable.Creator<NativeScanResult>() { + @Override + public NativeScanResult createFromParcel(Parcel in) { + NativeScanResult result = new NativeScanResult(); + result.ssid = in.createByteArray(); + if (result.ssid == null) { + result.ssid = new byte[0]; + } + result.bssid = in.createByteArray(); + if (result.bssid == null) { + result.bssid = new byte[0]; + } + result.infoElement = in.createByteArray(); + if (result.infoElement == null) { + result.infoElement = new byte[0]; + } + result.frequency = in.readInt(); + result.signalMbm = in.readInt(); + result.tsf = in.readLong(); + result.capability = in.readInt(); + result.associated = (in.readInt() != 0); + result.radioChainInfos = new ArrayList<>(); + in.readTypedList(result.radioChainInfos, RadioChainInfo.CREATOR); + return result; + } + + @Override + public NativeScanResult[] newArray(int size) { + return new NativeScanResult[size]; + } + }; +} diff --git a/wifi/java/src/android/net/wifi/nl80211/NativeWifiClient.java b/wifi/java/src/android/net/wifi/nl80211/NativeWifiClient.java new file mode 100644 index 000000000000..984d7d034302 --- /dev/null +++ b/wifi/java/src/android/net/wifi/nl80211/NativeWifiClient.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.nl80211; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.net.MacAddress; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Structure providing information about clients (STAs) associated with a SoftAp. + * + * @hide + */ +@SystemApi +public final class NativeWifiClient implements Parcelable { + private final MacAddress mMacAddress; + + /** + * The MAC address of the client (STA) represented by this object. The MAC address may be null + * in case of an error. + */ + @Nullable public MacAddress getMacAddress() { + return mMacAddress; + } + + /** + * Construct a native Wi-Fi client. + */ + public NativeWifiClient(@Nullable MacAddress macAddress) { + this.mMacAddress = macAddress; + } + + /** override comparator */ + @Override + public boolean equals(Object rhs) { + if (this == rhs) return true; + if (!(rhs instanceof NativeWifiClient)) { + return false; + } + NativeWifiClient other = (NativeWifiClient) rhs; + return Objects.equals(mMacAddress, other.mMacAddress); + } + + /** override hash code */ + @Override + public int hashCode() { + return mMacAddress.hashCode(); + } + + /** implement Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** + * implement Parcelable interface + * |flag| is ignored. + */ + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeByteArray(mMacAddress.toByteArray()); + } + + /** implement Parcelable interface */ + @NonNull public static final Parcelable.Creator<NativeWifiClient> CREATOR = + new Parcelable.Creator<NativeWifiClient>() { + @Override + public NativeWifiClient createFromParcel(Parcel in) { + MacAddress macAddress; + try { + macAddress = MacAddress.fromBytes(in.createByteArray()); + } catch (IllegalArgumentException e) { + macAddress = null; + } + return new NativeWifiClient(macAddress); + } + + @Override + public NativeWifiClient[] newArray(int size) { + return new NativeWifiClient[size]; + } + }; +} diff --git a/wifi/java/src/android/net/wifi/nl80211/PnoNetwork.java b/wifi/java/src/android/net/wifi/nl80211/PnoNetwork.java new file mode 100644 index 000000000000..e8eff09583b9 --- /dev/null +++ b/wifi/java/src/android/net/wifi/nl80211/PnoNetwork.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.nl80211; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; +import java.util.Objects; + +/** + * Configuration for a PNO (preferred network offload) network used in {@link PnoSettings}. A PNO + * network allows configuration of a specific network to search for. + * + * @hide + */ +@SystemApi +public final class PnoNetwork implements Parcelable { + private boolean mIsHidden; + private byte[] mSsid; + private int[] mFrequencies; + + /** + * Indicates whether the PNO network configuration is for a hidden SSID - i.e. a network which + * does not broadcast its SSID and must be queried explicitly. + * + * @return True if the configuration is for a hidden network, false otherwise. + */ + public boolean isHidden() { + return mIsHidden; + } + + /** + * Configure whether the PNO network configuration is for a hidden SSID - i.e. a network which + * does not broadcast its SSID and must be queried explicitly. + * + * @param isHidden True if the configuration is for a hidden network, false otherwise. + */ + public void setHidden(boolean isHidden) { + mIsHidden = isHidden; + } + + /** + * Get the raw bytes for the SSID of the PNO network being scanned for. + * + * @return A byte array. + */ + @NonNull public byte[] getSsid() { + return mSsid; + } + + /** + * Set the raw bytes for the SSID of the PNO network being scanned for. + * + * @param ssid A byte array. + */ + public void setSsid(@NonNull byte[] ssid) { + if (ssid == null) { + throw new IllegalArgumentException("null argument"); + } + this.mSsid = ssid; + } + + /** + * Get the frequencies (in MHz) on which to PNO scan for the current network is being searched + * for. A null return (i.e. no frequencies configured) indicates that the network is search for + * on all supported frequencies. + * + * @return A array of frequencies (in MHz), a null indicates no configured frequencies. + */ + @NonNull public int[] getFrequenciesMhz() { + return mFrequencies; + } + + /** + * Set the frequencies (in MHz) on which to PNO scan for the current network is being searched + * for. A null configuration (i.e. no frequencies configured) indicates that the network is + * search for on all supported frequencies. + * + * @param frequenciesMhz an array of frequencies (in MHz), null indicating no configured + * frequencies. + */ + public void setFrequenciesMhz(@NonNull int[] frequenciesMhz) { + if (frequenciesMhz == null) { + throw new IllegalArgumentException("null argument"); + } + this.mFrequencies = frequenciesMhz; + } + + /** Construct an uninitialized PnoNetwork object */ + public PnoNetwork() { } + + /** override comparator */ + @Override + public boolean equals(Object rhs) { + if (this == rhs) return true; + if (!(rhs instanceof PnoNetwork)) { + return false; + } + PnoNetwork network = (PnoNetwork) rhs; + return Arrays.equals(mSsid, network.mSsid) + && Arrays.equals(mFrequencies, network.mFrequencies) + && mIsHidden == network.mIsHidden; + } + + /** override hash code */ + @Override + public int hashCode() { + return Objects.hash( + mIsHidden, + Arrays.hashCode(mSsid), + Arrays.hashCode(mFrequencies)); + } + + /** implement Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** + * implement Parcelable interface + * |flag| is ignored. + */ + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(mIsHidden ? 1 : 0); + out.writeByteArray(mSsid); + out.writeIntArray(mFrequencies); + } + + /** implement Parcelable interface */ + @NonNull public static final Parcelable.Creator<PnoNetwork> CREATOR = + new Parcelable.Creator<PnoNetwork>() { + @Override + public PnoNetwork createFromParcel(Parcel in) { + PnoNetwork result = new PnoNetwork(); + result.mIsHidden = in.readInt() != 0 ? true : false; + result.mSsid = in.createByteArray(); + if (result.mSsid == null) { + result.mSsid = new byte[0]; + } + result.mFrequencies = in.createIntArray(); + if (result.mFrequencies == null) { + result.mFrequencies = new int[0]; + } + return result; + } + + @Override + public PnoNetwork[] newArray(int size) { + return new PnoNetwork[size]; + } + }; +} diff --git a/wifi/java/src/android/net/wifi/nl80211/PnoSettings.java b/wifi/java/src/android/net/wifi/nl80211/PnoSettings.java new file mode 100644 index 000000000000..00ebe624ba0d --- /dev/null +++ b/wifi/java/src/android/net/wifi/nl80211/PnoSettings.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.nl80211; + +import android.annotation.DurationMillisLong; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Configuration for a PNO (preferred network offload). A mechanism by which scans are offloaded + * from the host device to the Wi-Fi chip. + * + * @hide + */ +@SystemApi +public final class PnoSettings implements Parcelable { + private long mIntervalMs; + private int mMin2gRssi; + private int mMin5gRssi; + private int mMin6gRssi; + private List<PnoNetwork> mPnoNetworks; + + /** Construct an uninitialized PnoSettings object */ + public PnoSettings() { } + + /** + * Get the requested PNO scan interval in milliseconds. + * + * @return An interval in milliseconds. + */ + public @DurationMillisLong long getIntervalMillis() { + return mIntervalMs; + } + + /** + * Set the requested PNO scan interval in milliseconds. + * + * @param intervalMillis An interval in milliseconds. + */ + public void setIntervalMillis(@DurationMillisLong long intervalMillis) { + this.mIntervalMs = intervalMillis; + } + + /** + * Get the requested minimum RSSI threshold (in dBm) for APs to report in scan results in the + * 2.4GHz band. + * + * @return An RSSI value in dBm. + */ + public int getMin2gRssiDbm() { + return mMin2gRssi; + } + + /** + * Set the requested minimum RSSI threshold (in dBm) for APs to report in scan scan results in + * the 2.4GHz band. + * + * @param min2gRssiDbm An RSSI value in dBm. + */ + public void setMin2gRssiDbm(int min2gRssiDbm) { + this.mMin2gRssi = min2gRssiDbm; + } + + /** + * Get the requested minimum RSSI threshold (in dBm) for APs to report in scan results in the + * 5GHz band. + * + * @return An RSSI value in dBm. + */ + public int getMin5gRssiDbm() { + return mMin5gRssi; + } + + /** + * Set the requested minimum RSSI threshold (in dBm) for APs to report in scan scan results in + * the 5GHz band. + * + * @param min5gRssiDbm An RSSI value in dBm. + */ + public void setMin5gRssiDbm(int min5gRssiDbm) { + this.mMin5gRssi = min5gRssiDbm; + } + + /** + * Get the requested minimum RSSI threshold (in dBm) for APs to report in scan results in the + * 6GHz band. + * + * @return An RSSI value in dBm. + */ + public int getMin6gRssiDbm() { + return mMin6gRssi; + } + + /** + * Set the requested minimum RSSI threshold (in dBm) for APs to report in scan scan results in + * the 6GHz band. + * + * @param min6gRssiDbm An RSSI value in dBm. + */ + public void setMin6gRssiDbm(int min6gRssiDbm) { + this.mMin6gRssi = min6gRssiDbm; + } + + /** + * Return the configured list of specific networks to search for in a PNO scan. + * + * @return A list of {@link PnoNetwork} objects, possibly empty if non configured. + */ + @NonNull public List<PnoNetwork> getPnoNetworks() { + return mPnoNetworks; + } + + /** + * Set the list of specified networks to scan for in a PNO scan. The networks (APs) are + * specified using {@link PnoNetwork}s. An empty list indicates that all networks are scanned + * for. + * + * @param pnoNetworks A (possibly empty) list of {@link PnoNetwork} objects. + */ + public void setPnoNetworks(@NonNull List<PnoNetwork> pnoNetworks) { + this.mPnoNetworks = pnoNetworks; + } + + /** override comparator */ + @Override + public boolean equals(Object rhs) { + if (this == rhs) return true; + if (!(rhs instanceof PnoSettings)) { + return false; + } + PnoSettings settings = (PnoSettings) rhs; + if (settings == null) { + return false; + } + return mIntervalMs == settings.mIntervalMs + && mMin2gRssi == settings.mMin2gRssi + && mMin5gRssi == settings.mMin5gRssi + && mMin6gRssi == settings.mMin6gRssi + && mPnoNetworks.equals(settings.mPnoNetworks); + } + + /** override hash code */ + @Override + public int hashCode() { + return Objects.hash(mIntervalMs, mMin2gRssi, mMin5gRssi, mMin6gRssi, mPnoNetworks); + } + + /** implement Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** + * implement Parcelable interface + * |flag| is ignored. + **/ + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeLong(mIntervalMs); + out.writeInt(mMin2gRssi); + out.writeInt(mMin5gRssi); + out.writeInt(mMin6gRssi); + out.writeTypedList(mPnoNetworks); + } + + /** implement Parcelable interface */ + @NonNull public static final Parcelable.Creator<PnoSettings> CREATOR = + new Parcelable.Creator<PnoSettings>() { + @Override + public PnoSettings createFromParcel(Parcel in) { + PnoSettings result = new PnoSettings(); + result.mIntervalMs = in.readLong(); + result.mMin2gRssi = in.readInt(); + result.mMin5gRssi = in.readInt(); + result.mMin6gRssi = in.readInt(); + + result.mPnoNetworks = new ArrayList<>(); + in.readTypedList(result.mPnoNetworks, PnoNetwork.CREATOR); + + return result; + } + + @Override + public PnoSettings[] newArray(int size) { + return new PnoSettings[size]; + } + }; +} diff --git a/wifi/java/src/android/net/wifi/nl80211/RadioChainInfo.java b/wifi/java/src/android/net/wifi/nl80211/RadioChainInfo.java new file mode 100644 index 000000000000..2c12163dc9a1 --- /dev/null +++ b/wifi/java/src/android/net/wifi/nl80211/RadioChainInfo.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.nl80211; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Objects; + +/** + * A class representing the radio chains of the Wi-Fi modems. Use to provide raw information about + * signals received on different radio chains. + * + * @hide + */ +@SystemApi +public final class RadioChainInfo implements Parcelable { + private static final String TAG = "RadioChainInfo"; + + /** @hide */ + @VisibleForTesting + public int chainId; + /** @hide */ + @VisibleForTesting + public int level; + + /** + * Return an identifier for this radio chain. This is an arbitrary ID which is consistent for + * the same device. + * + * @return The radio chain ID. + */ + public int getChainId() { + return chainId; + } + + /** + * Returns the detected signal level on this radio chain in dBm (aka RSSI). + * + * @return A signal level in dBm. + */ + public int getLevelDbm() { + return level; + } + + /** + * Construct a RadioChainInfo. + */ + public RadioChainInfo(int chainId, int level) { + this.chainId = chainId; + this.level = level; + } + + /** override comparator */ + @Override + public boolean equals(Object rhs) { + if (this == rhs) return true; + if (!(rhs instanceof RadioChainInfo)) { + return false; + } + RadioChainInfo chainInfo = (RadioChainInfo) rhs; + if (chainInfo == null) { + return false; + } + return chainId == chainInfo.chainId && level == chainInfo.level; + } + + /** override hash code */ + @Override + public int hashCode() { + return Objects.hash(chainId, level); + } + + + /** implement Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** + * implement Parcelable interface + * |flags| is ignored. + */ + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(chainId); + out.writeInt(level); + } + + /** implement Parcelable interface */ + @NonNull public static final Parcelable.Creator<RadioChainInfo> CREATOR = + new Parcelable.Creator<RadioChainInfo>() { + /** + * Caller is responsible for providing a valid parcel. + */ + @Override + public RadioChainInfo createFromParcel(Parcel in) { + return new RadioChainInfo(in.readInt(), in.readInt()); + } + + @Override + public RadioChainInfo[] newArray(int size) { + return new RadioChainInfo[size]; + } + }; +} diff --git a/wifi/java/src/android/net/wifi/nl80211/SingleScanSettings.java b/wifi/java/src/android/net/wifi/nl80211/SingleScanSettings.java new file mode 100644 index 000000000000..24b1854fbf6c --- /dev/null +++ b/wifi/java/src/android/net/wifi/nl80211/SingleScanSettings.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.nl80211; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Objects; + +/** + * SingleScanSettings for wificond + * + * @hide + */ +public class SingleScanSettings implements Parcelable { + private static final String TAG = "SingleScanSettings"; + + public int scanType; + public ArrayList<ChannelSettings> channelSettings; + public ArrayList<HiddenNetwork> hiddenNetworks; + + /** public constructor */ + public SingleScanSettings() { } + + /** override comparator */ + @Override + public boolean equals(Object rhs) { + if (this == rhs) return true; + if (!(rhs instanceof SingleScanSettings)) { + return false; + } + SingleScanSettings settings = (SingleScanSettings) rhs; + if (settings == null) { + return false; + } + return scanType == settings.scanType + && channelSettings.equals(settings.channelSettings) + && hiddenNetworks.equals(settings.hiddenNetworks); + } + + /** override hash code */ + @Override + public int hashCode() { + return Objects.hash(scanType, channelSettings, hiddenNetworks); + } + + + /** implement Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + private static boolean isValidScanType(int scanType) { + return scanType == IWifiScannerImpl.SCAN_TYPE_LOW_SPAN + || scanType == IWifiScannerImpl.SCAN_TYPE_LOW_POWER + || scanType == IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY; + } + + /** + * implement Parcelable interface + * |flags| is ignored. + */ + @Override + public void writeToParcel(Parcel out, int flags) { + if (!isValidScanType(scanType)) { + Log.wtf(TAG, "Invalid scan type " + scanType); + } + out.writeInt(scanType); + out.writeTypedList(channelSettings); + out.writeTypedList(hiddenNetworks); + } + + /** implement Parcelable interface */ + public static final Parcelable.Creator<SingleScanSettings> CREATOR = + new Parcelable.Creator<SingleScanSettings>() { + /** + * Caller is responsible for providing a valid parcel. + */ + @Override + public SingleScanSettings createFromParcel(Parcel in) { + SingleScanSettings result = new SingleScanSettings(); + result.scanType = in.readInt(); + if (!isValidScanType(result.scanType)) { + Log.wtf(TAG, "Invalid scan type " + result.scanType); + } + result.channelSettings = new ArrayList<ChannelSettings>(); + in.readTypedList(result.channelSettings, ChannelSettings.CREATOR); + result.hiddenNetworks = new ArrayList<HiddenNetwork>(); + in.readTypedList(result.hiddenNetworks, HiddenNetwork.CREATOR); + if (in.dataAvail() != 0) { + Log.e(TAG, "Found trailing data after parcel parsing."); + } + return result; + } + + @Override + public SingleScanSettings[] newArray(int size) { + return new SingleScanSettings[size]; + } + }; +} diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java new file mode 100644 index 000000000000..4116234c4c8d --- /dev/null +++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java @@ -0,0 +1,1287 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi.nl80211; + +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.app.AlarmManager; +import android.content.Context; +import android.net.wifi.SoftApInfo; +import android.net.wifi.WifiAnnotations; +import android.net.wifi.WifiScanner; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework - used + * to encapsulate the Wi-Fi 80211nl management interface. The + * interface is only for use by the Wi-Fi framework and access is protected by SELinux permissions. + * + * @hide + */ +@SystemApi +@SystemService(Context.WIFI_NL80211_SERVICE) +public class WifiNl80211Manager { + private static final String TAG = "WifiNl80211Manager"; + private boolean mVerboseLoggingEnabled = false; + + /** + * The {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} + * timeout, in milliseconds, after which + * {@link SendMgmtFrameCallback#onFailure(int)} will be called with reason + * {@link #SEND_MGMT_FRAME_ERROR_TIMEOUT}. + */ + private static final int SEND_MGMT_FRAME_TIMEOUT_MS = 1000; + + private static final String TIMEOUT_ALARM_TAG = TAG + " Send Management Frame Timeout"; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SCAN_TYPE_"}, + value = {SCAN_TYPE_SINGLE_SCAN, + SCAN_TYPE_PNO_SCAN}) + public @interface ScanResultType {} + + /** + * Specifies a scan type: single scan initiated by the framework. Can be used in + * {@link #getScanResults(String, int)} to specify the type of scan result to fetch. + */ + public static final int SCAN_TYPE_SINGLE_SCAN = 0; + + /** + * Specifies a scan type: PNO scan. Can be used in {@link #getScanResults(String, int)} to + * specify the type of scan result to fetch. + */ + public static final int SCAN_TYPE_PNO_SCAN = 1; + + private AlarmManager mAlarmManager; + private Handler mEventHandler; + + // Cached wificond binder handlers. + private IWificond mWificond; + private HashMap<String, IClientInterface> mClientInterfaces = new HashMap<>(); + private HashMap<String, IApInterface> mApInterfaces = new HashMap<>(); + private HashMap<String, IWifiScannerImpl> mWificondScanners = new HashMap<>(); + private HashMap<String, IScanEvent> mScanEventHandlers = new HashMap<>(); + private HashMap<String, IPnoScanEvent> mPnoScanEventHandlers = new HashMap<>(); + private HashMap<String, IApInterfaceEventCallback> mApInterfaceListeners = new HashMap<>(); + private Runnable mDeathEventHandler; + /** + * Ensures that no more than one sendMgmtFrame operation runs concurrently. + */ + private AtomicBoolean mSendMgmtFrameInProgress = new AtomicBoolean(false); + + /** + * Interface used when waiting for scans to be completed (with results). + */ + public interface ScanEventCallback { + /** + * Called when scan results are available. Scans results should then be obtained from + * {@link #getScanResults(String, int)}. + */ + void onScanResultReady(); + + /** + * Called when a scan has failed. + */ + void onScanFailed(); + } + + /** + * Interface for a callback to provide information about PNO scan request requested with + * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}. Note that the + * callback are for the status of the request - not the scan itself. The results of the scan + * are returned with {@link ScanEventCallback}. + */ + public interface PnoScanRequestCallback { + /** + * Called when a PNO scan request has been successfully submitted. + */ + void onPnoRequestSucceeded(); + + /** + * Called when a PNO scan request fails. + */ + void onPnoRequestFailed(); + } + + private class ScanEventHandler extends IScanEvent.Stub { + private Executor mExecutor; + private ScanEventCallback mCallback; + + ScanEventHandler(@NonNull Executor executor, @NonNull ScanEventCallback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void OnScanResultReady() { + Log.d(TAG, "Scan result ready event"); + Binder.clearCallingIdentity(); + mExecutor.execute(() -> mCallback.onScanResultReady()); + } + + @Override + public void OnScanFailed() { + Log.d(TAG, "Scan failed event"); + Binder.clearCallingIdentity(); + mExecutor.execute(() -> mCallback.onScanFailed()); + } + } + + /** + * Result of a signal poll requested using {@link #signalPoll(String)}. + */ + public static class SignalPollResult { + /** @hide */ + public SignalPollResult(int currentRssiDbm, int txBitrateMbps, int rxBitrateMbps, + int associationFrequencyMHz) { + this.currentRssiDbm = currentRssiDbm; + this.txBitrateMbps = txBitrateMbps; + this.rxBitrateMbps = rxBitrateMbps; + this.associationFrequencyMHz = associationFrequencyMHz; + } + + /** + * RSSI value in dBM. + */ + public final int currentRssiDbm; + + /** + * Transmission bit rate in Mbps. + */ + public final int txBitrateMbps; + + /** + * Last received packet bit rate in Mbps. + */ + public final int rxBitrateMbps; + + /** + * Association frequency in MHz. + */ + public final int associationFrequencyMHz; + } + + /** + * Transmission counters obtained using {@link #getTxPacketCounters(String)}. + */ + public static class TxPacketCounters { + /** @hide */ + public TxPacketCounters(int txPacketSucceeded, int txPacketFailed) { + this.txPacketSucceeded = txPacketSucceeded; + this.txPacketFailed = txPacketFailed; + } + + /** + * Number of successfully transmitted packets. + */ + public final int txPacketSucceeded; + + /** + * Number of packet transmission failures. + */ + public final int txPacketFailed; + } + + /** + * Callbacks for SoftAp interface registered using + * {@link #registerApCallback(String, Executor, SoftApCallback)}. + */ + public interface SoftApCallback { + /** + * Invoked when there is a fatal failure and the SoftAp is shutdown. + */ + void onFailure(); + + /** + * Invoked when there is a change in the associated station (STA). + * @param client Information about the client whose status has changed. + * @param isConnected Indication as to whether the client is connected (true), or + * disconnected (false). + */ + void onConnectedClientsChanged(@NonNull NativeWifiClient client, boolean isConnected); + + /** + * Invoked when a channel switch event happens - i.e. the SoftAp is moved to a different + * channel. Also called on initial registration. + * @param frequencyMhz 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. + */ + void onSoftApChannelSwitched(int frequencyMhz, @WifiAnnotations.Bandwidth int bandwidth); + } + + /** + * Callback to notify the results of a + * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} call. + * Note: no callbacks will be triggered if the interface dies while sending a frame. + */ + public interface SendMgmtFrameCallback { + /** + * Called when the management frame was successfully sent and ACKed by the recipient. + * @param elapsedTimeMs The elapsed time between when the management frame was sent and when + * the ACK was processed, in milliseconds, as measured by wificond. + * This includes the time that the send frame spent queuing before it + * was sent, any firmware retries, and the time the received ACK spent + * queuing before it was processed. + */ + void onAck(int elapsedTimeMs); + + /** + * Called when the send failed. + * @param reason The error code for the failure. + */ + void onFailure(@SendMgmtFrameError int reason); + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SEND_MGMT_FRAME_ERROR_"}, + value = {SEND_MGMT_FRAME_ERROR_UNKNOWN, + SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED, + SEND_MGMT_FRAME_ERROR_NO_ACK, + SEND_MGMT_FRAME_ERROR_TIMEOUT, + SEND_MGMT_FRAME_ERROR_ALREADY_STARTED}) + public @interface SendMgmtFrameError {} + + // Send management frame error codes + + /** + * Unknown error occurred during call to + * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}. + */ + public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1; + + /** + * Specifying the MCS rate in + * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} is not + * supported by this device. + */ + public static final int SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED = 2; + + /** + * Driver reported that no ACK was received for the frame transmitted using + * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}. + */ + public static final int SEND_MGMT_FRAME_ERROR_NO_ACK = 3; + + /** + * Error code for when the driver fails to report on the status of the frame sent by + * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} + * after {@link #SEND_MGMT_FRAME_TIMEOUT_MS} milliseconds. + */ + public static final int SEND_MGMT_FRAME_ERROR_TIMEOUT = 4; + + /** + * An existing call to + * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} + * is in progress. Another frame cannot be sent until the first call completes. + */ + public static final int SEND_MGMT_FRAME_ERROR_ALREADY_STARTED = 5; + + /** @hide */ + public WifiNl80211Manager(Context context) { + mAlarmManager = context.getSystemService(AlarmManager.class); + mEventHandler = new Handler(context.getMainLooper()); + } + + /** @hide */ + @VisibleForTesting + public WifiNl80211Manager(Context context, IWificond wificond) { + this(context); + mWificond = wificond; + } + + private class PnoScanEventHandler extends IPnoScanEvent.Stub { + private Executor mExecutor; + private ScanEventCallback mCallback; + + PnoScanEventHandler(@NonNull Executor executor, @NonNull ScanEventCallback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void OnPnoNetworkFound() { + Log.d(TAG, "Pno scan result event"); + Binder.clearCallingIdentity(); + mExecutor.execute(() -> mCallback.onScanResultReady()); + } + + @Override + public void OnPnoScanFailed() { + Log.d(TAG, "Pno Scan failed event"); + Binder.clearCallingIdentity(); + mExecutor.execute(() -> mCallback.onScanFailed()); + } + } + + /** + * Listener for AP Interface events. + */ + private class ApInterfaceEventCallback extends IApInterfaceEventCallback.Stub { + private Executor mExecutor; + private SoftApCallback mSoftApListener; + + ApInterfaceEventCallback(Executor executor, SoftApCallback listener) { + mExecutor = executor; + mSoftApListener = listener; + } + + @Override + public void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected) { + if (mVerboseLoggingEnabled) { + Log.d(TAG, "onConnectedClientsChanged called with " + + client.getMacAddress() + " isConnected: " + isConnected); + } + + Binder.clearCallingIdentity(); + mExecutor.execute(() -> mSoftApListener.onConnectedClientsChanged(client, isConnected)); + } + + @Override + public void onSoftApChannelSwitched(int frequency, int bandwidth) { + Binder.clearCallingIdentity(); + mExecutor.execute(() -> mSoftApListener.onSoftApChannelSwitched(frequency, + toFrameworkBandwidth(bandwidth))); + } + + private @WifiAnnotations.Bandwidth int toFrameworkBandwidth(int bandwidth) { + switch(bandwidth) { + case IApInterfaceEventCallback.BANDWIDTH_INVALID: + return SoftApInfo.CHANNEL_WIDTH_INVALID; + case IApInterfaceEventCallback.BANDWIDTH_20_NOHT: + return SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT; + case IApInterfaceEventCallback.BANDWIDTH_20: + return SoftApInfo.CHANNEL_WIDTH_20MHZ; + case IApInterfaceEventCallback.BANDWIDTH_40: + return SoftApInfo.CHANNEL_WIDTH_40MHZ; + case IApInterfaceEventCallback.BANDWIDTH_80: + return SoftApInfo.CHANNEL_WIDTH_80MHZ; + case IApInterfaceEventCallback.BANDWIDTH_80P80: + return SoftApInfo.CHANNEL_WIDTH_80MHZ_PLUS_MHZ; + case IApInterfaceEventCallback.BANDWIDTH_160: + return SoftApInfo.CHANNEL_WIDTH_160MHZ; + default: + return SoftApInfo.CHANNEL_WIDTH_INVALID; + } + } + } + + /** + * Callback triggered by wificond. + */ + private class SendMgmtFrameEvent extends ISendMgmtFrameEvent.Stub { + private Executor mExecutor; + private SendMgmtFrameCallback mCallback; + private AlarmManager.OnAlarmListener mTimeoutCallback; + /** + * ensures that mCallback is only called once + */ + private boolean mWasCalled; + + private void runIfFirstCall(Runnable r) { + if (mWasCalled) return; + mWasCalled = true; + + mSendMgmtFrameInProgress.set(false); + r.run(); + } + + SendMgmtFrameEvent(@NonNull Executor executor, @NonNull SendMgmtFrameCallback callback) { + mExecutor = executor; + mCallback = callback; + // called in main thread + mTimeoutCallback = () -> runIfFirstCall(() -> { + if (mVerboseLoggingEnabled) { + Log.e(TAG, "Timed out waiting for ACK"); + } + Binder.clearCallingIdentity(); + mExecutor.execute(() -> mCallback.onFailure(SEND_MGMT_FRAME_ERROR_TIMEOUT)); + }); + mWasCalled = false; + + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + SEND_MGMT_FRAME_TIMEOUT_MS, + TIMEOUT_ALARM_TAG, mTimeoutCallback, mEventHandler); + } + + // called in binder thread + @Override + public void OnAck(int elapsedTimeMs) { + // post to main thread + mEventHandler.post(() -> runIfFirstCall(() -> { + mAlarmManager.cancel(mTimeoutCallback); + Binder.clearCallingIdentity(); + mExecutor.execute(() -> mCallback.onAck(elapsedTimeMs)); + })); + } + + // called in binder thread + @Override + public void OnFailure(int reason) { + // post to main thread + mEventHandler.post(() -> runIfFirstCall(() -> { + mAlarmManager.cancel(mTimeoutCallback); + Binder.clearCallingIdentity(); + mExecutor.execute(() -> mCallback.onFailure(reason)); + })); + } + } + + /** + * Called by the binder subsystem upon remote object death. + * Invoke all the register death handlers and clear state. + * @hide + */ + @VisibleForTesting + public void binderDied() { + mEventHandler.post(() -> { + Log.e(TAG, "Wificond died!"); + clearState(); + // Invalidate the global wificond handle on death. Will be refreshed + // on the next setup call. + mWificond = null; + if (mDeathEventHandler != null) { + mDeathEventHandler.run(); + } + }); + } + + /** + * Enable or disable verbose logging of the WifiNl80211Manager module. + * @param enable True to enable verbose logging. False to disable verbose logging. + */ + public void enableVerboseLogging(boolean enable) { + mVerboseLoggingEnabled = enable; + } + + /** + * Register a death notification for the WifiNl80211Manager which acts as a proxy for the + * wificond daemon (i.e. the death listener will be called when and if the wificond daemon + * dies). + * + * @param deathEventHandler A {@link Runnable} to be called whenever the wificond daemon dies. + */ + public void setOnServiceDeadCallback(@NonNull Runnable deathEventHandler) { + if (mDeathEventHandler != null) { + Log.e(TAG, "Death handler already present"); + } + mDeathEventHandler = deathEventHandler; + } + + /** + * Helper method to retrieve the global wificond handle and register for + * death notifications. + */ + private boolean retrieveWificondAndRegisterForDeath() { + if (mWificond != null) { + if (mVerboseLoggingEnabled) { + Log.d(TAG, "Wificond handle already retrieved"); + } + // We already have a wificond handle. + return true; + } + IBinder binder = ServiceManager.getService(Context.WIFI_NL80211_SERVICE); + mWificond = IWificond.Stub.asInterface(binder); + if (mWificond == null) { + Log.e(TAG, "Failed to get reference to wificond"); + return false; + } + try { + mWificond.asBinder().linkToDeath(() -> binderDied(), 0); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register death notification for wificond"); + // The remote has already died. + return false; + } + return true; + } + + /** + * Set up an interface for client (STA) mode. + * + * @param ifaceName Name of the interface to configure. + * @param executor The Executor on which to execute the callbacks. + * @param scanCallback A callback for framework initiated scans. + * @param pnoScanCallback A callback for PNO (offloaded) scans. + * @return true on success. + */ + public boolean setupInterfaceForClientMode(@NonNull String ifaceName, + @NonNull @CallbackExecutor Executor executor, + @NonNull ScanEventCallback scanCallback, @NonNull ScanEventCallback pnoScanCallback) { + Log.d(TAG, "Setting up interface for client mode"); + if (!retrieveWificondAndRegisterForDeath()) { + return false; + } + + if (scanCallback == null || pnoScanCallback == null || executor == null) { + Log.e(TAG, "setupInterfaceForClientMode invoked with null callbacks"); + return false; + } + + IClientInterface clientInterface = null; + try { + clientInterface = mWificond.createClientInterface(ifaceName); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to get IClientInterface due to remote exception"); + return false; + } + + if (clientInterface == null) { + Log.e(TAG, "Could not get IClientInterface instance from wificond"); + return false; + } + Binder.allowBlocking(clientInterface.asBinder()); + + // Refresh Handlers + mClientInterfaces.put(ifaceName, clientInterface); + try { + IWifiScannerImpl wificondScanner = clientInterface.getWifiScannerImpl(); + if (wificondScanner == null) { + Log.e(TAG, "Failed to get WificondScannerImpl"); + return false; + } + mWificondScanners.put(ifaceName, wificondScanner); + Binder.allowBlocking(wificondScanner.asBinder()); + ScanEventHandler scanEventHandler = new ScanEventHandler(executor, scanCallback); + mScanEventHandlers.put(ifaceName, scanEventHandler); + wificondScanner.subscribeScanEvents(scanEventHandler); + PnoScanEventHandler pnoScanEventHandler = new PnoScanEventHandler(executor, + pnoScanCallback); + mPnoScanEventHandlers.put(ifaceName, pnoScanEventHandler); + wificondScanner.subscribePnoScanEvents(pnoScanEventHandler); + } catch (RemoteException e) { + Log.e(TAG, "Failed to refresh wificond scanner due to remote exception"); + } + + return true; + } + + /** + * Tear down a specific client (STA) interface configured using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}. + * + * @param ifaceName Name of the interface to tear down. + * @return Returns true on success, false on failure (e.g. when called before an interface was + * set up). + */ + public boolean tearDownClientInterface(@NonNull String ifaceName) { + if (getClientInterface(ifaceName) == null) { + Log.e(TAG, "No valid wificond client interface handler"); + return false; + } + try { + IWifiScannerImpl scannerImpl = mWificondScanners.get(ifaceName); + if (scannerImpl != null) { + scannerImpl.unsubscribeScanEvents(); + scannerImpl.unsubscribePnoScanEvents(); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to unsubscribe wificond scanner due to remote exception"); + return false; + } + + if (mWificond == null) { + Log.e(TAG, "Reference to wifiCond is null"); + return false; + } + + boolean success; + try { + success = mWificond.tearDownClientInterface(ifaceName); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to teardown client interface due to remote exception"); + return false; + } + if (!success) { + Log.e(TAG, "Failed to teardown client interface"); + return false; + } + + mClientInterfaces.remove(ifaceName); + mWificondScanners.remove(ifaceName); + mScanEventHandlers.remove(ifaceName); + mPnoScanEventHandlers.remove(ifaceName); + return true; + } + + /** + * Set up interface as a Soft AP. + * + * @param ifaceName Name of the interface to configure. + * @return true on success. + */ + public boolean setupInterfaceForSoftApMode(@NonNull String ifaceName) { + Log.d(TAG, "Setting up interface for soft ap mode"); + if (!retrieveWificondAndRegisterForDeath()) { + return false; + } + + IApInterface apInterface = null; + try { + apInterface = mWificond.createApInterface(ifaceName); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to get IApInterface due to remote exception"); + return false; + } + + if (apInterface == null) { + Log.e(TAG, "Could not get IApInterface instance from wificond"); + return false; + } + Binder.allowBlocking(apInterface.asBinder()); + + // Refresh Handlers + mApInterfaces.put(ifaceName, apInterface); + return true; + } + + /** + * Tear down a Soft AP interface configured using + * {@link #setupInterfaceForSoftApMode(String)}. + * + * @param ifaceName Name of the interface to tear down. + * @return Returns true on success, false on failure (e.g. when called before an interface was + * set up). + */ + public boolean tearDownSoftApInterface(@NonNull String ifaceName) { + if (getApInterface(ifaceName) == null) { + Log.e(TAG, "No valid wificond ap interface handler"); + return false; + } + + if (mWificond == null) { + Log.e(TAG, "Reference to wifiCond is null"); + return false; + } + + boolean success; + try { + success = mWificond.tearDownApInterface(ifaceName); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to teardown AP interface due to remote exception"); + return false; + } + if (!success) { + Log.e(TAG, "Failed to teardown AP interface"); + return false; + } + mApInterfaces.remove(ifaceName); + mApInterfaceListeners.remove(ifaceName); + return true; + } + + /** + * Tear down all interfaces, whether clients (STA) or Soft AP. + * + * @return Returns true on success. + */ + public boolean tearDownInterfaces() { + Log.d(TAG, "tearing down interfaces in wificond"); + // Explicitly refresh the wificodn handler because |tearDownInterfaces()| + // could be used to cleanup before we setup any interfaces. + if (!retrieveWificondAndRegisterForDeath()) { + return false; + } + + try { + for (Map.Entry<String, IWifiScannerImpl> entry : mWificondScanners.entrySet()) { + entry.getValue().unsubscribeScanEvents(); + entry.getValue().unsubscribePnoScanEvents(); + } + mWificond.tearDownInterfaces(); + clearState(); + return true; + } catch (RemoteException e) { + Log.e(TAG, "Failed to tear down interfaces due to remote exception"); + } + + return false; + } + + /** Helper function to look up the interface handle using name */ + private IClientInterface getClientInterface(@NonNull String ifaceName) { + return mClientInterfaces.get(ifaceName); + } + + /** + * Request signal polling. + * + * @param ifaceName Name of the interface on which to poll. The interface must have been + * already set up using + *{@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @return A {@link SignalPollResult} object containing interface statistics, or a null on + * error (e.g. the interface hasn't been set up yet). + */ + @Nullable public SignalPollResult signalPoll(@NonNull String ifaceName) { + IClientInterface iface = getClientInterface(ifaceName); + if (iface == null) { + Log.e(TAG, "No valid wificond client interface handler"); + return null; + } + + int[] resultArray; + try { + resultArray = iface.signalPoll(); + if (resultArray == null || resultArray.length != 4) { + Log.e(TAG, "Invalid signal poll result from wificond"); + return null; + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to do signal polling due to remote exception"); + return null; + } + return new SignalPollResult(resultArray[0], resultArray[1], resultArray[3], resultArray[2]); + } + + /** + * Get current transmit (Tx) packet counters of the specified interface. The interface must + * have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @param ifaceName Name of the interface. + * @return {@link TxPacketCounters} of the current interface or null on error (e.g. when + * called before the interface has been set up). + */ + @Nullable public TxPacketCounters getTxPacketCounters(@NonNull String ifaceName) { + IClientInterface iface = getClientInterface(ifaceName); + if (iface == null) { + Log.e(TAG, "No valid wificond client interface handler"); + return null; + } + + int[] resultArray; + try { + resultArray = iface.getPacketCounters(); + if (resultArray == null || resultArray.length != 2) { + Log.e(TAG, "Invalid signal poll result from wificond"); + return null; + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to do signal polling due to remote exception"); + return null; + } + return new TxPacketCounters(resultArray[0], resultArray[1]); + } + + /** Helper function to look up the scanner impl handle using name */ + private IWifiScannerImpl getScannerImpl(@NonNull String ifaceName) { + return mWificondScanners.get(ifaceName); + } + + /** + * Fetch the latest scan results of the indicated type for the specified interface. Note that + * this method fetches the latest results - it does not initiate a scan. Initiating a scan can + * be done using {@link #startScan(String, int, Set, List)} or + * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @param ifaceName Name of the interface. + * @param scanType The type of scan result to be returned, can be + * {@link #SCAN_TYPE_SINGLE_SCAN} or {@link #SCAN_TYPE_PNO_SCAN}. + * @return Returns an array of {@link NativeScanResult} or an empty array on failure (e.g. when + * called before the interface has been set up). + */ + @NonNull public List<NativeScanResult> getScanResults(@NonNull String ifaceName, + @ScanResultType int scanType) { + IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName); + if (scannerImpl == null) { + Log.e(TAG, "No valid wificond scanner interface handler"); + return new ArrayList<>(); + } + List<NativeScanResult> results = null; + try { + if (scanType == SCAN_TYPE_SINGLE_SCAN) { + results = Arrays.asList(scannerImpl.getScanResults()); + } else { + results = Arrays.asList(scannerImpl.getPnoScanResults()); + } + } catch (RemoteException e1) { + Log.e(TAG, "Failed to create ScanDetail ArrayList"); + } + if (results == null) { + results = new ArrayList<>(); + } + if (mVerboseLoggingEnabled) { + Log.d(TAG, "get " + results.size() + " scan results from wificond"); + } + + return results; + } + + /** + * Return scan type for the parcelable {@link SingleScanSettings} + */ + private static int getScanType(@WifiAnnotations.ScanType int scanType) { + switch (scanType) { + case WifiScanner.SCAN_TYPE_LOW_LATENCY: + return IWifiScannerImpl.SCAN_TYPE_LOW_SPAN; + case WifiScanner.SCAN_TYPE_LOW_POWER: + return IWifiScannerImpl.SCAN_TYPE_LOW_POWER; + case WifiScanner.SCAN_TYPE_HIGH_ACCURACY: + return IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY; + default: + throw new IllegalArgumentException("Invalid scan type " + scanType); + } + } + + /** + * Start a scan using the specified parameters. A scan is an asynchronous operation. The + * result of the operation is returned in the {@link ScanEventCallback} registered when + * setting up an interface using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}. + * The latest scans can be obtained using {@link #getScanResults(String, int)} and using a + * {@link #SCAN_TYPE_SINGLE_SCAN} for the {@code scanType}. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @param ifaceName Name of the interface on which to initiate the scan. + * @param scanType Type of scan to perform, can be any of + * {@link WifiScanner#SCAN_TYPE_HIGH_ACCURACY}, {@link WifiScanner#SCAN_TYPE_LOW_POWER}, or + * {@link WifiScanner#SCAN_TYPE_LOW_LATENCY}. + * @param freqs list of frequencies to scan for, if null scan all supported channels. + * @param hiddenNetworkSSIDs List of hidden networks to be scanned for, a null indicates that + * no hidden frequencies will be scanned for. + * @return Returns true on success, false on failure (e.g. when called before the interface + * has been set up). + */ + public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType, + @Nullable Set<Integer> freqs, @Nullable List<byte[]> hiddenNetworkSSIDs) { + IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName); + if (scannerImpl == null) { + Log.e(TAG, "No valid wificond scanner interface handler"); + return false; + } + SingleScanSettings settings = new SingleScanSettings(); + try { + settings.scanType = getScanType(scanType); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Invalid scan type ", e); + return false; + } + settings.channelSettings = new ArrayList<>(); + settings.hiddenNetworks = new ArrayList<>(); + + if (freqs != null) { + for (Integer freq : freqs) { + ChannelSettings channel = new ChannelSettings(); + channel.frequency = freq; + settings.channelSettings.add(channel); + } + } + if (hiddenNetworkSSIDs != null) { + for (byte[] ssid : hiddenNetworkSSIDs) { + HiddenNetwork network = new HiddenNetwork(); + network.ssid = ssid; + + // settings.hiddenNetworks is expected to be very small, so this shouldn't cause + // any performance issues. + if (!settings.hiddenNetworks.contains(network)) { + settings.hiddenNetworks.add(network); + } + } + } + + try { + return scannerImpl.scan(settings); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to request scan due to remote exception"); + } + return false; + } + + /** + * Request a PNO (Preferred Network Offload). The offload request and the scans are asynchronous + * operations. The result of the request are returned in the {@code callback} parameter which + * is an {@link PnoScanRequestCallback}. The scan results are are return in the + * {@link ScanEventCallback} which is registered when setting up an interface using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}. + * The latest PNO scans can be obtained using {@link #getScanResults(String, int)} with the + * {@code scanType} set to {@link #SCAN_TYPE_PNO_SCAN}. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @param ifaceName Name of the interface on which to request a PNO. + * @param pnoSettings PNO scan configuration. + * @param executor The Executor on which to execute the callback. + * @param callback Callback for the results of the offload request. + * @return true on success, false on failure (e.g. when called before the interface has been set + * up). + */ + public boolean startPnoScan(@NonNull String ifaceName, @NonNull PnoSettings pnoSettings, + @NonNull @CallbackExecutor Executor executor, + @NonNull PnoScanRequestCallback callback) { + IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName); + if (scannerImpl == null) { + Log.e(TAG, "No valid wificond scanner interface handler"); + return false; + } + + if (callback == null || executor == null) { + Log.e(TAG, "startPnoScan called with a null callback"); + return false; + } + + try { + boolean success = scannerImpl.startPnoScan(pnoSettings); + if (success) { + executor.execute(callback::onPnoRequestSucceeded); + } else { + executor.execute(callback::onPnoRequestFailed); + } + return success; + } catch (RemoteException e1) { + Log.e(TAG, "Failed to start pno scan due to remote exception"); + } + return false; + } + + /** + * Stop PNO scan configured with + * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @param ifaceName Name of the interface on which the PNO scan was configured. + * @return true on success, false on failure (e.g. when called before the interface has been + * set up). + */ + public boolean stopPnoScan(@NonNull String ifaceName) { + IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName); + if (scannerImpl == null) { + Log.e(TAG, "No valid wificond scanner interface handler"); + return false; + } + try { + return scannerImpl.stopPnoScan(); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to stop pno scan due to remote exception"); + } + return false; + } + + /** + * Abort ongoing single scan started with {@link #startScan(String, int, Set, List)}. No failure + * callback, e.g. {@link ScanEventCallback#onScanFailed()}, is triggered by this operation. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. If the interface has not been set up then + * this method has no impact. + * + * @param ifaceName Name of the interface on which the scan was started. + */ + public void abortScan(@NonNull String ifaceName) { + IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName); + if (scannerImpl == null) { + Log.e(TAG, "No valid wificond scanner interface handler"); + return; + } + try { + scannerImpl.abortScan(); + } catch (RemoteException e1) { + Log.e(TAG, "Failed to request abortScan due to remote exception"); + } + } + + /** + * Query the list of valid frequencies (in MHz) 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 WifiScanner#WIFI_BAND_24_GHZ}, + * {@link WifiScanner#WIFI_BAND_5_GHZ}, + * {@link WifiScanner#WIFI_BAND_5_GHZ_DFS_ONLY}, + * {@link WifiScanner#WIFI_BAND_6_GHZ} + * @return frequencies vector of valid frequencies (MHz), or an empty array for error. + * @throws IllegalArgumentException if band is not recognized. + */ + public @NonNull int[] getChannelsMhzForBand(@WifiAnnotations.WifiBandBasic int band) { + if (mWificond == null) { + Log.e(TAG, "No valid wificond scanner interface handler"); + return new int[0]; + } + int[] result = null; + try { + switch (band) { + case WifiScanner.WIFI_BAND_24_GHZ: + result = mWificond.getAvailable2gChannels(); + break; + case WifiScanner.WIFI_BAND_5_GHZ: + result = mWificond.getAvailable5gNonDFSChannels(); + break; + case WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY: + result = mWificond.getAvailableDFSChannels(); + break; + case WifiScanner.WIFI_BAND_6_GHZ: + result = mWificond.getAvailable6gChannels(); + break; + default: + throw new IllegalArgumentException("unsupported band " + band); + } + } catch (RemoteException e1) { + Log.e(TAG, "Failed to request getChannelsForBand due to remote exception"); + } + if (result == null) { + result = new int[0]; + } + return result; + } + + /** Helper function to look up the interface handle using name */ + private IApInterface getApInterface(@NonNull String ifaceName) { + return mApInterfaces.get(ifaceName); + } + + /** + * Get the device phy capabilities for a given interface. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @return DeviceWiphyCapabilities or null on error (e.g. when called on an interface which has + * not been set up). + */ + @Nullable public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) { + if (mWificond == null) { + Log.e(TAG, "Can not query for device wiphy capabilities at this time"); + return null; + } + + try { + return mWificond.getDeviceWiphyCapabilities(ifaceName); + } catch (RemoteException e) { + return null; + } + } + + /** + * Register the provided callback handler for SoftAp events. The interface must first be created + * using {@link #setupInterfaceForSoftApMode(String)}. The callback registration is valid until + * the interface is deleted using {@link #tearDownSoftApInterface(String)} (no deregistration + * method is provided). + * <p> + * Note that only one callback can be registered at a time - any registration overrides previous + * registrations. + * + * @param ifaceName Name of the interface on which to register the callback. + * @param executor The Executor on which to execute the callbacks. + * @param callback Callback for AP events. + * @return true on success, false on failure (e.g. when called on an interface which has not + * been set up). + */ + public boolean registerApCallback(@NonNull String ifaceName, + @NonNull @CallbackExecutor Executor executor, + @NonNull SoftApCallback callback) { + IApInterface iface = getApInterface(ifaceName); + if (iface == null) { + Log.e(TAG, "No valid ap interface handler"); + return false; + } + + if (callback == null || executor == null) { + Log.e(TAG, "registerApCallback called with a null callback"); + return false; + } + + try { + IApInterfaceEventCallback wificondCallback = new ApInterfaceEventCallback(executor, + callback); + mApInterfaceListeners.put(ifaceName, wificondCallback); + boolean success = iface.registerCallback(wificondCallback); + if (!success) { + Log.e(TAG, "Failed to register ap callback."); + return false; + } + } catch (RemoteException e) { + Log.e(TAG, "Exception in registering AP callback: " + e); + return false; + } + return true; + } + + /** + * Send a management frame on the specified interface at the specified rate. Useful for probing + * the link with arbitrary frames. + * + * Note: The interface must have been already set up using + * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)} + * or {@link #setupInterfaceForSoftApMode(String)}. + * + * @param ifaceName The interface on which to send the frame. + * @param frame The raw byte array of the management frame to tramit. + * @param mcs The MCS (modulation and coding scheme), i.e. rate, at which to transmit the + * frame. Specified per IEEE 802.11. + * @param executor The Executor on which to execute the callbacks. + * @param callback A {@link SendMgmtFrameCallback} callback for results of the operation. + */ + public void sendMgmtFrame(@NonNull String ifaceName, @NonNull byte[] frame, int mcs, + @NonNull @CallbackExecutor Executor executor, + @NonNull SendMgmtFrameCallback callback) { + + if (callback == null || executor == null) { + Log.e(TAG, "callback cannot be null!"); + return; + } + + if (frame == null) { + Log.e(TAG, "frame cannot be null!"); + executor.execute(() -> callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN)); + return; + } + + // TODO (b/112029045) validate mcs + IClientInterface clientInterface = getClientInterface(ifaceName); + if (clientInterface == null) { + Log.e(TAG, "No valid wificond client interface handler"); + executor.execute(() -> callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN)); + return; + } + + if (!mSendMgmtFrameInProgress.compareAndSet(false, true)) { + Log.e(TAG, "An existing management frame transmission is in progress!"); + executor.execute(() -> callback.onFailure(SEND_MGMT_FRAME_ERROR_ALREADY_STARTED)); + return; + } + + SendMgmtFrameEvent sendMgmtFrameEvent = new SendMgmtFrameEvent(executor, callback); + try { + clientInterface.SendMgmtFrame(frame, sendMgmtFrameEvent, mcs); + } catch (RemoteException e) { + Log.e(TAG, "Exception while starting link probe: " + e); + // Call sendMgmtFrameEvent.OnFailure() instead of callback.onFailure() so that + // sendMgmtFrameEvent can clean up internal state, such as cancelling the timer. + sendMgmtFrameEvent.OnFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN); + } + } + + /** + * Clear all internal handles. + */ + private void clearState() { + // Refresh handlers + mClientInterfaces.clear(); + mWificondScanners.clear(); + mPnoScanEventHandlers.clear(); + mScanEventHandlers.clear(); + mApInterfaces.clear(); + mApInterfaceListeners.clear(); + mSendMgmtFrameInProgress.set(false); + } + + /** + * OEM parsed security type + */ + public static class OemSecurityType { + /** The protocol defined in {@link android.net.wifi.WifiAnnotations.Protocol}. */ + public final @WifiAnnotations.Protocol int protocol; + /** + * Supported key management types defined + * in {@link android.net.wifi.WifiAnnotations.KeyMgmt}. + */ + @NonNull public final List<Integer> keyManagement; + /** + * Supported pairwise cipher types defined + * in {@link android.net.wifi.WifiAnnotations.Cipher}. + */ + @NonNull public final List<Integer> pairwiseCipher; + /** The group cipher type defined in {@link android.net.wifi.WifiAnnotations.Cipher}. */ + public final @WifiAnnotations.Cipher int groupCipher; + /** + * Default constructor for OemSecurityType + * + * @param protocol The protocol defined in + * {@link android.net.wifi.WifiAnnotations.Protocol}. + * @param keyManagement Supported key management types defined + * in {@link android.net.wifi.WifiAnnotations.KeyMgmt}. + * @param pairwiseCipher Supported pairwise cipher types defined + * in {@link android.net.wifi.WifiAnnotations.Cipher}. + * @param groupCipher The group cipher type defined + * in {@link android.net.wifi.WifiAnnotations.Cipher}. + */ + public OemSecurityType( + @WifiAnnotations.Protocol int protocol, + @NonNull List<Integer> keyManagement, + @NonNull List<Integer> pairwiseCipher, + @WifiAnnotations.Cipher int groupCipher) { + this.protocol = protocol; + this.keyManagement = (keyManagement != null) + ? keyManagement : new ArrayList<Integer>(); + this.pairwiseCipher = (pairwiseCipher != null) + ? pairwiseCipher : new ArrayList<Integer>(); + this.groupCipher = groupCipher; + } + } + + /** + * OEM information element parser for security types not parsed by the framework. + * + * The OEM method should use the method inputs {@code id}, {@code idExt}, and {@code bytes} + * to perform the parsing. The method should place the results in an OemSecurityType objct. + * + * @param id The information element id. + * @param idExt The information element extension id. This is valid only when id is + * the extension id, {@code 255}. + * @param bytes The raw bytes of information element data, 'Element ID' and 'Length' are + * stripped off already. + * @return an OemSecurityType object if this IE is parsed successfully, null otherwise. + */ + @Nullable public static OemSecurityType parseOemSecurityTypeElement( + int id, + int idExt, + @NonNull byte[] bytes) { + return null; + } +} |