diff options
author | Arc Wang <arcwang@google.com> | 2020-01-09 05:52:01 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2020-01-09 05:52:01 +0000 |
commit | fbc0dcb846cc397d5388f4a1080a900590a6a4a2 (patch) | |
tree | 45f39d13f2e9c65bd03559413605bea4c0f0a68b | |
parent | c0d7e7318edaf31fd921472b0c0802457eb0c754 (diff) | |
parent | 059413165f41ac22f6ffd290fc649044252d043b (diff) |
Merge changes Idaffbd88,Ia736951a,Ied9d85cc
* changes:
Implement WifiEntry.getConnectedInfo()
Implement mac randomization privacy setting APIs in StandardWifiEntry
Add Provisioned Passpoint R1/R2 entries to Wifi Picker
8 files changed, 714 insertions, 93 deletions
diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/BaseWifiTracker.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/BaseWifiTracker.java index 28a2eb652..e52622e3c 100644 --- a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/BaseWifiTracker.java +++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/BaseWifiTracker.java @@ -16,11 +16,16 @@ package com.android.wifitrackerlib; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkRequest; import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; import android.os.Handler; @@ -30,6 +35,7 @@ import android.util.Log; import androidx.annotation.AnyThread; import androidx.annotation.MainThread; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; @@ -118,6 +124,18 @@ public class BaseWifiTracker implements LifecycleObserver { protected final long mScanIntervalMillis; protected final ScanResultUpdater mScanResultUpdater; + // Network request for listening on changes to Wifi link properties. + private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() + .clearCapabilities().addTransportType(TRANSPORT_WIFI).build(); + + private final ConnectivityManager.NetworkCallback mNetworkCallback = + new ConnectivityManager.NetworkCallback() { + @Override + public void onLinkPropertiesChanged(Network network, LinkProperties lp) { + handleLinkPropertiesChanged(lp); + } + }; + /** * Constructor for BaseWifiTracker. * @@ -175,6 +193,8 @@ public class BaseWifiTracker implements LifecycleObserver { filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); mContext.registerReceiver(mBroadcastReceiver, filter, /* broadcastPermission */ null, mWorkerHandler); + mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback, + mWorkerHandler); if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED) { mScanner.start(); } else { @@ -192,6 +212,7 @@ public class BaseWifiTracker implements LifecycleObserver { // TODO (b/70983952): Unregister score cache and receivers for network callbacks. mScanner.stop(); mContext.unregisterReceiver(mBroadcastReceiver); + mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); } /** @@ -249,6 +270,11 @@ public class BaseWifiTracker implements LifecycleObserver { // Do nothing. }; + @WorkerThread + protected void handleLinkPropertiesChanged(@Nullable LinkProperties linkProperties) { + // Do nothing. + }; + /** * Scanner to handle starting scans every SCAN_INTERVAL_MILLIS */ diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/PasspointWifiEntry.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/PasspointWifiEntry.java new file mode 100644 index 000000000..68d7adc8a --- /dev/null +++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/PasspointWifiEntry.java @@ -0,0 +1,339 @@ +/* + * 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 com.android.wifitrackerlib; + +import static android.net.wifi.WifiInfo.removeDoubleQuotes; + +import static androidx.core.util.Preconditions.checkNotNull; + +import static com.android.wifitrackerlib.Utils.getBestScanResultByLevel; +import static com.android.wifitrackerlib.Utils.getSecurityFromWifiConfiguration; + +import android.net.NetworkInfo; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.net.wifi.hotspot2.PasspointConfiguration; +import android.net.wifi.hotspot2.pps.HomeSp; +import android.os.Handler; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; + +import java.util.ArrayList; +import java.util.List; + +/** + * WifiEntry representation of a Passpoint network, uniquely identified by FQDN. + */ +class PasspointWifiEntry extends WifiEntry { + static final String KEY_PREFIX = "PasspointWifiEntry:"; + + private final List<ScanResult> mCurrentHomeScanResults = new ArrayList<>(); + private final List<ScanResult> mCurrentRoamingScanResults = new ArrayList<>(); + + @NonNull private final String mKey; + @NonNull private String mFriendlyName; + @Nullable private PasspointConfiguration mPasspointConfig; + @Nullable private WifiConfiguration mWifiConfig; + private @Security int mSecurity; + private boolean mIsRoaming = false; + @Nullable private NetworkInfo mNetworkInfo; + @Nullable private WifiInfo mWifiInfo; + @Nullable private ConnectCallback mConnectCallback; + @Nullable private DisconnectCallback mDisconnectCallback; + @Nullable private ForgetCallback mForgetCallback; + private boolean mCalledConnect = false; + private boolean mCalledDisconnect = false; + + private int mLevel = WIFI_LEVEL_UNREACHABLE; + + /** + * Create a PasspointWifiEntry with the associated PasspointConfiguration + */ + PasspointWifiEntry(@NonNull Handler callbackHandler, + @NonNull PasspointConfiguration passpointConfig, + @NonNull WifiManager wifiManager) throws IllegalArgumentException { + super(callbackHandler, false /* forSavedNetworksPage */, wifiManager); + + checkNotNull(passpointConfig, "Cannot construct with null PasspointConfiguration!"); + + final HomeSp homeSp = passpointConfig.getHomeSp(); + mKey = fqdnToPasspointWifiEntryKey(homeSp.getFqdn()); + mFriendlyName = homeSp.getFriendlyName(); + mSecurity = SECURITY_NONE; //TODO: Should this always be Enterprise? + } + + @Override + public String getKey() { + return mKey; + } + + @Override + @ConnectedState + public int getConnectedState() { + // TODO(b/70983952): Fill this method in + return CONNECTED_STATE_DISCONNECTED; + } + + @Override + public String getTitle() { + return mFriendlyName; + } + + @Override + public String getSummary() { + // TODO(b/70983952): Fill this method in + return "Passpoint"; // Placeholder string + } + + @Override + public int getLevel() { + return mLevel; + } + + @Override + public String getSsid() { + return mWifiConfig != null ? removeDoubleQuotes(mWifiConfig.SSID) : null; + } + + @Override + @Security + public int getSecurity() { + // TODO(b/70983952): Fill this method in + return mSecurity; + } + + @Override + public String getMacAddress() { + // TODO(b/70983952): Fill this method in + return null; + } + + @Override + public boolean isMetered() { + // TODO(b/70983952): Fill this method in + return false; + } + + @Override + public boolean isSaved() { + // TODO(b/70983952): Fill this method in + return false; + } + + @Override + public boolean isSubscription() { + return true; + } + + @Override + public WifiConfiguration getWifiConfiguration() { + return null; + } + + @Override + public ConnectedInfo getConnectedInfo() { + // TODO(b/70983952): Fill this method in + return null; + } + + @Override + public boolean canConnect() { + return mLevel != WIFI_LEVEL_UNREACHABLE + && getConnectedState() == CONNECTED_STATE_DISCONNECTED; + } + + @Override + public void connect(@Nullable ConnectCallback callback) { + // TODO(b/70983952): Fill this method in + } + + @Override + public boolean canDisconnect() { + return getConnectedState() == CONNECTED_STATE_CONNECTED; + } + + @Override + public void disconnect(@Nullable DisconnectCallback callback) { + // TODO(b/70983952): Fill this method in + } + + @Override + public boolean canForget() { + // TODO(b/70983952): Fill this method in + return false; + } + + @Override + public void forget(@Nullable ForgetCallback callback) { + // TODO(b/70983952): Fill this method in + } + + @Override + public boolean canSignIn() { + // TODO(b/70983952): Fill this method in + return false; + } + + @Override + public void signIn(@Nullable SignInCallback callback) { + // TODO(b/70983952): Fill this method in + } + + @Override + public boolean canShare() { + // TODO(b/70983952): Fill this method in + return false; + } + + @Override + public boolean canEasyConnect() { + // TODO(b/70983952): Fill this method in + return false; + } + + @Override + public String getQrCodeString() { + // TODO(b/70983952): Fill this method in + return null; + } + + @Override + public boolean canSetPassword() { + // TODO(b/70983952): Fill this method in + return false; + } + + @Override + public void setPassword(@NonNull String password) { + // TODO(b/70983952): Fill this method in + } + + @Override + @MeteredChoice + public int getMeteredChoice() { + // TODO(b/70983952): Fill this method in + return METERED_CHOICE_UNKNOWN; + } + + @Override + public boolean canSetMeteredChoice() { + // TODO(b/70983952): Fill this method in + return false; + } + + @Override + public void setMeteredChoice(int meteredChoice) { + // TODO(b/70983952): Fill this method in + } + + @Override + public boolean canSetPrivacy() { + // TODO(b/70983952): Fill this method in + return false; + } + + @Override + @Privacy + public int getPrivacy() { + // TODO(b/70983952): Fill this method in + return PRIVACY_UNKNOWN; + } + + @Override + public void setPrivacy(int privacy) { + // TODO(b/70983952): Fill this method in + } + + @Override + public boolean isAutoJoinEnabled() { + // TODO(b/70983952): Fill this method in + return false; + } + + @Override + public boolean canSetAutoJoinEnabled() { + // TODO(b/70983952): Fill this method in + return false; + } + + @Override + public void setAutoJoinEnabled(boolean enabled) { + // TODO(b/70983952): Fill this method in + } + + @WorkerThread + void updatePasspointConfig(@NonNull PasspointConfiguration passpointConfig) { + checkNotNull(passpointConfig, "Cannot update with null PasspointConfiguration!"); + mPasspointConfig = passpointConfig; + mFriendlyName = passpointConfig.getHomeSp().getFriendlyName(); + notifyOnUpdated(); + } + + @WorkerThread + void updateScanResultInfo(@NonNull WifiConfiguration wifiConfig, + @Nullable List<ScanResult> homeScanResults, + @Nullable List<ScanResult> roamingScanResults) + throws IllegalArgumentException { + mWifiConfig = wifiConfig; + mSecurity = getSecurityFromWifiConfiguration(wifiConfig); + + if (homeScanResults == null) { + homeScanResults = new ArrayList<>(); + } + if (roamingScanResults == null) { + roamingScanResults = new ArrayList<>(); + } + + ScanResult bestScanResult; + if (homeScanResults.isEmpty() && !roamingScanResults.isEmpty()) { + mIsRoaming = true; + bestScanResult = getBestScanResultByLevel(roamingScanResults); + } else { + mIsRoaming = false; + bestScanResult = getBestScanResultByLevel(homeScanResults); + } + + if (bestScanResult == null) { + mLevel = WIFI_LEVEL_UNREACHABLE; + } else { + mLevel = mWifiManager.calculateSignalLevel(bestScanResult.level); + } + + notifyOnUpdated(); + } + + @WorkerThread + @Override + protected boolean connectionInfoMatches(@NonNull WifiInfo wifiInfo, + @NonNull NetworkInfo networkInfo) { + if (!wifiInfo.isPasspointAp()) { + return false; + } + + return mWifiConfig != null && mWifiConfig.networkId == wifiInfo.getNetworkId(); + } + + @NonNull + static String fqdnToPasspointWifiEntryKey(@NonNull String fqdn) { + checkNotNull(fqdn, "Cannot create key with null fqdn!"); + return KEY_PREFIX + fqdn; + } +} diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardNetworkDetailsTracker.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardNetworkDetailsTracker.java index 3f0870dad..6190b98ce 100644 --- a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardNetworkDetailsTracker.java +++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardNetworkDetailsTracker.java @@ -20,12 +20,14 @@ import static androidx.core.util.Preconditions.checkNotNull; import static com.android.wifitrackerlib.StandardWifiEntry.scanResultToStandardWifiEntryKey; import static com.android.wifitrackerlib.StandardWifiEntry.wifiConfigToStandardWifiEntryKey; +import static com.android.wifitrackerlib.WifiEntry.CONNECTED_STATE_CONNECTED; import static java.util.stream.Collectors.toList; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; +import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.NetworkScoreManager; import android.net.wifi.WifiConfiguration; @@ -71,6 +73,8 @@ class StandardNetworkDetailsTracker extends NetworkDetailsTracker { final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); final NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo(); mChosenEntry.updateConnectionInfo(wifiInfo, networkInfo); + handleLinkPropertiesChanged(mConnectivityManager.getLinkProperties( + mWifiManager.getCurrentNetwork())); } @AnyThread @@ -123,6 +127,14 @@ class StandardNetworkDetailsTracker extends NetworkDetailsTracker { (NetworkInfo) intent.getExtra(WifiManager.EXTRA_NETWORK_INFO)); } + @WorkerThread + @Override + protected void handleLinkPropertiesChanged(@NonNull LinkProperties linkProperties) { + if (mChosenEntry.getConnectedState() == CONNECTED_STATE_CONNECTED) { + mChosenEntry.updateLinkProperties(linkProperties); + } + } + /** * Updates the tracked entry's scan results up to the max scan age (or more, if the last scan * was unsuccessful). If Wifi is disabled, the tracked entry's level will be cleared. diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardWifiEntry.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardWifiEntry.java index 149ba45a6..54dddcb8e 100644 --- a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardWifiEntry.java +++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardWifiEntry.java @@ -16,7 +16,6 @@ package com.android.wifitrackerlib; -import static android.net.wifi.WifiInfo.INVALID_RSSI; import static android.net.wifi.WifiInfo.removeDoubleQuotes; import static androidx.core.util.Preconditions.checkNotNull; @@ -55,15 +54,9 @@ class StandardWifiEntry extends WifiEntry { @NonNull private final String mSsid; private final @Security int mSecurity; @Nullable private WifiConfiguration mWifiConfig; - @Nullable private NetworkInfo mNetworkInfo; - @Nullable private WifiInfo mWifiInfo; @Nullable private ConnectCallback mConnectCallback; @Nullable private DisconnectCallback mDisconnectCallback; @Nullable private ForgetCallback mForgetCallback; - private boolean mCalledConnect = false; - private boolean mCalledDisconnect = false; - - private int mLevel = WIFI_LEVEL_UNREACHABLE; StandardWifiEntry(@NonNull Handler callbackHandler, @NonNull List<ScanResult> scanResults, @NonNull WifiManager wifiManager) throws IllegalArgumentException { @@ -117,28 +110,6 @@ class StandardWifiEntry extends WifiEntry { } @Override - @ConnectedState - public int getConnectedState() { - if (mNetworkInfo == null) { - return CONNECTED_STATE_DISCONNECTED; - } - - switch (mNetworkInfo.getDetailedState()) { - case SCANNING: - case CONNECTING: - case AUTHENTICATING: - case OBTAINING_IPADDR: - case VERIFYING_POOR_LINK: - case CAPTIVE_PORTAL_CHECK: - return CONNECTED_STATE_CONNECTING; - case CONNECTED: - return CONNECTED_STATE_CONNECTED; - default: - return CONNECTED_STATE_DISCONNECTED; - } - } - - @Override public String getTitle() { return mSsid; } @@ -208,8 +179,7 @@ class StandardWifiEntry extends WifiEntry { @Override public ConnectedInfo getConnectedInfo() { - // TODO(b/70983952): Fill this method in - return null; + return mConnectedInfo; } @Override @@ -287,6 +257,7 @@ class StandardWifiEntry extends WifiEntry { } } + @Override public boolean canSignIn() { // TODO(b/70983952): Fill this method in return false; @@ -400,8 +371,7 @@ class StandardWifiEntry extends WifiEntry { @Override public boolean canSetPrivacy() { - // TODO(b/70983952): Fill this method in - return false; + return isSaved(); } @Override @@ -420,7 +390,13 @@ class StandardWifiEntry extends WifiEntry { @Override public void setPrivacy(int privacy) { - // TODO(b/70983952): Fill this method in + if (!canSetPrivacy()) { + return; + } + + mWifiConfig.macRandomizationSetting = privacy == PRIVACY_RANDOMIZED_MAC + ? WifiConfiguration.RANDOMIZATION_PERSISTENT : WifiConfiguration.RANDOMIZATION_NONE; + mWifiManager.save(mWifiConfig, null /* listener */); } @Override @@ -497,42 +473,14 @@ class StandardWifiEntry extends WifiEntry { notifyOnUpdated(); } - /** - * Updates information regarding the current network connection. If the supplied WifiInfo and - * NetworkInfo do not represent this WifiEntry, then the WifiEntry will update to be - * unconnected. - */ @WorkerThread - void updateConnectionInfo(@Nullable WifiInfo wifiInfo, @Nullable NetworkInfo networkInfo) { - if (mWifiConfig != null && wifiInfo != null - && mWifiConfig.networkId == wifiInfo.getNetworkId()) { - mNetworkInfo = networkInfo; - mWifiInfo = wifiInfo; - final int wifiInfoRssi = wifiInfo.getRssi(); - if (wifiInfoRssi != INVALID_RSSI) { - mLevel = mWifiManager.calculateSignalLevel(wifiInfoRssi); - } - if (mCalledConnect && getConnectedState() == CONNECTED_STATE_CONNECTED) { - mCalledConnect = false; - mCallbackHandler.post(() -> { - if (mConnectCallback != null) { - mConnectCallback.onConnectResult(ConnectCallback.CONNECT_STATUS_SUCCESS); - } - }); - } - } else { - mNetworkInfo = null; - } - if (mCalledDisconnect && getConnectedState() == CONNECTED_STATE_DISCONNECTED) { - mCalledDisconnect = false; - mCallbackHandler.post(() -> { - if (mDisconnectCallback != null) { - mDisconnectCallback.onDisconnectResult( - DisconnectCallback.DISCONNECT_STATUS_SUCCESS); - } - }); + protected boolean connectionInfoMatches(@NonNull WifiInfo wifiInfo, + @NonNull NetworkInfo networkInfo) { + if (wifiInfo.isPasspointAp() || wifiInfo.isOsuAp()) { + return false; } - notifyOnUpdated(); + + return mWifiConfig != null && mWifiConfig.networkId == wifiInfo.getNetworkId(); } @NonNull diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiEntry.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiEntry.java index 4752e8449..08801d045 100644 --- a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiEntry.java +++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiEntry.java @@ -16,9 +16,17 @@ package com.android.wifitrackerlib; +import static android.net.wifi.WifiInfo.INVALID_RSSI; + import static androidx.core.util.Preconditions.checkNotNull; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.NetworkInfo; +import android.net.NetworkUtils; +import android.net.RouteInfo; import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Handler; @@ -27,10 +35,17 @@ import androidx.annotation.IntDef; import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; /** * Abstract base class for an entry representing a Wi-Fi network in a Wi-Fi picker/settings. @@ -136,6 +151,17 @@ public abstract class WifiEntry implements Comparable<WifiEntry> { private WifiEntryCallback mListener; protected Handler mCallbackHandler; + protected int mLevel = WIFI_LEVEL_UNREACHABLE; + protected WifiInfo mWifiInfo; + protected NetworkInfo mNetworkInfo; + protected ConnectedInfo mConnectedInfo; + + protected ConnectCallback mConnectCallback; + protected DisconnectCallback mDisconnectCallback; + + protected boolean mCalledConnect = false; + protected boolean mCalledDisconnect = false; + WifiEntry(@NonNull Handler callbackHandler, boolean forSavedNetworksPage, @NonNull WifiManager wifiManager) throws IllegalArgumentException { checkNotNull(callbackHandler, "Cannot construct with null handler!"); @@ -152,7 +178,26 @@ public abstract class WifiEntry implements Comparable<WifiEntry> { /** Returns connection state of the network defined by the CONNECTED_STATE constants */ @ConnectedState - public abstract int getConnectedState(); + public int getConnectedState() { + if (mNetworkInfo == null) { + return CONNECTED_STATE_DISCONNECTED; + } + + switch (mNetworkInfo.getDetailedState()) { + case SCANNING: + case CONNECTING: + case AUTHENTICATING: + case OBTAINING_IPADDR: + case VERIFYING_POOR_LINK: + case CAPTIVE_PORTAL_CHECK: + return CONNECTED_STATE_CONNECTING; + case CONNECTED: + return CONNECTED_STATE_CONNECTED; + default: + return CONNECTED_STATE_DISCONNECTED; + } + } + /** Returns the display title. This is most commonly the SSID of a network. */ public abstract String getTitle(); @@ -206,7 +251,9 @@ public abstract class WifiEntry implements Comparable<WifiEntry> { * * Returns null if getConnectedState() != CONNECTED_STATE_CONNECTED. */ - public abstract ConnectedInfo getConnectedInfo(); + public ConnectedInfo getConnectedInfo() { + return mConnectedInfo; + } /** * Info associated with the active connection. @@ -214,10 +261,10 @@ public abstract class WifiEntry implements Comparable<WifiEntry> { public static class ConnectedInfo { @Frequency public int frequencyMhz; - public List<String> dnsServers; + public List<String> dnsServers = new ArrayList<>(); public int linkSpeedMbps; public String ipAddress; - public List<String> ipv6Addresses; + public List<String> ipv6Addresses = new ArrayList<>(); public String gateway; public String subnetMask; } @@ -408,6 +455,105 @@ public abstract class WifiEntry implements Comparable<WifiEntry> { void onSignInResult(@SignInStatus int status); } + /** + * Returns whether or not the supplied WifiInfo and NetworkInfo represent this WifiEntry + */ + protected abstract boolean connectionInfoMatches(@NonNull WifiInfo wifiInfo, + @NonNull NetworkInfo networkInfo); + + /** + * Updates information regarding the current network connection. If the supplied WifiInfo and + * NetworkInfo do not match this WifiEntry, then the WifiEntry will update to be + * unconnected. + */ + @WorkerThread + void updateConnectionInfo(@Nullable WifiInfo wifiInfo, @Nullable NetworkInfo networkInfo) { + if (wifiInfo != null && networkInfo != null + && connectionInfoMatches(wifiInfo, networkInfo)) { + // Connection info matches, so the WifiInfo/NetworkInfo represent this network and + // the network is currently connecting or connected. + mWifiInfo = wifiInfo; + mNetworkInfo = networkInfo; + final int wifiInfoRssi = wifiInfo.getRssi(); + if (wifiInfoRssi != INVALID_RSSI) { + mLevel = mWifiManager.calculateSignalLevel(wifiInfoRssi); + } + if (getConnectedState() == CONNECTED_STATE_CONNECTED) { + if (mCalledConnect) { + mCalledConnect = false; + mCallbackHandler.post(() -> { + if (mConnectCallback != null) { + mConnectCallback.onConnectResult( + ConnectCallback.CONNECT_STATUS_SUCCESS); + } + }); + } + + if (mConnectedInfo == null) { + mConnectedInfo = new ConnectedInfo(); + } + mConnectedInfo.frequencyMhz = wifiInfo.getFrequency(); + mConnectedInfo.linkSpeedMbps = wifiInfo.getLinkSpeed(); + } + } else { // Connection info doesn't matched, so this network is disconnected + mNetworkInfo = null; + mConnectedInfo = null; + if (mCalledDisconnect) { + mCalledDisconnect = false; + mCallbackHandler.post(() -> { + if (mDisconnectCallback != null) { + mDisconnectCallback.onDisconnectResult( + DisconnectCallback.DISCONNECT_STATUS_SUCCESS); + } + }); + } + } + notifyOnUpdated(); + } + + // Method for WifiTracker to update the link properties, which is valid for all WifiEntry types. + @WorkerThread + void updateLinkProperties(@Nullable LinkProperties linkProperties) { + if (getConnectedState() != CONNECTED_STATE_CONNECTED) { + return; + } + + if (mConnectedInfo == null) { + mConnectedInfo = new ConnectedInfo(); + } + // Find IPv4 and IPv6 addresses, and subnet mask + List<String> ipv6Addresses = new ArrayList<>(); + for (LinkAddress addr : linkProperties.getLinkAddresses()) { + if (addr.getAddress() instanceof Inet4Address) { + mConnectedInfo.ipAddress = addr.getAddress().getHostAddress(); + try { + InetAddress all = InetAddress.getByAddress( + new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255}); + mConnectedInfo.subnetMask = NetworkUtils.getNetworkPart( + all, addr.getPrefixLength()).getHostAddress(); + } catch (UnknownHostException e) { + // Leave subnet null; + } + } else if (addr.getAddress() instanceof Inet6Address) { + ipv6Addresses.add(addr.getAddress().getHostAddress()); + } + } + mConnectedInfo.ipv6Addresses = ipv6Addresses; + + // Find IPv4 default gateway. + for (RouteInfo routeInfo : linkProperties.getRoutes()) { + if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) { + mConnectedInfo.gateway = routeInfo.getGateway().getHostAddress(); + break; + } + } + + // Find DNS servers + mConnectedInfo.dnsServers = linkProperties.getDnsServers().stream() + .map(InetAddress::getHostAddress).collect(Collectors.toList()); + + notifyOnUpdated(); + } // TODO (b/70983952) Come up with a sorting scheme that does the right thing. @Override @@ -446,6 +592,10 @@ public abstract class WifiEntry implements Comparable<WifiEntry> { .append(getLevel()) .append(",security:") .append(getSecurity()) + .append(",connected:") + .append(getConnectedState() == CONNECTED_STATE_CONNECTED ? "true" : "false") + .append(",connectedInfo:") + .append(getConnectedInfo()) .toString(); } } diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiPickerTracker.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiPickerTracker.java index abd9841fe..50b7a244b 100644 --- a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiPickerTracker.java +++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiPickerTracker.java @@ -26,19 +26,23 @@ import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; +import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.NetworkScoreManager; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; +import android.net.wifi.hotspot2.PasspointConfiguration; import android.os.Handler; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import androidx.annotation.AnyThread; import androidx.annotation.GuardedBy; @@ -81,6 +85,10 @@ public class WifiPickerTracker extends BaseWifiTracker { private WifiEntry mConnectedWifiEntry; // Cache containing visible StandardWifiEntries. Must be accessed only by the worker thread. private final Map<String, StandardWifiEntry> mStandardWifiEntryCache = new HashMap<>(); + // Cache containing visible PasspointWifiEntries. Must be accessed only by the worker thread. + private final Map<String, PasspointWifiEntry> mPasspointWifiEntryCache = new HashMap<>(); + // Cache containing saved PasspointConfigurations mapped by PasspointWifiEntry key. + private final Map<String, PasspointConfiguration> mPasspointConfigCache = new HashMap<>(); private int mNumSavedNetworks = 0; @@ -149,22 +157,30 @@ public class WifiPickerTracker extends BaseWifiTracker { */ @AnyThread public int getNumSavedSubscriptions() { - //TODO(b/70983952): Use a cached value for this and update dynamically instead of calling - // this expensive method every time. - return mWifiManager.getPasspointConfigurations().size(); + return mPasspointConfigCache.size(); } @WorkerThread @Override protected void handleOnStart() { - mScanResultUpdater.update(mWifiManager.getScanResults()); + mScanResultUpdater.update(Utils.filterScanResultsByCapabilities( + mWifiManager.getScanResults(), + mWifiManager.isWpa3SaeSupported(), + mWifiManager.isWpa3SuiteBSupported(), + mWifiManager.isEnhancedOpenSupported())); + // PasspointConfigurations should be populated before ScanResults, so that the scan results + // have a PasspointConfiguration to map to. + updatePasspointWifiEntryConfigs(mWifiManager.getPasspointConfigurations()); conditionallyUpdateScanResults(true /* lastScanSucceeded */); - updateStandardWifiEntryConfigs(mWifiManager.getConfiguredNetworks()); final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); final NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo(); - updateStandardWifiEntryConnectionInfo(wifiInfo, networkInfo); + updateConnectionInfo(wifiInfo, networkInfo); // Create a StandardWifiEntry for the current connection if there are no scan results yet. conditionallyCreateConnectedStandardWifiEntry(wifiInfo, networkInfo); + handleLinkPropertiesChanged(mConnectivityManager.getLinkProperties( + mWifiManager.getCurrentNetwork())); + notifyOnNumSavedNetworksChanged(); + notifyOnNumSavedSubscriptionsChanged(); updateWifiEntries(); } @@ -198,7 +214,12 @@ public class WifiPickerTracker extends BaseWifiTracker { } else { updateStandardWifiEntryConfigs(mWifiManager.getConfiguredNetworks()); } + updatePasspointWifiEntryConfigs(mWifiManager.getPasspointConfigurations()); + // Update Passpoint scan results since adding a new PasspointConfiguration may result in + // current scans mapping to a new PasspointWifiEntry. + updatePasspointWifiEntryScans(mScanResultUpdater.getScanResults()); notifyOnNumSavedNetworksChanged(); + notifyOnNumSavedSubscriptionsChanged(); updateWifiEntries(); } @@ -206,11 +227,20 @@ public class WifiPickerTracker extends BaseWifiTracker { @Override protected void handleNetworkStateChangedAction(@NonNull Intent intent) { checkNotNull(intent, "Intent cannot be null!"); - updateStandardWifiEntryConnectionInfo(mWifiManager.getConnectionInfo(), + updateConnectionInfo(mWifiManager.getConnectionInfo(), (NetworkInfo) intent.getExtra(WifiManager.EXTRA_NETWORK_INFO)); updateWifiEntries(); } + @WorkerThread + @Override + protected void handleLinkPropertiesChanged(@NonNull LinkProperties linkProperties) { + if (mConnectedWifiEntry != null + && mConnectedWifiEntry.getConnectedState() == CONNECTED_STATE_CONNECTED) { + mConnectedWifiEntry.updateLinkProperties(linkProperties); + } + } + /** * Update the list returned by getWifiEntries() with the current states of the entry caches. */ @@ -220,9 +250,8 @@ public class WifiPickerTracker extends BaseWifiTracker { mWifiEntries.clear(); mWifiEntries.addAll(mStandardWifiEntryCache.values().stream().filter(entry -> entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED).collect(toList())); - // mWifiEntries.addAll(mPasspointWifiEntryCache); - // mWifiEntries.addAll(mCarrierWifiEntryCache); - // mWifiEntries.addAll(mSuggestionWifiEntryCache); + mWifiEntries.addAll(mPasspointWifiEntryCache.values().stream().filter(entry -> + entry.getConnectedState() == CONNECTED_STATE_DISCONNECTED).collect(toList())); mConnectedWifiEntry = mStandardWifiEntryCache.values().stream().filter(entry -> { final @WifiEntry.ConnectedState int connectedState = entry.getConnectedState(); return connectedState == CONNECTED_STATE_CONNECTED @@ -248,14 +277,8 @@ public class WifiPickerTracker extends BaseWifiTracker { private void updateStandardWifiEntryScans(@NonNull List<ScanResult> scanResults) { checkNotNull(scanResults, "Scan Result list should not be null!"); - // Filter out scan results with unsupported capabilities - final List<ScanResult> filteredScans = Utils.filterScanResultsByCapabilities(scanResults, - mWifiManager.isWpa3SaeSupported(), - mWifiManager.isWpa3SuiteBSupported(), - mWifiManager.isEnhancedOpenSupported()); - // Group scans by StandardWifiEntry key - final Map<String, List<ScanResult>> scanResultsByKey = filteredScans.stream() + final Map<String, List<ScanResult>> scanResultsByKey = scanResults.stream() .filter(scanResult -> !TextUtils.isEmpty(scanResult.SSID)) .collect(groupingBy(StandardWifiEntry::scanResultToStandardWifiEntryKey)); @@ -278,6 +301,38 @@ public class WifiPickerTracker extends BaseWifiTracker { updateStandardWifiEntryConfigs(mWifiManager.getConfiguredNetworks()); } + @WorkerThread + private void updatePasspointWifiEntryScans(@NonNull List<ScanResult> scanResults) { + checkNotNull(scanResults, "Scan Result list should not be null!"); + + List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> matchingWifiConfigs = + mWifiManager.getAllMatchingWifiConfigs(scanResults); + for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pair : matchingWifiConfigs) { + final WifiConfiguration wifiConfig = pair.first; + final List<ScanResult> homeScans = + pair.second.get(WifiManager.PASSPOINT_HOME_NETWORK); + final List<ScanResult> roamingScans = + pair.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK); + final String key = PasspointWifiEntry.fqdnToPasspointWifiEntryKey(wifiConfig.FQDN); + // Skip in case the returned + if (!mPasspointConfigCache.containsKey(key)) { + continue; + } + + // Create PasspointWifiEntry if one doesn't exist for the seen key yet. + if (!mPasspointWifiEntryCache.containsKey(key)) { + mPasspointWifiEntryCache.put(key, new PasspointWifiEntry( + mMainHandler, mPasspointConfigCache.get(key), mWifiManager)); + } + mPasspointWifiEntryCache.get(key).updateScanResultInfo(wifiConfig, + homeScans, roamingScans); + } + + // Remove entries that are now unreachable + mPasspointWifiEntryCache.entrySet() + .removeIf(entry -> entry.getValue().getLevel() == WIFI_LEVEL_UNREACHABLE); + } + /** * Conditionally updates the WifiEntry scan results based on the current wifi state and * whether the last scan succeeded or not. @@ -286,19 +341,26 @@ public class WifiPickerTracker extends BaseWifiTracker { private void conditionallyUpdateScanResults(boolean lastScanSucceeded) { if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_DISABLED) { updateStandardWifiEntryScans(Collections.emptyList()); + updatePasspointWifiEntryScans(Collections.emptyList()); return; } long scanAgeWindow = mMaxScanAgeMillis; if (lastScanSucceeded) { // Scan succeeded, cache new scans - mScanResultUpdater.update(mWifiManager.getScanResults()); + mScanResultUpdater.update(Utils.filterScanResultsByCapabilities( + mWifiManager.getScanResults(), + mWifiManager.isWpa3SaeSupported(), + mWifiManager.isWpa3SuiteBSupported(), + mWifiManager.isEnhancedOpenSupported())); } else { // Scan failed, increase scan age window to prevent WifiEntry list from // clearing prematurely. scanAgeWindow += mScanIntervalMillis; } + updateStandardWifiEntryScans(mScanResultUpdater.getScanResults(scanAgeWindow)); + updatePasspointWifiEntryScans(mScanResultUpdater.getScanResults(scanAgeWindow)); } /** @@ -352,14 +414,17 @@ public class WifiPickerTracker extends BaseWifiTracker { } /** - * Updates all StandardWifiEntries with the current connection info. + * Updates all WifiEntries with the current connection info. * @param wifiInfo WifiInfo of the current connection * @param networkInfo NetworkInfo of the current connection */ @WorkerThread - private void updateStandardWifiEntryConnectionInfo(@Nullable WifiInfo wifiInfo, + private void updateConnectionInfo(@Nullable WifiInfo wifiInfo, @Nullable NetworkInfo networkInfo) { - for (StandardWifiEntry entry : mStandardWifiEntryCache.values()) { + for (WifiEntry entry : mStandardWifiEntryCache.values()) { + entry.updateConnectionInfo(wifiInfo, networkInfo); + } + for (WifiEntry entry : mPasspointWifiEntryCache.values()) { entry.updateConnectionInfo(wifiInfo, networkInfo); } } @@ -386,6 +451,30 @@ public class WifiPickerTracker extends BaseWifiTracker { }); } + @WorkerThread + private void updatePasspointWifiEntryConfigs(@NonNull List<PasspointConfiguration> configs) { + checkNotNull(configs, "Config list should not be null!"); + + mPasspointConfigCache.clear(); + mPasspointConfigCache.putAll(configs.stream().collect( + toMap((config) -> PasspointWifiEntry.fqdnToPasspointWifiEntryKey( + config.getHomeSp().getFqdn()), Function.identity()))); + + // Iterate through current entries and update each entry's config or remove if no config + // matches the entry anymore. + mPasspointWifiEntryCache.entrySet().removeIf((entry) -> { + final PasspointWifiEntry wifiEntry = entry.getValue(); + final String key = wifiEntry.getKey(); + final PasspointConfiguration cachedConfig = mPasspointConfigCache.get(key); + if (cachedConfig != null) { + wifiEntry.updatePasspointConfig(cachedConfig); + return false; + } else { + return true; + } + }); + } + /** * Posts onWifiEntryChanged callback on the main thread. */ diff --git a/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/StandardWifiEntryTest.java b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/StandardWifiEntryTest.java index bb5f814bd..a94d2bd79 100644 --- a/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/StandardWifiEntryTest.java +++ b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/StandardWifiEntryTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.net.LinkProperties; import android.net.MacAddress; import android.net.NetworkInfo; import android.net.wifi.ScanResult; @@ -497,6 +498,24 @@ public class StandardWifiEntryTest { assertThat(oweWifiEntry.canEasyConnect()).isFalse(); } + @Test + public void testUpdateLinkProperties_updatesConnectedInfo() { + final WifiConfiguration config = new WifiConfiguration(); + config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP); + config.SSID = "\"ssid\""; + config.networkId = 1; + final StandardWifiEntry entry = new StandardWifiEntry(mTestHandler, config, + mMockWifiManager); + when(mMockWifiInfo.getNetworkId()).thenReturn(1); + when(mMockWifiInfo.getRssi()).thenReturn(GOOD_RSSI); + when(mMockNetworkInfo.getDetailedState()).thenReturn(NetworkInfo.DetailedState.CONNECTED); + entry.updateConnectionInfo(mMockWifiInfo, mMockNetworkInfo); + + entry.updateLinkProperties(new LinkProperties()); + + assertThat(entry.getConnectedInfo()).isNotNull(); + } + private StandardWifiEntry getSavedStandardWifiEntry(int wifiConfigurationSecureType) { final WifiConfiguration config = new WifiConfiguration(); config.SSID = "\"ssid\""; diff --git a/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiPickerTrackerTest.java b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiPickerTrackerTest.java index f052e8df2..8bca9cd11 100644 --- a/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiPickerTrackerTest.java +++ b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiPickerTrackerTest.java @@ -35,8 +35,11 @@ import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; +import android.net.wifi.hotspot2.PasspointConfiguration; +import android.net.wifi.hotspot2.pps.HomeSp; import android.os.Handler; import android.os.test.TestLooper; +import android.util.Pair; import androidx.lifecycle.Lifecycle; @@ -50,7 +53,9 @@ import java.time.Clock; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class WifiPickerTrackerTest { @@ -493,4 +498,37 @@ public class WifiPickerTrackerTest { verify(mMockCallback, atLeastOnce()).onWifiEntriesChanged(); assertThat(wifiPickerTracker.getConnectedWifiEntry()).isNull(); } + + /** + * Tests that a PasspointWifiEntry is returned when Passpoint scans are visible. + */ + @Test + public void testGetWifiEntries_passpointInRange_returnsPasspointWifiEntry() { + final WifiPickerTracker wifiPickerTracker = createTestWifiPickerTracker(); + final PasspointConfiguration passpointConfig = new PasspointConfiguration(); + final HomeSp homeSp = new HomeSp(); + homeSp.setFqdn("fqdn"); + homeSp.setFriendlyName("friendlyName"); + passpointConfig.setHomeSp(homeSp); + when(mMockWifiManager.getPasspointConfigurations()) + .thenReturn(Collections.singletonList(passpointConfig)); + wifiPickerTracker.onStart(); + verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(), + any(), any(), any()); + mTestLooper.dispatchAll(); + + final WifiConfiguration wifiConfig = new WifiConfiguration(); + wifiConfig.FQDN = "fqdn"; + final Map<Integer, List<ScanResult>> mapping = new HashMap<>(); + mapping.put(WifiManager.PASSPOINT_HOME_NETWORK, Collections.singletonList( + buildScanResult("ssid", "bssid", START_MILLIS))); + List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> allMatchingWifiConfigs = + Collections.singletonList(new Pair<>(wifiConfig, mapping)); + when(mMockWifiManager.getAllMatchingWifiConfigs(any())).thenReturn(allMatchingWifiConfigs); + mBroadcastReceiverCaptor.getValue().onReceive(mMockContext, + new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); + + assertThat(wifiPickerTracker.getWifiEntries()).isNotEmpty(); + assertThat(wifiPickerTracker.getWifiEntries().get(0).getTitle()).isEqualTo("friendlyName"); + } } |