diff options
author | Roshan Pius <rpius@google.com> | 2018-11-06 17:43:02 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2018-11-06 17:43:02 +0000 |
commit | d2a20c1a3975668bc5aeab53a8f0daae6662366a (patch) | |
tree | 37b5ec26895032b06815f6b78c2abde4c8a5eea6 | |
parent | 894960003ef7ccb181dee54da59fe787f471e406 (diff) | |
parent | 3ff9e5cb3fe2fab47365bdf3ffb75139c099b3da (diff) |
Merge changes from topics "network_request_match_callback", "wifi_network_specifier"
* changes:
WifiNetworkFactory: Implement network matching using network specifier
ScanResultMatchInfo: Refactor network type retrieval
WifiServiceImpl: Network request match callback registration
WifiNetworkFactory: Always allow requests from signature app
WifiNetworkFactory: Trigger periodic scans
WifiConfigurationUtil: Validation for network specifier
WifiNetworkFactory: Implement |acceptRequest|
10 files changed, 1780 insertions, 60 deletions
diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java index dd852945b..b401515d5 100644 --- a/service/java/com/android/server/wifi/ClientModeImpl.java +++ b/service/java/com/android/server/wifi/ClientModeImpl.java @@ -38,6 +38,7 @@ import android.net.IpConfiguration; import android.net.KeepalivePacketData; import android.net.LinkProperties; import android.net.MacAddress; +import android.net.MatchAllNetworkSpecifier; import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkCapabilities; @@ -50,6 +51,7 @@ import android.net.StaticIpConfiguration; import android.net.TrafficStats; import android.net.dhcp.DhcpClient; import android.net.ip.IpClient; +import android.net.wifi.INetworkRequestMatchCallback; import android.net.wifi.RssiPacketCountInfo; import android.net.wifi.ScanResult; import android.net.wifi.SupplicantState; @@ -793,15 +795,18 @@ public class ClientModeImpl extends StateMachine { // TODO - needs to be a bit more dynamic mDfltNetworkCapabilities = new NetworkCapabilities(mNetworkCapabilitiesFilter); + NetworkCapabilities factoryNetworkCapabilities = + new NetworkCapabilities(mNetworkCapabilitiesFilter); + factoryNetworkCapabilities.setNetworkSpecifier(new MatchAllNetworkSpecifier()); // Make the network factories. mNetworkFactory = mWifiInjector.makeWifiNetworkFactory( - mNetworkCapabilitiesFilter, mWifiConnectivityManager); + factoryNetworkCapabilities, mWifiConnectivityManager); // We can't filter untrusted network in the capabilities filter because a trusted // network would still satisfy a request that accepts untrusted ones. // We need a second network factory for untrusted network requests because we need a // different score filter for these requests. mUntrustedNetworkFactory = mWifiInjector.makeUntrustedWifiNetworkFactory( - mNetworkCapabilitiesFilter, mWifiConnectivityManager); + factoryNetworkCapabilities, mWifiConnectivityManager); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_ON); @@ -1063,6 +1068,7 @@ public class ClientModeImpl extends StateMachine { mWifiConfigManager.enableVerboseLogging(verbose); mSupplicantStateTracker.enableVerboseLogging(verbose); mPasspointManager.enableVerboseLogging(verbose); + mNetworkFactory.enableVerboseLogging(verbose); } private static final String SYSTEM_PROPERTY_LOG_CONTROL_WIFIHAL = "log.tag.WifiHAL"; @@ -5763,4 +5769,20 @@ public class ClientModeImpl extends StateMachine { resultMsg.recycle(); return result; } + + /** + * Add a network request match callback to {@link WifiNetworkFactory}. + */ + public void addNetworkRequestMatchCallback(IBinder binder, + INetworkRequestMatchCallback callback, + int callbackIdentifier) { + mNetworkFactory.addCallback(binder, callback, callbackIdentifier); + } + + /** + * Remove a network request match callback from {@link WifiNetworkFactory}. + */ + public void removeNetworkRequestMatchCallback(int callbackIdentifier) { + mNetworkFactory.removeCallback(callbackIdentifier); + } } diff --git a/service/java/com/android/server/wifi/ScanResultMatchInfo.java b/service/java/com/android/server/wifi/ScanResultMatchInfo.java index ad29c2312..72fab6bc0 100644 --- a/service/java/com/android/server/wifi/ScanResultMatchInfo.java +++ b/service/java/com/android/server/wifi/ScanResultMatchInfo.java @@ -15,11 +15,15 @@ */ package com.android.server.wifi; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import com.android.server.wifi.util.ScanResultUtil; +import java.lang.annotation.Retention; import java.util.Objects; /** @@ -31,6 +35,15 @@ public class ScanResultMatchInfo { public static final int NETWORK_TYPE_PSK = 2; public static final int NETWORK_TYPE_EAP = 3; + @Retention(SOURCE) + @IntDef(prefix = { "NETWORK_TYPE_" }, value = { + NETWORK_TYPE_OPEN, + NETWORK_TYPE_WEP, + NETWORK_TYPE_PSK, + NETWORK_TYPE_EAP + }) + public @interface NetworkType {} + /** * SSID of the network. */ @@ -38,29 +51,51 @@ public class ScanResultMatchInfo { /** * Security Type of the network. */ - public int networkType; + public @NetworkType int networkType; /** - * Get the ScanResultMatchInfo for the given WifiConfiguration + * Fetch network type from network configuration. */ - public static ScanResultMatchInfo fromWifiConfiguration(WifiConfiguration config) { - ScanResultMatchInfo info = new ScanResultMatchInfo(); - info.networkSsid = config.SSID; + public static @NetworkType int getNetworkType(WifiConfiguration config) { if (WifiConfigurationUtil.isConfigForPskNetwork(config)) { - info.networkType = NETWORK_TYPE_PSK; + return NETWORK_TYPE_PSK; } else if (WifiConfigurationUtil.isConfigForEapNetwork(config)) { - info.networkType = NETWORK_TYPE_EAP; + return NETWORK_TYPE_EAP; } else if (WifiConfigurationUtil.isConfigForWepNetwork(config)) { - info.networkType = NETWORK_TYPE_WEP; + return NETWORK_TYPE_WEP; } else if (WifiConfigurationUtil.isConfigForOpenNetwork(config)) { - info.networkType = NETWORK_TYPE_OPEN; - } else { - throw new IllegalArgumentException("Invalid WifiConfiguration: " + config); + return NETWORK_TYPE_OPEN; } + throw new IllegalArgumentException("Invalid WifiConfiguration: " + config); + } + + /** + * Get the ScanResultMatchInfo for the given WifiConfiguration + */ + public static ScanResultMatchInfo fromWifiConfiguration(WifiConfiguration config) { + ScanResultMatchInfo info = new ScanResultMatchInfo(); + info.networkSsid = config.SSID; + info.networkType = getNetworkType(config); return info; } /** + * Fetch network type from scan result. + */ + public static @NetworkType int getNetworkType(ScanResult scanResult) { + if (ScanResultUtil.isScanResultForPskNetwork(scanResult)) { + return NETWORK_TYPE_PSK; + } else if (ScanResultUtil.isScanResultForEapNetwork(scanResult)) { + return NETWORK_TYPE_EAP; + } else if (ScanResultUtil.isScanResultForWepNetwork(scanResult)) { + return NETWORK_TYPE_WEP; + } else if (ScanResultUtil.isScanResultForOpenNetwork(scanResult)) { + return NETWORK_TYPE_OPEN; + } + throw new IllegalArgumentException("Invalid ScanResult: " + scanResult); + } + + /** * Get the ScanResultMatchInfo for the given ScanResult */ public static ScanResultMatchInfo fromScanResult(ScanResult scanResult) { @@ -70,17 +105,7 @@ public class ScanResultMatchInfo { // However, according to our public documentation ths {@link WifiConfiguration#SSID} can // either have a hex string or quoted ASCII string SSID. info.networkSsid = ScanResultUtil.createQuotedSSID(scanResult.SSID); - if (ScanResultUtil.isScanResultForPskNetwork(scanResult)) { - info.networkType = NETWORK_TYPE_PSK; - } else if (ScanResultUtil.isScanResultForEapNetwork(scanResult)) { - info.networkType = NETWORK_TYPE_EAP; - } else if (ScanResultUtil.isScanResultForWepNetwork(scanResult)) { - info.networkType = NETWORK_TYPE_WEP; - } else if (ScanResultUtil.isScanResultForOpenNetwork(scanResult)) { - info.networkType = NETWORK_TYPE_OPEN; - } else { - throw new IllegalArgumentException("Invalid ScanResult: " + scanResult); - } + info.networkType = getNetworkType(scanResult); return info; } diff --git a/service/java/com/android/server/wifi/WifiConfigurationUtil.java b/service/java/com/android/server/wifi/WifiConfigurationUtil.java index 0258834ea..9bf6e327d 100644 --- a/service/java/com/android/server/wifi/WifiConfigurationUtil.java +++ b/service/java/com/android/server/wifi/WifiConfigurationUtil.java @@ -16,15 +16,21 @@ package com.android.server.wifi; +import static com.android.server.wifi.util.NativeUtil.addEnclosingQuotes; + import android.content.pm.UserInfo; import android.net.IpConfiguration; +import android.net.MacAddress; import android.net.StaticIpConfiguration; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiEnterpriseConfig; +import android.net.wifi.WifiNetworkSpecifier; import android.net.wifi.WifiScanner; +import android.os.PatternMatcher; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wifi.util.NativeUtil; @@ -50,16 +56,21 @@ public class WifiConfigurationUtil { /** * Constants used for validating external config objects. */ - private static final int ENCLOSING_QUTOES_LEN = 2; - private static final int SSID_UTF_8_MIN_LEN = 1 + ENCLOSING_QUTOES_LEN; - private static final int SSID_UTF_8_MAX_LEN = 32 + ENCLOSING_QUTOES_LEN; + private static final int ENCLOSING_QUOTES_LEN = 2; + private static final int SSID_UTF_8_MIN_LEN = 1 + ENCLOSING_QUOTES_LEN; + private static final int SSID_UTF_8_MAX_LEN = 32 + ENCLOSING_QUOTES_LEN; private static final int SSID_HEX_MIN_LEN = 2; private static final int SSID_HEX_MAX_LEN = 64; - private static final int PSK_ASCII_MIN_LEN = 8 + ENCLOSING_QUTOES_LEN; - private static final int PSK_ASCII_MAX_LEN = 63 + ENCLOSING_QUTOES_LEN; + private static final int PSK_ASCII_MIN_LEN = 8 + ENCLOSING_QUOTES_LEN; + private static final int PSK_ASCII_MAX_LEN = 63 + ENCLOSING_QUOTES_LEN; private static final int PSK_HEX_LEN = 64; @VisibleForTesting public static final String PASSWORD_MASK = "*"; + private static final String MATCH_EMPTY_SSID_PATTERN_PATH = ""; + private static final Pair<MacAddress, MacAddress> MATCH_NONE_BSSID_PATTERN = + new Pair(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS); + private static final Pair<MacAddress, MacAddress> MATCH_ALL_BSSID_PATTERN = + new Pair(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); /** * Check whether a network configuration is visible to a user or any of its managed profiles. @@ -319,6 +330,34 @@ public class WifiConfigurationUtil { return true; } + private static boolean validateBssid(MacAddress bssid) { + if (bssid == null) return true; + if (bssid.getAddressType() != MacAddress.TYPE_UNICAST) { + Log.e(TAG, "validateBssid failed: invalid bssid"); + return false; + } + return true; + } + + private static boolean validateBssid(String bssid) { + if (bssid == null) return true; + if (bssid.isEmpty()) { + Log.e(TAG, "validateBssid failed: empty string"); + return false; + } + MacAddress bssidMacAddress; + try { + bssidMacAddress = MacAddress.fromString(bssid); + } catch (IllegalArgumentException e) { + Log.e(TAG, "validateBssid failed: malformed string: " + bssid); + return false; + } + if (!validateBssid(bssidMacAddress)) { + return false; + } + return true; + } + private static boolean validatePsk(String psk, boolean isAdd) { if (isAdd) { if (psk == null) { @@ -461,13 +500,14 @@ public class WifiConfigurationUtil { * * This method checks for the following parameters: * 1. {@link WifiConfiguration#SSID} - * 2. {@link WifiConfiguration#preSharedKey} - * 3. {@link WifiConfiguration#allowedKeyManagement} - * 4. {@link WifiConfiguration#allowedProtocols} - * 5. {@link WifiConfiguration#allowedAuthAlgorithms} - * 6. {@link WifiConfiguration#allowedGroupCiphers} - * 7. {@link WifiConfiguration#allowedPairwiseCiphers} - * 8. {@link WifiConfiguration#getIpConfiguration()} + * 2. {@link WifiConfiguration#BSSID} + * 3. {@link WifiConfiguration#preSharedKey} + * 4. {@link WifiConfiguration#allowedKeyManagement} + * 5. {@link WifiConfiguration#allowedProtocols} + * 6. {@link WifiConfiguration#allowedAuthAlgorithms} + * 7. {@link WifiConfiguration#allowedGroupCiphers} + * 8. {@link WifiConfiguration#allowedPairwiseCiphers} + * 9. {@link WifiConfiguration#getIpConfiguration()} * * @param config {@link WifiConfiguration} received from an external application. * @param isAdd {@link #VALIDATE_FOR_ADD} to indicate a network config received for an add, @@ -480,6 +520,9 @@ public class WifiConfigurationUtil { if (!validateSsid(config.SSID, isAdd)) { return false; } + if (!validateBssid(config.BSSID)) { + return false; + } if (!validateBitSets(config)) { return false; } @@ -497,6 +540,134 @@ public class WifiConfigurationUtil { return true; } + private static boolean validateBssidPattern( + Pair<MacAddress, MacAddress> bssidPatternMatcher) { + if (bssidPatternMatcher == null) return true; + MacAddress baseAddress = bssidPatternMatcher.first; + MacAddress mask = bssidPatternMatcher.second; + if (baseAddress.getAddressType() != MacAddress.TYPE_UNICAST) { + Log.e(TAG, "validateBssidPatternMatcher failed : invalid base address: " + baseAddress); + return false; + } + if (mask.equals(MacAddress.ALL_ZEROS_ADDRESS) + && !baseAddress.equals(MacAddress.ALL_ZEROS_ADDRESS)) { + Log.e(TAG, "validateBssidPatternMatcher failed : invalid mask/base: " + mask + "/" + + baseAddress); + return false; + } + // TBD: Can we do any more sanity checks? + return true; + } + + // TODO(b/113878056): Some of this is duplicated in {@link WifiNetworkConfigBuilder}. + // Merge them somehow?. + private static boolean isValidNetworkSpecifier(WifiNetworkSpecifier specifier) { + PatternMatcher ssidPatternMatcher = specifier.ssidPatternMatcher; + Pair<MacAddress, MacAddress> bssidPatternMatcher = specifier.bssidPatternMatcher; + if (ssidPatternMatcher == null || bssidPatternMatcher == null) { + return false; + } + if (ssidPatternMatcher.getPath() == null || bssidPatternMatcher.first == null + || bssidPatternMatcher.second == null) { + return false; + } + return true; + } + + private static boolean isMatchNoneNetworkSpecifier(WifiNetworkSpecifier specifier) { + PatternMatcher ssidPatternMatcher = specifier.ssidPatternMatcher; + Pair<MacAddress, MacAddress> bssidPatternMatcher = specifier.bssidPatternMatcher; + if (ssidPatternMatcher.getType() != PatternMatcher.PATTERN_PREFIX + && ssidPatternMatcher.getPath().equals(MATCH_EMPTY_SSID_PATTERN_PATH)) { + return true; + } + if (bssidPatternMatcher.equals(MATCH_NONE_BSSID_PATTERN)) { + return true; + } + return false; + } + + private static boolean isMatchAllNetworkSpecifier(WifiNetworkSpecifier specifier) { + PatternMatcher ssidPatternMatcher = specifier.ssidPatternMatcher; + Pair<MacAddress, MacAddress> bssidPatternMatcher = specifier.bssidPatternMatcher; + if (ssidPatternMatcher.match(MATCH_EMPTY_SSID_PATTERN_PATH) + && bssidPatternMatcher.equals(MATCH_ALL_BSSID_PATTERN)) { + return true; + } + return false; + } + + /** + * Validate the configuration received from an external application inside + * {@link WifiNetworkSpecifier}. + * + * This method checks for the following parameters: + * 1. {@link WifiNetworkSpecifier#ssidPatternMatcher} + * 2. {@link WifiNetworkSpecifier#bssidPatternMatcher} + * 3. {@link WifiConfiguration#SSID} + * 4. {@link WifiConfiguration#BSSID} + * 5. {@link WifiConfiguration#preSharedKey} + * 6. {@link WifiConfiguration#allowedKeyManagement} + * 7. {@link WifiConfiguration#allowedProtocols} + * 8. {@link WifiConfiguration#allowedAuthAlgorithms} + * 9. {@link WifiConfiguration#allowedGroupCiphers} + * 10. {@link WifiConfiguration#allowedPairwiseCiphers} + * 11. {@link WifiConfiguration#getIpConfiguration()} + * + * @param specifier Instance of {@link WifiNetworkSpecifier}. + * @return true if the parameters are valid, false otherwise. + */ + public static boolean validateNetworkSpecifier(WifiNetworkSpecifier specifier) { + if (!isValidNetworkSpecifier(specifier)) { + Log.e(TAG, "validateNetworkSpecifier failed : invalid network specifier"); + return false; + } + if (isMatchNoneNetworkSpecifier(specifier)) { + Log.e(TAG, "validateNetworkSpecifier failed : match-none specifier"); + return false; + } + if (isMatchAllNetworkSpecifier(specifier)) { + Log.e(TAG, "validateNetworkSpecifier failed : match-all specifier"); + return false; + } + WifiConfiguration config = specifier.wifiConfiguration; + if (specifier.ssidPatternMatcher.getType() == PatternMatcher.PATTERN_LITERAL) { + // For literal SSID matches, the value should satisfy SSID requirements. + // WifiConfiguration.SSID needs quotes around ASCII SSID. + if (!validateSsid(addEnclosingQuotes(specifier.ssidPatternMatcher.getPath()), true)) { + return false; + } + } else { + if (config.hiddenSSID) { + Log.e(TAG, "validateNetworkSpecifier failed : ssid pattern not supported " + + "for hidden networks"); + return false; + } + } + if (Objects.equals(specifier.bssidPatternMatcher.second, MacAddress.BROADCAST_ADDRESS)) { + // For literal BSSID matches, the value should satisfy MAC address requirements. + if (!validateBssid(specifier.bssidPatternMatcher.first)) { + return false; + } + } else { + if (!validateBssidPattern(specifier.bssidPatternMatcher)) { + return false; + } + } + if (!validateBitSets(config)) { + return false; + } + if (!validateKeyMgmt(config.allowedKeyManagement)) { + return false; + } + if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK) + && !validatePsk(config.preSharedKey, true)) { + return false; + } + // TBD: Validate some enterprise params as well in the future here. + return true; + } + /** * Check if the provided two networks are the same. * Note: This does not check if network selection BSSID's are the same. diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java index 53b669e4c..e708e2bde 100644 --- a/service/java/com/android/server/wifi/WifiInjector.java +++ b/service/java/com/android/server/wifi/WifiInjector.java @@ -18,6 +18,7 @@ package com.android.server.wifi; import android.annotation.NonNull; import android.app.ActivityManager; +import android.app.AlarmManager; import android.app.AppOpsManager; import android.content.Context; import android.hardware.SystemSensorManager; @@ -548,7 +549,10 @@ public class WifiInjector { public WifiNetworkFactory makeWifiNetworkFactory( NetworkCapabilities nc, WifiConnectivityManager wifiConnectivityManager) { return new WifiNetworkFactory( - mWifiCoreHandlerThread.getLooper(), mContext, nc, wifiConnectivityManager); + mWifiCoreHandlerThread.getLooper(), mContext, nc, + (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE), + (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE), + mClock, this, wifiConnectivityManager, mWifiPermissionsUtil); } /** diff --git a/service/java/com/android/server/wifi/WifiNetworkFactory.java b/service/java/com/android/server/wifi/WifiNetworkFactory.java index 489601d0d..9da64d231 100644 --- a/service/java/com/android/server/wifi/WifiNetworkFactory.java +++ b/service/java/com/android/server/wifi/WifiNetworkFactory.java @@ -16,63 +16,524 @@ package com.android.server.wifi; +import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.server.wifi.util.NativeUtil.addEnclosingQuotes; + +import android.app.ActivityManager; +import android.app.AlarmManager; import android.content.Context; +import android.net.MacAddress; import android.net.NetworkCapabilities; import android.net.NetworkFactory; import android.net.NetworkRequest; +import android.net.NetworkSpecifier; +import android.net.wifi.INetworkRequestMatchCallback; +import android.net.wifi.INetworkRequestUserSelectionCallback; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiNetworkSpecifier; +import android.net.wifi.WifiScanner; +import android.os.Handler; +import android.os.IBinder; import android.os.Looper; +import android.os.RemoteException; +import android.os.WorkSource; import android.util.Log; +import android.util.Pair; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.wifi.util.ExternalCallbackTracker; +import com.android.server.wifi.util.WifiPermissionsUtil; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * Network factory to handle trusted wifi network requests. */ public class WifiNetworkFactory extends NetworkFactory { private static final String TAG = "WifiNetworkFactory"; + @VisibleForTesting private static final int SCORE_FILTER = 60; + @VisibleForTesting + public static final int PERIODIC_SCAN_INTERVAL_MS = 10 * 1000; // 10 seconds + private final Context mContext; + private final ActivityManager mActivityManager; + private final AlarmManager mAlarmManager; + private final Clock mClock; + private final Handler mHandler; + private final WifiInjector mWifiInjector; private final WifiConnectivityManager mWifiConnectivityManager; - private int mConnectionReqCount = 0; + private final WifiPermissionsUtil mWifiPermissionsUtil; + private final WifiScanner.ScanSettings mScanSettings; + private final NetworkFactoryScanListener mScanListener; + private final NetworkFactoryAlarmListener mPeriodicScanTimerListener; + private final ExternalCallbackTracker<INetworkRequestMatchCallback> mRegisteredCallbacks; + private WifiScanner mWifiScanner; + + private int mGenericConnectionReqCount = 0; + private NetworkRequest mActiveSpecificNetworkRequest; + private WifiNetworkSpecifier mActiveSpecificNetworkRequestSpecifier; + private List<WifiConfiguration> mActiveMatchedNetworks; + // Verbose logging flag. + private boolean mVerboseLoggingEnabled = false; + private boolean mPeriodicScanTimerSet = false; + + // Scan listener for scan requests. + private class NetworkFactoryScanListener implements WifiScanner.ScanListener { + @Override + public void onSuccess() { + // Scan request succeeded, wait for results to report to external clients. + if (mVerboseLoggingEnabled) { + Log.d(TAG, "Scan request succeeded"); + } + } + + @Override + public void onFailure(int reason, String description) { + Log.e(TAG, "Scan failure received. reason: " + reason + + ", description: " + description); + // TODO(b/113878056): Retry scan to workaround any transient scan failures. + scheduleNextPeriodicScan(); + } + + @Override + public void onResults(WifiScanner.ScanData[] scanDatas) { + if (mVerboseLoggingEnabled) { + Log.d(TAG, "Scan results received"); + } + // For single scans, the array size should always be 1. + if (scanDatas.length != 1) { + Log.wtf(TAG, "Found more than 1 batch of scan results, Ignoring..."); + return; + } + WifiScanner.ScanData scanData = scanDatas[0]; + ScanResult[] scanResults = scanData.getResults(); + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Received " + scanResults.length + " scan results"); + } + List<WifiConfiguration> matchedNetworks = + getNetworksMatchingActiveNetworkRequest(scanResults); + sendNetworkRequestMatchCallbacksForActiveRequest(matchedNetworks); + mActiveMatchedNetworks = matchedNetworks; + + // Didn't find a match, schedule the next scan. + scheduleNextPeriodicScan(); + } + + @Override + public void onFullResult(ScanResult fullScanResult) { + // Ignore for single scans. + } - public WifiNetworkFactory(Looper l, Context c, NetworkCapabilities f, - WifiConnectivityManager connectivityManager) { - super(l, c, TAG, f); + @Override + public void onPeriodChanged(int periodInMs) { + // Ignore for single scans. + } + }; + + private class NetworkFactoryAlarmListener implements AlarmManager.OnAlarmListener { + @Override + public void onAlarm() { + // Trigger the next scan. + startScan(); + } + } + + // Callback result from settings UI. + private class NetworkFactoryUserSelectionCallback extends + INetworkRequestUserSelectionCallback.Stub { + @Override + public void select(WifiConfiguration wifiConfiguration) { + mHandler.post(() -> { + if (mActiveSpecificNetworkRequest == null) { + Log.e(TAG, "Stale callback select received"); + return; + } + // TODO(b/113878056): Trigger network connection to |wificonfiguration|. + }); + } + + @Override + public void reject() { + mHandler.post(() -> { + if (mActiveSpecificNetworkRequest == null) { + Log.e(TAG, "Stale callback reject received"); + return; + } + // TODO(b/113878056): Clear the active network request. + }); + } + } + + public WifiNetworkFactory(Looper looper, Context context, NetworkCapabilities nc, + ActivityManager activityManager, AlarmManager alarmManager, + Clock clock, WifiInjector wifiInjector, + WifiConnectivityManager connectivityManager, + WifiPermissionsUtil wifiPermissionsUtil) { + super(looper, context, TAG, nc); + mContext = context; + mActivityManager = activityManager; + mAlarmManager = alarmManager; + mClock = clock; + mHandler = new Handler(looper); + mWifiInjector = wifiInjector; mWifiConnectivityManager = connectivityManager; + mWifiPermissionsUtil = wifiPermissionsUtil; + // Create the scan settings. + mScanSettings = new WifiScanner.ScanSettings(); + mScanSettings.type = WifiScanner.TYPE_HIGH_ACCURACY; + mScanSettings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS; + mScanSettings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN; + mScanListener = new NetworkFactoryScanListener(); + mPeriodicScanTimerListener = new NetworkFactoryAlarmListener(); + mRegisteredCallbacks = new ExternalCallbackTracker<INetworkRequestMatchCallback>(mHandler); setScoreFilter(SCORE_FILTER); } + /** + * Enable verbose logging. + */ + public void enableVerboseLogging(int verbose) { + mVerboseLoggingEnabled = (verbose > 0); + } + + /** + * Add a new callback for network request match handling. + */ + public void addCallback(IBinder binder, INetworkRequestMatchCallback callback, + int callbackIdentifier) { + if (!mRegisteredCallbacks.add(binder, callback, callbackIdentifier)) { + Log.e(TAG, "Failed to add callback"); + return; + } + // Register our user selection callback immediately. + try { + callback.onUserSelectionCallbackRegistration(new NetworkFactoryUserSelectionCallback()); + } catch (RemoteException e) { + Log.e(TAG, "Unable to invoke user selection registration callback " + callback, e); + } + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Adding callback. Num callbacks: " + mRegisteredCallbacks.getNumCallbacks()); + } + } + + /** + * Remove an existing callback for network request match handling. + */ + public void removeCallback(int callbackIdentifier) { + mRegisteredCallbacks.remove(callbackIdentifier); + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Removing callback. Num callbacks: " + + mRegisteredCallbacks.getNumCallbacks()); + } + } + + /** + * Check whether to accept the new network connection request. + * + * All the validation of the incoming request is done in this method. + */ + @Override + public boolean acceptRequest(NetworkRequest networkRequest, int score) { + NetworkSpecifier ns = networkRequest.networkCapabilities.getNetworkSpecifier(); + // Generic wifi request. Always accept. + if (ns == null) { + // Generic wifi request. Always accept. + } else { + // Invalid network specifier. + if (!(ns instanceof WifiNetworkSpecifier)) { + Log.e(TAG, "Invalid network specifier mentioned. Rejecting"); + return false; + } + + WifiNetworkSpecifier wns = (WifiNetworkSpecifier) ns; + if (!WifiConfigurationUtil.validateNetworkSpecifier(wns)) { + Log.e(TAG, "Invalid network specifier." + + " Rejecting request from " + wns.requestorUid); + return false; + } + // Only allow specific wifi network request from foreground app/service. + if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(wns.requestorUid) + && !isRequestFromForegroundAppOrService(wns.requestorUid)) { + Log.e(TAG, "Request not from foreground app or service." + + " Rejecting request from " + wns.requestorUid); + return false; + } + // If there is a pending request, only proceed if the new request is from a foreground + // app. + if (mActiveSpecificNetworkRequest != null + && !mWifiPermissionsUtil.checkNetworkSettingsPermission(wns.requestorUid) + && !isRequestFromForegroundApp(wns.requestorUid)) { + WifiNetworkSpecifier aWns = + (WifiNetworkSpecifier) mActiveSpecificNetworkRequest + .networkCapabilities + .getNetworkSpecifier(); + if (isRequestFromForegroundApp(aWns.requestorUid)) { + Log.e(TAG, "Already processing active request from a foreground app " + + aWns.requestorUid + ". Rejecting request from " + wns.requestorUid); + return false; + } + } + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Accepted network request with specifier from fg " + + (isRequestFromForegroundApp(wns.requestorUid) ? "app" : "service")); + } + } + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Accepted network request " + networkRequest); + } + return true; + } + + /** + * Handle new network connection requests. + * + * The assumption here is that {@link #acceptRequest(NetworkRequest, int)} has already sanitized + * the incoming request. + */ @Override protected void needNetworkFor(NetworkRequest networkRequest, int score) { - if (++mConnectionReqCount == 1) { - mWifiConnectivityManager.setTrustedConnectionAllowed(true); + NetworkSpecifier ns = networkRequest.networkCapabilities.getNetworkSpecifier(); + if (ns == null) { + // Generic wifi request. Turn on auto-join if necessary. + if (++mGenericConnectionReqCount == 1) { + mWifiConnectivityManager.setTrustedConnectionAllowed(true); + } + } else { + // Invalid network specifier. + if (!(ns instanceof WifiNetworkSpecifier)) { + Log.e(TAG, "Invalid network specifier mentioned. Rejecting"); + return; + } + retrieveWifiScanner(); + + // Store the active network request. + mActiveSpecificNetworkRequest = new NetworkRequest(networkRequest); + WifiNetworkSpecifier wns = (WifiNetworkSpecifier) ns; + mActiveSpecificNetworkRequestSpecifier = new WifiNetworkSpecifier( + wns.ssidPatternMatcher, wns.bssidPatternMatcher, wns.wifiConfiguration, + wns.requestorUid); + + // Trigger periodic scans for finding a network in the request. + startPeriodicScans(); + // Disable Auto-join so that NetworkFactory can take control of the network selection. + // TODO(b/117979585): Defer turning off auto-join. + mWifiConnectivityManager.enable(false); } } @Override protected void releaseNetworkFor(NetworkRequest networkRequest) { - if (mConnectionReqCount == 0) { - Log.e(TAG, "No valid network request to release"); - return; - } - if (--mConnectionReqCount == 0) { - mWifiConnectivityManager.setTrustedConnectionAllowed(false); + NetworkSpecifier ns = networkRequest.networkCapabilities.getNetworkSpecifier(); + if (ns == null) { + // Generic wifi request. Turn off auto-join if necessary. + if (mGenericConnectionReqCount == 0) { + Log.e(TAG, "No valid network request to release"); + return; + } + if (--mGenericConnectionReqCount == 0) { + mWifiConnectivityManager.setTrustedConnectionAllowed(false); + } + } else { + // Invalid network specifier. + if (!(ns instanceof WifiNetworkSpecifier)) { + Log.e(TAG, "Invalid network specifier mentioned. Ingoring"); + return; + } + if (!mActiveSpecificNetworkRequest.equals(networkRequest)) { + Log.e(TAG, "Network specifier does not match the active request. Ignoring"); + return; + } + // Release the active network request. + mActiveSpecificNetworkRequest = null; + mActiveSpecificNetworkRequestSpecifier = null; + // Cancel the periodic scans. + cancelPeriodicScans(); + // Re-enable Auto-join (if there is a generic request pending). + if (mGenericConnectionReqCount > 0) { + mWifiConnectivityManager.enable(true); + } } } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { super.dump(fd, pw, args); - pw.println(TAG + ": mConnectionReqCount " + mConnectionReqCount); + pw.println(TAG + ": mGenericConnectionReqCount " + mGenericConnectionReqCount); + pw.println(TAG + ": mActiveSpecificNetworkRequest " + mActiveSpecificNetworkRequest); } /** * Check if there is at-least one connection request. */ public boolean hasConnectionRequests() { - return mConnectionReqCount > 0; + return mGenericConnectionReqCount > 0 || mActiveSpecificNetworkRequest != null; + } + + /** + * Check if the request comes from foreground app/service. + */ + private boolean isRequestFromForegroundAppOrService(int requestorUid) { + try { + String requestorPackageName = mContext.getPackageManager().getNameForUid(requestorUid); + return mActivityManager.getPackageImportance(requestorPackageName) + <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; + } catch (SecurityException e) { + Log.e(TAG, "Failed to check the app state", e); + return false; + } + } + + /** + * Check if the request comes from foreground app. + */ + private boolean isRequestFromForegroundApp(int requestorUid) { + try { + String requestorPackageName = mContext.getPackageManager().getNameForUid(requestorUid); + return mActivityManager.getPackageImportance(requestorPackageName) + <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; + } catch (SecurityException e) { + Log.e(TAG, "Failed to check the app state", e); + return false; + } + } + + /** + * Helper method to populate WifiScanner handle. This is done lazily because + * WifiScanningService is started after WifiService. + */ + private void retrieveWifiScanner() { + if (mWifiScanner != null) return; + mWifiScanner = mWifiInjector.getWifiScanner(); + checkNotNull(mWifiScanner); + } + + private void startPeriodicScans() { + if (mActiveSpecificNetworkRequestSpecifier == null) { + Log.e(TAG, "Periodic scan triggered when there is no active network request. " + + "Ignoring..."); + return; + } + WifiNetworkSpecifier wns = mActiveSpecificNetworkRequestSpecifier; + WifiConfiguration wifiConfiguration = wns.wifiConfiguration; + if (wifiConfiguration.hiddenSSID) { + mScanSettings.hiddenNetworks = new WifiScanner.ScanSettings.HiddenNetwork[1]; + // Can't search for SSID pattern in hidden networks. + mScanSettings.hiddenNetworks[0] = + new WifiScanner.ScanSettings.HiddenNetwork( + addEnclosingQuotes(wns.ssidPatternMatcher.getPath())); + } + startScan(); + } + + private void cancelPeriodicScans() { + if (mPeriodicScanTimerSet) { + mAlarmManager.cancel(mPeriodicScanTimerListener); + mPeriodicScanTimerSet = false; + } + // Clear the hidden networks field after each request. + mScanSettings.hiddenNetworks = null; + } + + private void scheduleNextPeriodicScan() { + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + mClock.getElapsedSinceBootMillis() + PERIODIC_SCAN_INTERVAL_MS, + TAG, mPeriodicScanTimerListener, mHandler); + mPeriodicScanTimerSet = true; + } + + private void startScan() { + if (mActiveSpecificNetworkRequestSpecifier == null) { + Log.e(TAG, "Scan triggered when there is no active network request. Ignoring..."); + return; + } + if (mVerboseLoggingEnabled) { + Log.v(TAG, "Starting the next scan for " + mActiveSpecificNetworkRequestSpecifier); + } + // Create a worksource using the caller's UID. + WorkSource workSource = new WorkSource(mActiveSpecificNetworkRequestSpecifier.requestorUid); + mWifiScanner.startScan(mScanSettings, mScanListener, workSource); + } + + private boolean doesScanResultMatchWifiNetworkSpecifier( + WifiNetworkSpecifier wns, ScanResult scanResult) { + if (!wns.ssidPatternMatcher.match(scanResult.SSID)) { + return false; + } + MacAddress bssid = MacAddress.fromString(scanResult.BSSID); + MacAddress matchBaseAddress = wns.bssidPatternMatcher.first; + MacAddress matchMask = wns.bssidPatternMatcher.second; + if (!bssid.matches(matchBaseAddress, matchMask)) { + return false; + } + if (ScanResultMatchInfo.getNetworkType(wns.wifiConfiguration) + != ScanResultMatchInfo.getNetworkType(scanResult)) { + return false; + } + return true; + } + + // Loops through the scan results and finds scan results matching the active network + // request. Returns a list of WifiConfiguration representing all the networks that + // match the active network request's specifier. + private List<WifiConfiguration> getNetworksMatchingActiveNetworkRequest( + ScanResult[] scanResults) { + // There could be multiple bssid's (i.e ScanResult) within the same ssid matching the + // specifier, we need to collapse all of them into a single network represented by a + // WifiConfiguration object. + // Use a map keyed in by pair of SSID & network type to collect the list of unique + // networks (i.e wificonfiguration objects) matching the specifier. + if (mActiveSpecificNetworkRequest == null) { + Log.e(TAG, "Scan results received with no active network request. Ignoring..."); + return new ArrayList<>(); + } + Map<Pair<String, Integer>, WifiConfiguration> matchedNetworks = new HashMap<>(); + WifiNetworkSpecifier wns = (WifiNetworkSpecifier) + mActiveSpecificNetworkRequest.networkCapabilities.getNetworkSpecifier(); + checkNotNull(wns); + + for (ScanResult scanResult : scanResults) { + if (doesScanResultMatchWifiNetworkSpecifier(wns, scanResult)) { + // Use the WifiConfiguration provided in the request & fill in the SSID + // & BSSID fields from ScanResult. + WifiConfiguration matchedNetwork = new WifiConfiguration(wns.wifiConfiguration); + matchedNetwork.SSID = addEnclosingQuotes(scanResult.SSID); + matchedNetwork.BSSID = scanResult.BSSID; + matchedNetworks.put( + Pair.create(matchedNetwork.SSID, + ScanResultMatchInfo.getNetworkType(matchedNetwork)), + matchedNetwork); + } + } + if (mVerboseLoggingEnabled) { + Log.e(TAG, "List of networks matching the active request " + + matchedNetworks.values()); + } + return new ArrayList<>(matchedNetworks.values()); + } + + private void sendNetworkRequestMatchCallbacksForActiveRequest( + List<WifiConfiguration> matchedNetworks) { + if (mRegisteredCallbacks.getNumCallbacks() == 0) { + Log.e(TAG, "No callback registered for sending network request matches. " + + "Ignoring..."); + return; + } + for (INetworkRequestMatchCallback callback : mRegisteredCallbacks.getCallbacks()) { + try { + callback.onMatch(matchedNetworks); + } catch (RemoteException e) { + Log.e(TAG, "Unable to invoke network request match callback " + callback, e); + } + } } } diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java index 6206d9b15..4b1766d79 100644 --- a/service/java/com/android/server/wifi/WifiServiceImpl.java +++ b/service/java/com/android/server/wifi/WifiServiceImpl.java @@ -58,6 +58,7 @@ import android.net.Network; import android.net.NetworkUtils; import android.net.Uri; import android.net.ip.IpClient; +import android.net.wifi.INetworkRequestMatchCallback; import android.net.wifi.ISoftApCallback; import android.net.wifi.ITrafficStateCallback; import android.net.wifi.IWifiManager; @@ -75,6 +76,7 @@ import android.os.AsyncTask; import android.os.BatteryStats; import android.os.Binder; import android.os.Bundle; +import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; @@ -2822,7 +2824,7 @@ public class WifiServiceImpl extends IWifiManager.Stub { /** * see {@link android.net.wifi.WifiManager#registerTrafficStateCallback( - * TrafficStateCallback, Handler)} + * WifiManager.TrafficStateCallback, Handler)} * * @param binder IBinder instance to allow cleanup if the app dies * @param callback Traffic State callback to register @@ -2890,4 +2892,61 @@ public class WifiServiceImpl extends IWifiManager.Stub { private static boolean hasAutomotiveFeature(Context context) { return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } + + /** + * see {@link android.net.wifi.WifiManager#registerNetworkRequestMatchCallback( + * WifiManager.NetworkRequestMatchCallback, Handler)} ( + * + * @param binder IBinder instance to allow cleanup if the app dies + * @param callback Network Request Match callback to register + * @param callbackIdentifier Unique ID of the registering callback. This ID will be used to + * unregister the callback. + * See {@link #unregisterNetworkRequestMatchCallback(int)} (int)} + * + * @throws SecurityException if the caller does not have permission to register a callback + * @throws RemoteException if remote exception happens + * @throws IllegalArgumentException if the arguments are null or invalid + */ + @Override + public void registerNetworkRequestMatchCallback(IBinder binder, + INetworkRequestMatchCallback callback, + int callbackIdentifier) { + // verify arguments + if (binder == null) { + throw new IllegalArgumentException("Binder must not be null"); + } + if (callback == null) { + throw new IllegalArgumentException("Callback must not be null"); + } + enforceNetworkSettingsPermission(); + if (mVerboseLoggingEnabled) { + mLog.info("registerNetworkRequestMatchCallback uid=%") + .c(Binder.getCallingUid()).flush(); + } + // Post operation to handler thread + mClientHandler.post(() -> { + mClientModeImpl.addNetworkRequestMatchCallback(binder, callback, callbackIdentifier); + }); + } + + /** + * see {@link android.net.wifi.WifiManager#unregisterNetworkRequestMatchCallback( + * WifiManager.NetworkRequestMatchCallback)} + * + * @param callbackIdentifier Unique ID of the callback to be unregistered. + * + * @throws SecurityException if the caller does not have permission to register a callback + */ + @Override + public void unregisterNetworkRequestMatchCallback(int callbackIdentifier) { + enforceNetworkSettingsPermission(); + if (mVerboseLoggingEnabled) { + mLog.info("unregisterNetworkRequestMatchCallback uid=%") + .c(Binder.getCallingUid()).flush(); + } + // Post operation to handler thread + mClientHandler.post(() -> { + mClientModeImpl.removeNetworkRequestMatchCallback(callbackIdentifier); + }); + } } diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java index cdc9590b9..97d7e4f22 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java @@ -425,12 +425,9 @@ public class WifiConfigurationTestUtil { } /** - * Creates a scan detail corresponding to the provided network and given BSSID, level &frequency - * values. + * Gets scan result capabilities for a particular network configuration. */ - public static ScanDetail createScanDetailForNetwork( - WifiConfiguration configuration, String bssid, int level, int frequency, - long tsf, long seen) { + public static String getScanResultCapsForNetwork(WifiConfiguration configuration) { String caps; if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) { caps = "[WPA2-PSK-CCMP]"; @@ -443,6 +440,16 @@ public class WifiConfigurationTestUtil { } else { caps = "[]"; } + return caps; + } + + /** + * Creates a scan detail corresponding to the provided network and given BSSID, etc. + */ + public static ScanDetail createScanDetailForNetwork( + WifiConfiguration configuration, String bssid, int level, int frequency, + long tsf, long seen) { + String caps = getScanResultCapsForNetwork(configuration); WifiSsid ssid = WifiSsid.createFromAsciiEncoded(configuration.getPrintableSsid()); return new ScanDetail(ssid, bssid, caps, level, frequency, tsf, seen); } diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java index 3774feca3..089ae425d 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java @@ -20,11 +20,15 @@ import static org.junit.Assert.*; import android.content.pm.UserInfo; import android.net.IpConfiguration; +import android.net.MacAddress; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiEnterpriseConfig; +import android.net.wifi.WifiNetworkSpecifier; import android.net.wifi.WifiScanner; +import android.os.PatternMatcher; import android.os.UserHandle; import android.support.test.filters.SmallTest; +import android.util.Pair; import org.junit.Test; @@ -42,6 +46,7 @@ public class WifiConfigurationUtilTest { static final int CURRENT_USER_ID = 0; static final int CURRENT_USER_MANAGED_PROFILE_USER_ID = 10; static final int OTHER_USER_ID = 11; + static final int TEST_UID = 10000; static final String TEST_SSID = "test_ssid"; static final String TEST_SSID_1 = "test_ssid_1"; static final String TEST_BSSID = "aa:aa:11:22:cc:dd"; @@ -490,6 +495,118 @@ public class WifiConfigurationUtilTest { } /** + * Verify that the validate method successfully validates good WifiNetworkSpecifier with + * only ssid pattern set. + */ + @Test + public void testValidateNetworkSpecifierPositiveCases_SsidPattern() { + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + new PatternMatcher(TEST_SSID, PatternMatcher.PATTERN_LITERAL), + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS), + WifiConfigurationTestUtil.createOpenNetwork(), + TEST_UID); + assertTrue(WifiConfigurationUtil.validateNetworkSpecifier(specifier)); + } + + /** + * Verify that the validate method successfully validates good WifiNetworkSpecifier with + * only bssid pattern set. + */ + @Test + public void testValidateNetworkSpecifierPositiveCases_BssidPattern() { + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + new PatternMatcher(".*", PatternMatcher.PATTERN_SIMPLE_GLOB), + Pair.create(MacAddress.fromString(TEST_BSSID), MacAddress.BROADCAST_ADDRESS), + WifiConfigurationTestUtil.createOpenNetwork(), + TEST_UID); + assertTrue(WifiConfigurationUtil.validateNetworkSpecifier(specifier)); + } + + /** + * Verify that the validate method successfully validates good WifiNetworkSpecifier with + * both ssid & bssid patterns set. + */ + @Test + public void testValidateNetworkSpecifierPositiveCases_BothSsidPatternAndBssidPattern() { + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + new PatternMatcher(TEST_SSID, PatternMatcher.PATTERN_LITERAL), + Pair.create(MacAddress.fromString(TEST_BSSID), MacAddress.BROADCAST_ADDRESS), + WifiConfigurationTestUtil.createOpenNetwork(), + TEST_UID); + assertTrue(WifiConfigurationUtil.validateNetworkSpecifier(specifier)); + } + + /** + * Verify that the validate method fails to validate WifiNetworkSpecifier with no + * ssid/bssid info. + */ + @Test + public void testValidateNetworkSpecifierNegativeCases_NoSsidBssid() { + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + new PatternMatcher(".*", PatternMatcher.PATTERN_SIMPLE_GLOB), + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS), + WifiConfigurationTestUtil.createOpenNetwork(), + TEST_UID); + assertFalse(WifiConfigurationUtil.validateNetworkSpecifier(specifier)); + } + + /** + * Verify that the validate method fails to validate WifiNetworkSpecifier with invalid SSID + * match pattern. + */ + @Test + public void testValidateNetworkSpecifierNegativeCases_MatchNoneSsidPattern() { + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + new PatternMatcher("", PatternMatcher.PATTERN_LITERAL), + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS), + WifiConfigurationTestUtil.createOpenNetwork(), + TEST_UID); + assertFalse(WifiConfigurationUtil.validateNetworkSpecifier(specifier)); + } + + /** + * Verify that the validate method fails to validate WifiNetworkSpecifier with illegal + * pattern. + */ + @Test + public void testValidateNetworkSpecifierNegativeCases_MatchNoneBssidPattern() { + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + new PatternMatcher(TEST_SSID, PatternMatcher.PATTERN_LITERAL), + Pair.create(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS), + WifiConfigurationTestUtil.createOpenNetwork(), + TEST_UID); + assertFalse(WifiConfigurationUtil.validateNetworkSpecifier(specifier)); + } + + /** + * Verify that the validate method fails to validate WifiNetworkSpecifier with illegal + * pattern. + */ + @Test + public void testValidateNetworkSpecifierNegativeCases_InvalidBssidPattern() { + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + new PatternMatcher(TEST_SSID, PatternMatcher.PATTERN_LITERAL), + Pair.create(MacAddress.fromString(TEST_BSSID), MacAddress.ALL_ZEROS_ADDRESS), + WifiConfigurationTestUtil.createOpenNetwork(), + TEST_UID); + assertFalse(WifiConfigurationUtil.validateNetworkSpecifier(specifier)); + } + + /** + * Verify that the validate method fails to validate WifiNetworkSpecifier with SSID pattern + * for hidden network. + */ + @Test + public void testValidateNetworkSpecifierNegativeCases_NoSsidPatternForHiddenNetwork() { + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + new PatternMatcher(TEST_SSID, PatternMatcher.PATTERN_PREFIX), + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS), + WifiConfigurationTestUtil.createOpenHiddenNetwork(), + TEST_UID); + assertFalse(WifiConfigurationUtil.validateNetworkSpecifier(specifier)); + } + + /** * Verify the instance of {@link android.net.wifi.WifiScanner.PnoSettings.PnoNetwork} created * for an open network using {@link WifiConfigurationUtil#createPnoNetwork( * WifiConfiguration)}. diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java index 46519e519..eaaaf5704 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java @@ -16,31 +16,91 @@ package com.android.server.wifi; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; + +import static com.android.server.wifi.WifiNetworkFactory.PERIODIC_SCAN_INTERVAL_MS; +import static com.android.server.wifi.util.NativeUtil.addEnclosingQuotes; + import static org.junit.Assert.*; import static org.mockito.Mockito.*; +import android.app.ActivityManager; +import android.app.AlarmManager; +import android.app.AlarmManager.OnAlarmListener; import android.content.Context; +import android.content.pm.PackageManager; +import android.net.MacAddress; import android.net.NetworkCapabilities; import android.net.NetworkRequest; +import android.net.wifi.INetworkRequestMatchCallback; +import android.net.wifi.INetworkRequestUserSelectionCallback; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiNetworkSpecifier; +import android.net.wifi.WifiScanner; +import android.net.wifi.WifiScanner.ScanListener; +import android.net.wifi.WifiScanner.ScanSettings; +import android.os.IBinder; +import android.os.PatternMatcher; +import android.os.WorkSource; import android.os.test.TestLooper; import android.test.suitebuilder.annotation.SmallTest; +import android.util.Pair; + +import com.android.server.wifi.util.WifiPermissionsUtil; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + /** * Unit tests for {@link com.android.server.wifi.WifiNetworkFactory}. */ @SmallTest public class WifiNetworkFactoryTest { + private static final int TEST_UID_1 = 10423; + private static final int TEST_UID_2 = 10424; + private static final int TEST_CALLBACK_IDENTIFIER = 123; + private static final String TEST_PACKAGE_NAME_1 = "com.test.networkrequest.1"; + private static final String TEST_PACKAGE_NAME_2 = "com.test.networkrequest.2"; + private static final String TEST_SSID_1 = "test1234"; + private static final String TEST_SSID_2 = "test12345678"; + private static final String TEST_SSID_3 = "abc1234"; + private static final String TEST_SSID_4 = "abc12345678"; + private static final String TEST_BSSID_1 = "12:34:23:23:45:ac"; + private static final String TEST_BSSID_2 = "12:34:23:32:12:67"; + private static final String TEST_BSSID_3 = "45:34:34:12:bb:dd"; + private static final String TEST_BSSID_4 = "45:34:34:56:ee:ff"; + private static final String TEST_BSSID_1_2_OUI = "12:34:23:00:00:00"; + private static final String TEST_BSSID_OUI_MASK = "ff:ff:ff:00:00:00"; + @Mock WifiConnectivityManager mWifiConnectivityManager; @Mock Context mContext; + @Mock ActivityManager mActivityManager; + @Mock AlarmManager mAlarmManager; + @Mock Clock mClock; + @Mock WifiInjector mWifiInjector; + @Mock WifiPermissionsUtil mWifiPermissionsUtil; + @Mock WifiScanner mWifiScanner; + @Mock PackageManager mPackageManager; + @Mock IBinder mAppBinder; + @Mock INetworkRequestMatchCallback mNetworkRequestMatchCallback; NetworkCapabilities mNetworkCapabilities; TestLooper mLooper; NetworkRequest mNetworkRequest; + WifiScanner.ScanData[] mTestScanDatas; + ArgumentCaptor<ScanSettings> mScanSettingsArgumentCaptor = + ArgumentCaptor.forClass(ScanSettings.class); + ArgumentCaptor<WorkSource> mWorkSourceArgumentCaptor = + ArgumentCaptor.forClass(WorkSource.class); private WifiNetworkFactory mWifiNetworkFactory; @@ -54,9 +114,20 @@ public class WifiNetworkFactoryTest { mLooper = new TestLooper(); mNetworkCapabilities = new NetworkCapabilities(); mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + mTestScanDatas = ScanTestUtil.createScanDatas(new int[][]{ { 2417, 2427, 5180, 5170 } }); + + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.getNameForUid(TEST_UID_1)).thenReturn(TEST_PACKAGE_NAME_1); + when(mPackageManager.getNameForUid(TEST_UID_2)).thenReturn(TEST_PACKAGE_NAME_2); + when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_1)) + .thenReturn(IMPORTANCE_FOREGROUND_SERVICE); + when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_2)) + .thenReturn(IMPORTANCE_FOREGROUND_SERVICE); + when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner); mWifiNetworkFactory = new WifiNetworkFactory(mLooper.getLooper(), mContext, - mNetworkCapabilities, mWifiConnectivityManager); + mNetworkCapabilities, mActivityManager, mAlarmManager, mClock, mWifiInjector, + mWifiConnectivityManager, mWifiPermissionsUtil); mNetworkRequest = new NetworkRequest.Builder() .setCapabilities(mNetworkCapabilities) @@ -75,7 +146,7 @@ public class WifiNetworkFactoryTest { * Validates handling of needNetworkFor. */ @Test - public void testHandleNetworkRequest() { + public void testHandleNetworkRequestWithNoSpecifier() { assertFalse(mWifiNetworkFactory.hasConnectionRequests()); mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); @@ -92,7 +163,7 @@ public class WifiNetworkFactoryTest { * Validates handling of releaseNetwork. */ @Test - public void testHandleNetworkRelease() { + public void testHandleNetworkReleaseWithNoSpecifier() { // Release network with out a corresponding request should be ignored. mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest); assertFalse(mWifiNetworkFactory.hasConnectionRequests()); @@ -105,4 +176,706 @@ public class WifiNetworkFactoryTest { assertFalse(mWifiNetworkFactory.hasConnectionRequests()); verify(mWifiConnectivityManager).setTrustedConnectionAllowed(false); } + + /** + * Validates handling of acceptNetwork for requests with no network specifier. + */ + @Test + public void testHandleAcceptNetworkRequestWithNoSpecifier() { + assertTrue(mWifiNetworkFactory.acceptRequest(mNetworkRequest, 0)); + } + + /** + * Validates handling of acceptNetwork with a network specifier from a non foreground + * app/service. + */ + @Test + public void testHandleAcceptNetworkRequestFromNonFgAppOrSvcWithSpecifier() { + when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_1)) + .thenReturn(IMPORTANCE_FOREGROUND_SERVICE + 1); + + WifiNetworkSpecifier specifier = createWifiNetworkSpecifier(TEST_UID_1, false); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + + assertFalse(mWifiNetworkFactory.acceptRequest(mNetworkRequest, 0)); + } + + /** + * Validates handling of acceptNetwork with a network specifier from a foreground + * app. + */ + @Test + public void testHandleAcceptNetworkRequestFromFgAppWithSpecifier() { + when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_1)) + .thenReturn(IMPORTANCE_FOREGROUND); + + WifiNetworkSpecifier specifier = createWifiNetworkSpecifier(TEST_UID_1, false); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + + assertTrue(mWifiNetworkFactory.acceptRequest(mNetworkRequest, 0)); + } + + /** + * Validates handling of acceptNetwork with a network specifier from apps holding + * NETWORK_SETTINGS. + */ + @Test + public void testHandleAcceptNetworkRequestFromNetworkSettingAppWithSpecifier() { + when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_1)) + .thenReturn(IMPORTANCE_GONE); + when(mWifiPermissionsUtil.checkNetworkSettingsPermission(TEST_UID_1)) + .thenReturn(true); + + WifiNetworkSpecifier specifier = createWifiNetworkSpecifier(TEST_UID_1, false); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + + assertTrue(mWifiNetworkFactory.acceptRequest(mNetworkRequest, 0)); + } + + /** + * Validates handling of acceptNetwork with a network specifier from a foreground + * app. + */ + @Test + public void testHandleAcceptNetworkRequestFromFgAppWithSpecifierWithPendingRequestFromFgSvc() { + when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_1)) + .thenReturn(IMPORTANCE_FOREGROUND_SERVICE); + when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_2)) + .thenReturn(IMPORTANCE_FOREGROUND); + + // Handle request 1. + WifiNetworkSpecifier specifier1 = createWifiNetworkSpecifier(TEST_UID_1, false); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier1); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + // Make request 2 which will be accepted because a fg app request can + // override a fg service request. + WifiNetworkSpecifier specifier2 = createWifiNetworkSpecifier(TEST_UID_2, false); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier2); + assertTrue(mWifiNetworkFactory.acceptRequest(mNetworkRequest, 0)); + } + + /** + * Validates handling of acceptNetwork with a network specifier from a foreground + * app. + */ + @Test + public void testHandleAcceptNetworkRequestFromFgSvcWithSpecifierWithPendingRequestFromFgSvc() { + when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_1)) + .thenReturn(IMPORTANCE_FOREGROUND_SERVICE); + when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_2)) + .thenReturn(IMPORTANCE_FOREGROUND_SERVICE); + + // Handle request 1. + WifiNetworkSpecifier specifier1 = createWifiNetworkSpecifier(TEST_UID_1, false); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier1); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + // Make request 2 which will be accepted because a fg service request can + // override an existing fg service request. + WifiNetworkSpecifier specifier2 = createWifiNetworkSpecifier(TEST_UID_2, false); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier2); + assertTrue(mWifiNetworkFactory.acceptRequest(mNetworkRequest, 0)); + } + + /** + * Validates handling of acceptNetwork with a network specifier from a foreground + * app. + */ + @Test + public void testHandleAcceptNetworkRequestFromFgAppWithSpecifierWithPendingRequestFromFgApp() { + when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_1)) + .thenReturn(IMPORTANCE_FOREGROUND); + when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_2)) + .thenReturn(IMPORTANCE_FOREGROUND); + + // Handle request 1. + WifiNetworkSpecifier specifier1 = createWifiNetworkSpecifier(TEST_UID_1, false); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier1); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + // Make request 2 which will be accepted because a fg app request can + // override an existing fg app request. + WifiNetworkSpecifier specifier2 = createWifiNetworkSpecifier(TEST_UID_2, false); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier2); + assertTrue(mWifiNetworkFactory.acceptRequest(mNetworkRequest, 0)); + } + + /** + * Validates handling of acceptNetwork with a network specifier from a foreground + * service when we're in the midst of processing a request from a foreground app. + */ + @Test + public void testHandleAcceptNetworkRequestFromFgSvcWithSpecifierWithPendingRequestFromFgApp() { + when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_1)) + .thenReturn(IMPORTANCE_FOREGROUND); + when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_2)) + .thenReturn(IMPORTANCE_FOREGROUND_SERVICE); + + // Handle request 1. + WifiNetworkSpecifier specifier1 = createWifiNetworkSpecifier(TEST_UID_1, false); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier1); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + // Make request 2 which will be rejected because a fg service request cannot + // override a fg app request. + WifiNetworkSpecifier specifier2 = createWifiNetworkSpecifier(TEST_UID_2, false); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier2); + assertFalse(mWifiNetworkFactory.acceptRequest(mNetworkRequest, 0)); + } + + /** + * Verify handling of new network request with network specifier. + */ + @Test + public void testHandleNetworkRequestWithSpecifier() { + WifiNetworkSpecifier specifier = createWifiNetworkSpecifier(TEST_UID_1, false); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + // Disable connectivity manager . + verify(mWifiConnectivityManager).enable(false); + verify(mWifiScanner).startScan(mScanSettingsArgumentCaptor.capture(), any(), + mWorkSourceArgumentCaptor.capture()); + + // Verify scan settings. + ScanSettings scanSettings = mScanSettingsArgumentCaptor.getValue(); + assertNotNull(scanSettings); + assertEquals(WifiScanner.WIFI_BAND_BOTH_WITH_DFS, scanSettings.band); + assertEquals(WifiScanner.TYPE_HIGH_ACCURACY, scanSettings.type); + assertEquals(WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN, scanSettings.reportEvents); + assertNull(scanSettings.hiddenNetworks); + WorkSource workSource = mWorkSourceArgumentCaptor.getValue(); + assertNotNull(workSource); + assertEquals(TEST_UID_1, workSource.get(0)); + } + + /** + * Verify handling of new network request with network specifier for a hidden network. + */ + @Test + public void testHandleNetworkRequestWithSpecifierForHiddenNetwork() { + WifiNetworkSpecifier specifier = createWifiNetworkSpecifier(TEST_UID_1, true); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + // Disable connectivity manager . + verify(mWifiConnectivityManager).enable(false); + verify(mWifiScanner).startScan(mScanSettingsArgumentCaptor.capture(), any(), + mWorkSourceArgumentCaptor.capture()); + + // Verify scan settings. + ScanSettings scanSettings = mScanSettingsArgumentCaptor.getValue(); + assertNotNull(scanSettings); + assertEquals(WifiScanner.WIFI_BAND_BOTH_WITH_DFS, scanSettings.band); + assertEquals(WifiScanner.TYPE_HIGH_ACCURACY, scanSettings.type); + assertEquals(WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN, scanSettings.reportEvents); + assertNotNull(scanSettings.hiddenNetworks); + assertNotNull(scanSettings.hiddenNetworks[0]); + assertEquals(scanSettings.hiddenNetworks[0].ssid, + addEnclosingQuotes(specifier.ssidPatternMatcher.getPath())); + WorkSource workSource = mWorkSourceArgumentCaptor.getValue(); + assertNotNull(workSource); + assertEquals(TEST_UID_1, workSource.get(0)); + } + + /** + * Verify handling of new network request with network specifier for a non-hidden network + * after processing a previous hidden network requst. + * Validates that the scan settings was properly reset between the 2 request + * {@link ScanSettings#hiddenNetworks} + */ + @Test + public void testHandleNetworkRequestWithSpecifierAfterPreviousHiddenNetworkRequest() { + testHandleNetworkRequestWithSpecifierForHiddenNetwork(); + mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest); + reset(mWifiScanner, mWifiConnectivityManager); + testHandleNetworkRequestWithSpecifier(); + } + + /** + * Verify handling of release of the active network request with network specifier. + */ + @Test + public void testHandleNetworkReleaseWithSpecifier() { + // Make a generic request first to ensure that we re-enable auto-join after release. + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + WifiNetworkSpecifier specifier = createWifiNetworkSpecifier(TEST_UID_1, false); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + + // Make the network request with specifier. + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + // Disable connectivity manager . + verify(mWifiConnectivityManager).enable(false); + verify(mWifiScanner).startScan(any(), any(), any()); + + // Release the network request. + mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest); + // Re-enable connectivity manager . + verify(mWifiConnectivityManager).enable(true); + } + + /** + * Verify the periodic scan to find a network matching the network specifier. + * Simulates the case where the network is not found in any of the scan results. + */ + @Test + public void testPeriodicScanNetworkRequestWithSpecifier() { + WifiNetworkSpecifier specifier = createWifiNetworkSpecifier(TEST_UID_1, false); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + verify(mWifiConnectivityManager).enable(false); + verifyPeriodicScans(0, + PERIODIC_SCAN_INTERVAL_MS, // 10s + PERIODIC_SCAN_INTERVAL_MS, // 10s + PERIODIC_SCAN_INTERVAL_MS, // 10s + PERIODIC_SCAN_INTERVAL_MS); // 10s + } + + /** + * Verify the periodic scan back off to find a network matching the network specifier + * is cancelled when the active network request is released. + */ + @Test + public void testPeriodicScanCancelOnReleaseNetworkRequestWithSpecifier() { + WifiNetworkSpecifier specifier = createWifiNetworkSpecifier(TEST_UID_1, false); + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + verify(mWifiConnectivityManager).enable(false); + verifyPeriodicScans(0, + PERIODIC_SCAN_INTERVAL_MS, // 10s + PERIODIC_SCAN_INTERVAL_MS); // 10s + + mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest); + // Cancel the alarm set for the next scan. + verify(mAlarmManager).cancel(any(OnAlarmListener.class)); + } + + /** + * Verify callback registration/unregistration. + */ + @Test + public void testHandleCallbackRegistrationAndUnregistration() throws Exception { + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + + //Ensure that we register the user selection callback using the newly registered callback. + verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration( + any(INetworkRequestUserSelectionCallback.class)); + + // TBD: Need to hook up the matching logic to invoke these callbacks to actually + // verify that they're in the database. + mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER); + } + + /** + * Verify network specifier matching for a specifier containing a specific SSID match using + * 4 WPA_PSK scan results, each with unique SSID. + */ + @Test + public void testNetworkSpecifierMatchSuccessUsingLiteralSsidMatch() throws Exception { + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + // Setup scan data for open networks. + setupScanData(SCAN_RESULT_TYPE_WPA_PSK, + TEST_SSID_1, TEST_SSID_2, TEST_SSID_3, TEST_SSID_4); + + // Setup network specifier for open networks. + PatternMatcher ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL); + Pair<MacAddress, MacAddress> bssidPatternMatch = + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1); + + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + verify(mWifiConnectivityManager).enable(false); + verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); + + ArgumentCaptor<List<WifiConfiguration>> matchedNetworksCaptor = + ArgumentCaptor.forClass(List.class); + verify(mNetworkRequestMatchCallback).onMatch(matchedNetworksCaptor.capture()); + + assertNotNull(matchedNetworksCaptor.getValue()); + // We only expect 1 network match in this case. + assertEquals(1, matchedNetworksCaptor.getValue().size()); + WifiConfiguration matchedNetwork = matchedNetworksCaptor.getValue().get(0); + assertEquals("\"" + TEST_SSID_1 + "\"", matchedNetwork.SSID); + assertEquals(TEST_BSSID_1, matchedNetwork.BSSID); + assertTrue(matchedNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)); + } + + /** + * Verify network specifier matching for a specifier containing a Prefix SSID match using + * 4 open scan results, each with unique SSID. + */ + @Test + public void testNetworkSpecifierMatchSuccessUsingPrefixSsidMatch() throws Exception { + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + // Setup scan data for open networks. + setupScanData(SCAN_RESULT_TYPE_OPEN, + TEST_SSID_1, TEST_SSID_2, TEST_SSID_3, TEST_SSID_4); + + // Setup network specifier for open networks. + PatternMatcher ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_PREFIX); + Pair<MacAddress, MacAddress> bssidPatternMatch = + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1); + + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + verify(mWifiConnectivityManager).enable(false); + verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); + + ArgumentCaptor<List<WifiConfiguration>> matchedNetworksCaptor = + ArgumentCaptor.forClass(List.class); + verify(mNetworkRequestMatchCallback).onMatch(matchedNetworksCaptor.capture()); + + assertNotNull(matchedNetworksCaptor.getValue()); + // We expect 2 network matches in this case. + assertEquals(2, matchedNetworksCaptor.getValue().size()); + for (WifiConfiguration matchedNetwork : matchedNetworksCaptor.getValue()) { + if (matchedNetwork.SSID.equals("\"" + TEST_SSID_1 + "\"")) { + assertEquals(TEST_BSSID_1, matchedNetwork.BSSID); + assertTrue(matchedNetwork.allowedKeyManagement + .get(WifiConfiguration.KeyMgmt.NONE)); + } else if (matchedNetwork.SSID.equals("\"" + TEST_SSID_2 + "\"")) { + assertEquals(TEST_BSSID_2, matchedNetwork.BSSID); + assertTrue(matchedNetwork.allowedKeyManagement + .get(WifiConfiguration.KeyMgmt.NONE)); + } else { + fail(); + } + } + } + + /** + * Verify network specifier matching for a specifier containing a specific BSSID match using + * 4 WPA_PSK scan results, each with unique SSID. + */ + @Test + public void testNetworkSpecifierMatchSuccessUsingLiteralBssidMatch() throws Exception { + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + // Setup scan data for open networks. + setupScanData(SCAN_RESULT_TYPE_WPA_PSK, + TEST_SSID_1, TEST_SSID_2, TEST_SSID_3, TEST_SSID_4); + + // Setup network specifier for open networks. + PatternMatcher ssidPatternMatch = + new PatternMatcher(".*", PatternMatcher.PATTERN_SIMPLE_GLOB); + Pair<MacAddress, MacAddress> bssidPatternMatch = + Pair.create(MacAddress.fromString(TEST_BSSID_1), MacAddress.BROADCAST_ADDRESS); + WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1); + + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + verify(mWifiConnectivityManager).enable(false); + verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); + + ArgumentCaptor<List<WifiConfiguration>> matchedNetworksCaptor = + ArgumentCaptor.forClass(List.class); + verify(mNetworkRequestMatchCallback).onMatch(matchedNetworksCaptor.capture()); + + assertNotNull(matchedNetworksCaptor.getValue()); + // We only expect 1 network match in this case. + assertEquals(1, matchedNetworksCaptor.getValue().size()); + WifiConfiguration matchedNetwork = matchedNetworksCaptor.getValue().get(0); + assertEquals("\"" + TEST_SSID_1 + "\"", matchedNetwork.SSID); + assertEquals(TEST_BSSID_1, matchedNetwork.BSSID); + assertTrue(matchedNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)); + } + + /** + * Verify network specifier matching for a specifier containing a prefix BSSID match using + * 4 WPA_EAP scan results, each with unique SSID. + */ + @Test + public void testNetworkSpecifierMatchSuccessUsingOuiPrefixBssidMatch() throws Exception { + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + // Setup scan data for open networks. + setupScanData(SCAN_RESULT_TYPE_WPA_EAP, + TEST_SSID_1, TEST_SSID_2, TEST_SSID_3, TEST_SSID_4); + + // Setup network specifier for open networks. + PatternMatcher ssidPatternMatch = + new PatternMatcher(".*", PatternMatcher.PATTERN_SIMPLE_GLOB); + Pair<MacAddress, MacAddress> bssidPatternMatch = + Pair.create(MacAddress.fromString(TEST_BSSID_1_2_OUI), + MacAddress.fromString(TEST_BSSID_OUI_MASK)); + WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP); + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1); + + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + verify(mWifiConnectivityManager).enable(false); + verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); + + ArgumentCaptor<List<WifiConfiguration>> matchedNetworksCaptor = + ArgumentCaptor.forClass(List.class); + verify(mNetworkRequestMatchCallback).onMatch(matchedNetworksCaptor.capture()); + + assertNotNull(matchedNetworksCaptor.getValue()); + // We expect 2 network matches in this case. + assertEquals(2, matchedNetworksCaptor.getValue().size()); + for (WifiConfiguration matchedNetwork : matchedNetworksCaptor.getValue()) { + if (matchedNetwork.SSID.equals("\"" + TEST_SSID_1 + "\"")) { + assertEquals(TEST_BSSID_1, matchedNetwork.BSSID); + assertTrue(matchedNetwork.allowedKeyManagement + .get(WifiConfiguration.KeyMgmt.WPA_EAP)); + } else if (matchedNetwork.SSID.equals("\"" + TEST_SSID_2 + "\"")) { + assertEquals(TEST_BSSID_2, matchedNetwork.BSSID); + assertTrue(matchedNetwork.allowedKeyManagement + .get(WifiConfiguration.KeyMgmt.WPA_EAP)); + } else { + fail(); + } + } + } + + /** + * Verify network specifier matching for a specifier containing a specific SSID match using + * 4 WPA_PSK scan results, 3 of which have the same SSID. + */ + @Test + public void testNetworkSpecifierMatchSuccessUsingLiteralSsidMatchWithMultipleBssidMatches() + throws Exception { + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + // Setup scan data for open networks. + setupScanData(SCAN_RESULT_TYPE_WPA_PSK, + TEST_SSID_1, TEST_SSID_1, TEST_SSID_1, TEST_SSID_2); + + // Setup network specifier for open networks. + PatternMatcher ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL); + Pair<MacAddress, MacAddress> bssidPatternMatch = + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1); + + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + verify(mWifiConnectivityManager).enable(false); + verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); + + ArgumentCaptor<List<WifiConfiguration>> matchedNetworksCaptor = + ArgumentCaptor.forClass(List.class); + verify(mNetworkRequestMatchCallback).onMatch(matchedNetworksCaptor.capture()); + + assertNotNull(matchedNetworksCaptor.getValue()); + // We still only expect 1 network match in this case. + assertEquals(1, matchedNetworksCaptor.getValue().size()); + WifiConfiguration matchedNetwork = matchedNetworksCaptor.getValue().get(0); + assertEquals("\"" + TEST_SSID_1 + "\"", matchedNetwork.SSID); + assertTrue(TEST_BSSID_1.equals(matchedNetwork.BSSID) + || TEST_BSSID_2.equals(matchedNetwork.BSSID) + || TEST_BSSID_3.equals(matchedNetwork.BSSID)); + assertTrue(matchedNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)); + } + + /** + * Verify network specifier match failure for a specifier containing a specific SSID match using + * 4 WPA_PSK scan results, 2 of which SSID_1 and the other 2 SSID_2. But, none of the scan + * results' SSID match the one requested in the specifier. + */ + @Test + public void testNetworkSpecifierMatchFailUsingLiteralSsidMatchWhenSsidNotFound() + throws Exception { + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + // Setup scan data for open networks. + setupScanData(SCAN_RESULT_TYPE_WPA_PSK, + TEST_SSID_1, TEST_SSID_1, TEST_SSID_2, TEST_SSID_2); + + // Setup network specifier for open networks. + PatternMatcher ssidPatternMatch = + new PatternMatcher(TEST_SSID_3, PatternMatcher.PATTERN_LITERAL); + Pair<MacAddress, MacAddress> bssidPatternMatch = + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1); + + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + verify(mWifiConnectivityManager).enable(false); + verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); + + ArgumentCaptor<List<WifiConfiguration>> matchedNetworksCaptor = + ArgumentCaptor.forClass(List.class); + verify(mNetworkRequestMatchCallback).onMatch(matchedNetworksCaptor.capture()); + + assertNotNull(matchedNetworksCaptor.getValue()); + // We expect no network match in this case. + assertEquals(0, matchedNetworksCaptor.getValue().size()); + } + + /** + * Verify network specifier match failure for a specifier containing a specific SSID match using + * 4 open scan results, each with unique SSID. But, none of the scan + * results' key mgmt match the one requested in the specifier. + */ + @Test + public void testNetworkSpecifierMatchFailUsingLiteralSsidMatchWhenKeyMgmtDiffers() + throws Exception { + mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback, + TEST_CALLBACK_IDENTIFIER); + // Setup scan data for open networks. + setupScanData(SCAN_RESULT_TYPE_OPEN, + TEST_SSID_1, TEST_SSID_2, TEST_SSID_3, TEST_SSID_4); + + // Setup network specifier for open networks. + PatternMatcher ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_PREFIX); + Pair<MacAddress, MacAddress> bssidPatternMatch = + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + WifiNetworkSpecifier specifier = new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1); + + mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier); + mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0); + + verify(mWifiConnectivityManager).enable(false); + verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS); + + ArgumentCaptor<List<WifiConfiguration>> matchedNetworksCaptor = + ArgumentCaptor.forClass(List.class); + verify(mNetworkRequestMatchCallback).onMatch(matchedNetworksCaptor.capture()); + + assertNotNull(matchedNetworksCaptor.getValue()); + // We expect no network match in this case. + assertEquals(0, matchedNetworksCaptor.getValue().size()); + } + + // Simulates the periodic scans performed to find a matching network. + // a) Start scan + // b) Scan results received. + // c) Set alarm for next scan at the expected interval. + // d) Alarm fires, go to step a) again and repeat. + private void verifyPeriodicScans(long...expectedIntervalsInSeconds) { + when(mClock.getElapsedSinceBootMillis()).thenReturn(0L); + + ArgumentCaptor<OnAlarmListener> alarmListenerArgumentCaptor = + ArgumentCaptor.forClass(OnAlarmListener.class); + OnAlarmListener alarmListener = null; + ArgumentCaptor<ScanListener> scanListenerArgumentCaptor = + ArgumentCaptor.forClass(ScanListener.class); + ScanListener scanListener = null; + + InOrder inOrder = inOrder(mWifiScanner, mAlarmManager); + + for (int i = 0; i < expectedIntervalsInSeconds.length - 1; i++) { + long expectedCurrentIntervalInMs = expectedIntervalsInSeconds[i]; + long expectedNextIntervalInMs = expectedIntervalsInSeconds[i + 1]; + + // First scan is immediately fired, so need for the alarm to fire. + if (expectedCurrentIntervalInMs != 0) { + // Fire the alarm and ensure that we started the next scan. + alarmListener.onAlarm(); + } + inOrder.verify(mWifiScanner).startScan( + any(), scanListenerArgumentCaptor.capture(), any()); + scanListener = scanListenerArgumentCaptor.getValue(); + assertNotNull(scanListener); + + // Now trigger the scan results callback and verify the alarm set for the next scan. + scanListener.onResults(mTestScanDatas); + + inOrder.verify(mAlarmManager).set(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), + eq(expectedNextIntervalInMs), any(), + alarmListenerArgumentCaptor.capture(), any()); + alarmListener = alarmListenerArgumentCaptor.getValue(); + assertNotNull(alarmListener); + } + + verifyNoMoreInteractions(mWifiScanner, mAlarmManager); + } + + private WifiNetworkSpecifier createWifiNetworkSpecifier(int uid, boolean isHidden) { + PatternMatcher ssidPatternMatch = + new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL); + Pair<MacAddress, MacAddress> bssidPatternMatch = + Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + WifiConfiguration wifiConfiguration; + if (isHidden) { + wifiConfiguration = WifiConfigurationTestUtil.createPskHiddenNetwork(); + } else { + wifiConfiguration = WifiConfigurationTestUtil.createPskNetwork(); + } + return new WifiNetworkSpecifier( + ssidPatternMatch, bssidPatternMatch, wifiConfiguration, uid); + } + + private static final int SCAN_RESULT_TYPE_OPEN = 0; + private static final int SCAN_RESULT_TYPE_WPA_PSK = 1; + private static final int SCAN_RESULT_TYPE_WPA_EAP = 2; + + private String getScanResultCapsForType(int scanResultType) { + switch (scanResultType) { + case SCAN_RESULT_TYPE_OPEN: + return WifiConfigurationTestUtil.getScanResultCapsForNetwork( + WifiConfigurationTestUtil.createOpenNetwork()); + case SCAN_RESULT_TYPE_WPA_PSK: + return WifiConfigurationTestUtil.getScanResultCapsForNetwork( + WifiConfigurationTestUtil.createPskNetwork()); + case SCAN_RESULT_TYPE_WPA_EAP: + return WifiConfigurationTestUtil.getScanResultCapsForNetwork( + WifiConfigurationTestUtil.createEapNetwork()); + } + fail("Invalid scan result type " + scanResultType); + return ""; + } + + // Helper method to setup the scan data for verifying the matching algo. + private void setupScanData(int scanResultType, String ssid1, String ssid2, String ssid3, + String ssid4) { + // 4 scan results, + assertEquals(1, mTestScanDatas.length); + ScanResult[] scanResults = mTestScanDatas[0].getResults(); + assertEquals(4, scanResults.length); + + String caps = getScanResultCapsForType(scanResultType); + + scanResults[0].SSID = ssid1; + scanResults[0].BSSID = TEST_BSSID_1; + scanResults[0].capabilities = caps; + scanResults[1].SSID = ssid2; + scanResults[1].BSSID = TEST_BSSID_2; + scanResults[1].capabilities = caps; + scanResults[2].SSID = ssid3; + scanResults[2].BSSID = TEST_BSSID_3; + scanResults[2].capabilities = caps; + scanResults[3].SSID = ssid4; + scanResults[3].BSSID = TEST_BSSID_4; + scanResults[3].capabilities = caps; + } } diff --git a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java index b4c7014ba..171b1a907 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java @@ -66,6 +66,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.net.Uri; +import android.net.wifi.INetworkRequestMatchCallback; import android.net.wifi.ISoftApCallback; import android.net.wifi.ITrafficStateCallback; import android.net.wifi.ScanResult; @@ -139,6 +140,7 @@ public class WifiServiceImplTest { private static final int OTHER_TEST_UID = 1300000; private static final int TEST_USER_HANDLE = 13; private static final int TEST_TRAFFIC_STATE_CALLBACK_IDENTIFIER = 17; + private static final int TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER = 234; private static final String WIFI_IFACE_NAME = "wlan0"; private static final String TEST_COUNTRY_CODE = "US"; @@ -202,6 +204,7 @@ public class WifiServiceImplTest { @Mock WifiTrafficPoller mWifiTrafficPolller; @Mock ScanRequestProxy mScanRequestProxy; @Mock ITrafficStateCallback mTrafficStateCallback; + @Mock INetworkRequestMatchCallback mNetworkRequestMatchCallback; @Spy FakeWifiLog mLog; @@ -3009,4 +3012,82 @@ public class WifiServiceImplTest { mLooper.dispatchAll(); verify(mWifiTrafficPoller).removeCallback(0); } + + /** + * Verify that a call to registerNetworkRequestMatchCallback throws a SecurityException if the + * caller does not have NETWORK_SETTINGS permission. + */ + @Test + public void registerNetworkRequestMatchCallbackThrowsSecurityExceptionOnMissingPermissions() { + doThrow(new SecurityException()).when(mContext) + .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS), + eq("WifiService")); + try { + mWifiServiceImpl.registerNetworkRequestMatchCallback(mAppBinder, + mNetworkRequestMatchCallback, + TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER); + fail("expected SecurityException"); + } catch (SecurityException expected) { + } + } + + /** + * Verify that a call to registerNetworkRequestMatchCallback throws an IllegalArgumentException + * if the parameters are not provided. + */ + @Test + public void + registerNetworkRequestMatchCallbackThrowsIllegalArgumentExceptionOnInvalidArguments() { + try { + mWifiServiceImpl.registerNetworkRequestMatchCallback( + mAppBinder, null, TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + } + + /** + * Verify that a call to unregisterNetworkRequestMatchCallback throws a SecurityException if the + * caller does not have NETWORK_SETTINGS permission. + */ + @Test + public void unregisterNetworkRequestMatchCallbackThrowsSecurityExceptionOnMissingPermissions() { + doThrow(new SecurityException()).when(mContext) + .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS), + eq("WifiService")); + try { + mWifiServiceImpl.unregisterNetworkRequestMatchCallback( + TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER); + fail("expected SecurityException"); + } catch (SecurityException expected) { + } + } + + /** + * Verify that registerNetworkRequestMatchCallback adds callback to + * {@link ClientModeImpl}. + */ + @Test + public void registerNetworkRequestMatchCallbackAndVerify() throws Exception { + mWifiServiceImpl.registerNetworkRequestMatchCallback( + mAppBinder, mNetworkRequestMatchCallback, + TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER); + mLooper.dispatchAll(); + verify(mClientModeImpl).addNetworkRequestMatchCallback( + mAppBinder, mNetworkRequestMatchCallback, + TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER); + } + + /** + * Verify that unregisterNetworkRequestMatchCallback removes callback from + * {@link ClientModeImpl}. + */ + @Test + public void unregisterNetworkRequestMatchCallbackAndVerify() throws Exception { + mWifiServiceImpl.unregisterNetworkRequestMatchCallback( + TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER); + mLooper.dispatchAll(); + verify(mClientModeImpl).removeNetworkRequestMatchCallback( + TEST_NETWORK_REQUEST_MATCH_CALLBACK_IDENTIFIER); + } } |