| /* |
| * Copyright 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.wifi.hotspot2; |
| |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; |
| |
| 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.NetworkCapabilities; |
| import android.net.NetworkRequest; |
| import android.net.wifi.WifiConfiguration; |
| import android.net.wifi.WifiEnterpriseConfig; |
| import android.net.wifi.WifiInfo; |
| import android.net.wifi.WifiManager; |
| import android.net.wifi.WifiSsid; |
| import android.os.Handler; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| /** |
| * Responsible for setup/monitor on Wi-Fi state and connection to the OSU AP. |
| */ |
| public class OsuNetworkConnection { |
| private static final String TAG = "PasspointOsuNetworkConnection"; |
| private static final int TIMEOUT_MS = 10000; |
| |
| private final Context mContext; |
| |
| private boolean mVerboseLoggingEnabled = false; |
| private WifiManager mWifiManager; |
| private ConnectivityManager mConnectivityManager; |
| private ConnectivityCallbacks mConnectivityCallbacks; |
| private Callbacks mCallbacks; |
| private Handler mHandler; |
| private Network mNetwork = null; |
| private boolean mConnected = false; |
| private int mNetworkId = -1; |
| private boolean mWifiEnabled = false; |
| |
| /** |
| * Callbacks on Wi-Fi connection state changes. |
| */ |
| public interface Callbacks { |
| /** |
| * Invoked when network connection is established with IP connectivity. |
| * |
| * @param network {@link Network} associated with the connected network. |
| */ |
| void onConnected(Network network); |
| |
| /** |
| * Invoked when the targeted network is disconnected. |
| */ |
| void onDisconnected(); |
| |
| /** |
| * Invoked when a timer tracking connection request is not reset by successful connection. |
| */ |
| void onTimeOut(); |
| |
| /** |
| * Invoked when Wifi is enabled. |
| */ |
| void onWifiEnabled(); |
| |
| /** |
| * Invoked when Wifi is disabled. |
| */ |
| void onWifiDisabled(); |
| } |
| |
| public OsuNetworkConnection(Context context) { |
| mContext = context; |
| } |
| |
| /** |
| * Called to initialize tracking of wifi state and network events by registering for the |
| * corresponding intents. |
| */ |
| public void init(Handler handler) { |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); |
| BroadcastReceiver receiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { |
| int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, |
| WifiManager.WIFI_STATE_UNKNOWN); |
| if (state == WifiManager.WIFI_STATE_DISABLED && mWifiEnabled) { |
| mWifiEnabled = false; |
| if (mCallbacks != null) mCallbacks.onWifiDisabled(); |
| } |
| if (state == WifiManager.WIFI_STATE_ENABLED && !mWifiEnabled) { |
| mWifiEnabled = true; |
| if (mCallbacks != null) mCallbacks.onWifiEnabled(); |
| } |
| } |
| } |
| }; |
| mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| mContext.registerReceiver(receiver, filter, null, handler); |
| mWifiEnabled = mWifiManager.isWifiEnabled(); |
| mConnectivityManager = |
| (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); |
| mConnectivityCallbacks = new ConnectivityCallbacks(); |
| mHandler = handler; |
| } |
| |
| /** |
| * Disconnect, if required in the two cases |
| * - still connected to the OSU AP |
| * - connection to OSU AP was requested and in progress |
| */ |
| public void disconnectIfNeeded() { |
| if (mNetworkId < 0) { |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "No connection to tear down"); |
| } |
| return; |
| } |
| mConnectivityManager.unregisterNetworkCallback(mConnectivityCallbacks); |
| mWifiManager.removeNetwork(mNetworkId); |
| mNetworkId = -1; |
| mNetwork = null; |
| mConnected = false; |
| } |
| |
| /** |
| * Register for network and Wifi state events |
| * |
| * @param callbacks The callbacks to be invoked on network change events |
| */ |
| public void setEventCallback(Callbacks callbacks) { |
| mCallbacks = callbacks; |
| } |
| |
| /** |
| * Connect to a OSU Wi-Fi network specified by the given SSID. The security type of the Wi-Fi |
| * network is either open or OSEN (OSU Server-only authenticated layer 2 Encryption Network). |
| * When network access identifier is provided, OSEN is used. |
| * |
| * @param ssid The SSID to connect to |
| * @param nai Network access identifier of the network |
| * @param friendlyName a friendly name of service provider |
| * |
| * @return boolean true if connection was successfully initiated |
| */ |
| public boolean connect(WifiSsid ssid, String nai, String friendlyName) { |
| if (mConnected) { |
| if (mVerboseLoggingEnabled) { |
| // Already connected |
| Log.v(TAG, "Connect called twice"); |
| } |
| return true; |
| } |
| if (!mWifiEnabled) { |
| Log.w(TAG, "Wifi is not enabled"); |
| return false; |
| } |
| WifiConfiguration config = new WifiConfiguration(); |
| config.SSID = "\"" + ssid.toString() + "\""; |
| |
| // To suppress Wi-Fi has no internet access notification. |
| config.noInternetAccessExpected = true; |
| |
| // To suppress Wi-Fi Sign-in notification for captive portal. |
| config.osu = true; |
| |
| // Do not save this network |
| config.ephemeral = true; |
| config.providerFriendlyName = friendlyName; |
| |
| if (TextUtils.isEmpty(nai)) { |
| config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN); |
| } else { |
| // Setup OSEN connection with Unauthenticated user TLS and WFA Root certs |
| config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OSEN); |
| config.enterpriseConfig.setDomainSuffixMatch(nai); |
| config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.UNAUTH_TLS); |
| config.enterpriseConfig.setCaPath(WfaKeyStore.DEFAULT_WFA_CERT_DIR); |
| } |
| mNetworkId = mWifiManager.addNetwork(config); |
| if (mNetworkId < 0) { |
| Log.e(TAG, "Unable to add network"); |
| return false; |
| } |
| |
| // NET_CAPABILITY_TRUSTED is added by builder by default. |
| // But for ephemeral network, the capability needs to be removed |
| // as wifi stack creates network agent without the capability. |
| // That could cause connectivity service not to find the matching agent. |
| NetworkRequest networkRequest = new NetworkRequest.Builder() |
| .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) |
| .removeCapability(NET_CAPABILITY_TRUSTED) |
| .build(); |
| mConnectivityManager.requestNetwork(networkRequest, mConnectivityCallbacks, mHandler, |
| TIMEOUT_MS); |
| |
| // TODO(b/112195429): replace it with new connectivity API. |
| if (!mWifiManager.enableNetwork(mNetworkId, true)) { |
| Log.e(TAG, "Unable to enable network " + mNetworkId); |
| disconnectIfNeeded(); |
| return false; |
| } |
| |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "Current network ID " + mNetworkId); |
| } |
| return true; |
| } |
| |
| /** |
| * Method to update logging level in this class |
| * |
| * @param verbose enables verbose logging |
| */ |
| public void enableVerboseLogging(boolean verbose) { |
| mVerboseLoggingEnabled = verbose; |
| } |
| |
| private class ConnectivityCallbacks extends ConnectivityManager.NetworkCallback { |
| @Override |
| public void onAvailable(Network network) { |
| WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); |
| if (wifiInfo == null) { |
| Log.w(TAG, "wifiInfo is not valid"); |
| return; |
| } |
| if (mNetworkId < 0 || mNetworkId != wifiInfo.getNetworkId()) { |
| Log.w(TAG, "Irrelevant network available notification for netId: " |
| + wifiInfo.getNetworkId()); |
| return; |
| } |
| mNetwork = network; |
| mConnected = true; |
| } |
| |
| @Override |
| public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) { |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "onLinkPropertiesChanged for network=" + network |
| + " isProvisioned?" + linkProperties.isProvisioned()); |
| } |
| if (mNetwork == null) { |
| Log.w(TAG, "ignore onLinkPropertyChanged event for null network"); |
| return; |
| } |
| if (linkProperties.isProvisioned()) { |
| if (mCallbacks != null) { |
| mCallbacks.onConnected(network); |
| } |
| } |
| } |
| |
| @Override |
| public void onUnavailable() { |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "onUnvailable "); |
| } |
| if (mCallbacks != null) { |
| mCallbacks.onTimeOut(); |
| } |
| } |
| |
| @Override |
| public void onLost(Network network) { |
| if (mVerboseLoggingEnabled) { |
| Log.v(TAG, "onLost " + network); |
| } |
| if (network != mNetwork) { |
| Log.w(TAG, "Irrelevant network lost notification"); |
| return; |
| } |
| if (mCallbacks != null) { |
| mCallbacks.onDisconnected(); |
| } |
| } |
| } |
| } |
| |