1 /*
2  * Copyright 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wifi.hotspot2;
18 
19 import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
20 
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.net.ConnectivityManager;
26 import android.net.LinkProperties;
27 import android.net.Network;
28 import android.net.NetworkCapabilities;
29 import android.net.NetworkRequest;
30 import android.net.wifi.WifiConfiguration;
31 import android.net.wifi.WifiEnterpriseConfig;
32 import android.net.wifi.WifiInfo;
33 import android.net.wifi.WifiManager;
34 import android.net.wifi.WifiSsid;
35 import android.os.Handler;
36 import android.text.TextUtils;
37 import android.util.Log;
38 
39 /**
40  * Responsible for setup/monitor on Wi-Fi state and connection to the OSU AP.
41  */
42 public class OsuNetworkConnection {
43     private static final String TAG = "PasspointOsuNetworkConnection";
44     private static final int TIMEOUT_MS = 10000;
45 
46     private final Context mContext;
47 
48     private boolean mVerboseLoggingEnabled = false;
49     private WifiManager mWifiManager;
50     private ConnectivityManager mConnectivityManager;
51     private ConnectivityCallbacks mConnectivityCallbacks;
52     private Callbacks mCallbacks;
53     private Handler mHandler;
54     private Network mNetwork = null;
55     private boolean mConnected = false;
56     private int mNetworkId = -1;
57     private boolean mWifiEnabled = false;
58 
59     /**
60      * Callbacks on Wi-Fi connection state changes.
61      */
62     public interface Callbacks {
63         /**
64          * Invoked when network connection is established with IP connectivity.
65          *
66          * @param network {@link Network} associated with the connected network.
67          */
onConnected(Network network)68         void onConnected(Network network);
69 
70         /**
71          * Invoked when the targeted network is disconnected.
72          */
onDisconnected()73         void onDisconnected();
74 
75         /**
76          * Invoked when a timer tracking connection request is not reset by successful connection.
77          */
onTimeOut()78         void onTimeOut();
79 
80         /**
81          * Invoked when Wifi is enabled.
82          */
onWifiEnabled()83         void onWifiEnabled();
84 
85         /**
86          * Invoked when Wifi is disabled.
87          */
onWifiDisabled()88         void onWifiDisabled();
89     }
90 
OsuNetworkConnection(Context context)91     public OsuNetworkConnection(Context context) {
92         mContext = context;
93     }
94 
95     /**
96      * Called to initialize tracking of wifi state and network events by registering for the
97      * corresponding intents.
98      */
init(Handler handler)99     public void init(Handler handler) {
100         IntentFilter filter = new IntentFilter();
101         filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
102         BroadcastReceiver receiver = new BroadcastReceiver() {
103             @Override
104             public void onReceive(Context context, Intent intent) {
105                 String action = intent.getAction();
106                 if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
107                     int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
108                             WifiManager.WIFI_STATE_UNKNOWN);
109                     if (state == WifiManager.WIFI_STATE_DISABLED && mWifiEnabled) {
110                         mWifiEnabled = false;
111                         if (mCallbacks != null) mCallbacks.onWifiDisabled();
112                     }
113                     if (state == WifiManager.WIFI_STATE_ENABLED && !mWifiEnabled) {
114                         mWifiEnabled = true;
115                         if (mCallbacks != null) mCallbacks.onWifiEnabled();
116                     }
117                 }
118             }
119         };
120         mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
121         mContext.registerReceiver(receiver, filter, null, handler);
122         mWifiEnabled = mWifiManager.isWifiEnabled();
123         mConnectivityManager =
124                 (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
125         mConnectivityCallbacks = new ConnectivityCallbacks();
126         mHandler = handler;
127     }
128 
129     /**
130      * Disconnect, if required in the two cases
131      * - still connected to the OSU AP
132      * - connection to OSU AP was requested and in progress
133      */
disconnectIfNeeded()134     public void disconnectIfNeeded() {
135         if (mNetworkId < 0) {
136             if (mVerboseLoggingEnabled) {
137                 Log.v(TAG, "No connection to tear down");
138             }
139             return;
140         }
141         mConnectivityManager.unregisterNetworkCallback(mConnectivityCallbacks);
142         mWifiManager.removeNetwork(mNetworkId);
143         mNetworkId = -1;
144         mNetwork = null;
145         mConnected = false;
146     }
147 
148     /**
149      * Register for network and Wifi state events
150      *
151      * @param callbacks The callbacks to be invoked on network change events
152      */
setEventCallback(Callbacks callbacks)153     public void setEventCallback(Callbacks callbacks) {
154         mCallbacks = callbacks;
155     }
156 
157     /**
158      * Connect to a OSU Wi-Fi network specified by the given SSID. The security type of the Wi-Fi
159      * network is either open or OSEN (OSU Server-only authenticated layer 2 Encryption Network).
160      * When network access identifier is provided, OSEN is used.
161      *
162      * @param ssid The SSID to connect to
163      * @param nai Network access identifier of the network
164      * @param friendlyName a friendly name of service provider
165      *
166      * @return boolean true if connection was successfully initiated
167      */
connect(WifiSsid ssid, String nai, String friendlyName)168     public boolean connect(WifiSsid ssid, String nai, String friendlyName) {
169         if (mConnected) {
170             if (mVerboseLoggingEnabled) {
171                 // Already connected
172                 Log.v(TAG, "Connect called twice");
173             }
174             return true;
175         }
176         if (!mWifiEnabled) {
177             Log.w(TAG, "Wifi is not enabled");
178             return false;
179         }
180         WifiConfiguration config = new WifiConfiguration();
181         config.SSID = ssid.toString();
182 
183         // To suppress Wi-Fi has no internet access notification.
184         config.noInternetAccessExpected = true;
185 
186         // To suppress Wi-Fi Sign-in notification for captive portal.
187         config.osu = true;
188 
189         // Do not save this network
190         config.ephemeral = true;
191         config.providerFriendlyName = friendlyName;
192 
193         if (TextUtils.isEmpty(nai)) {
194             config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
195         } else {
196             // Setup OSEN connection with Unauthenticated user TLS and WFA Root certs
197             config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OSEN);
198             config.enterpriseConfig.setDomainSuffixMatch(nai);
199             config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.UNAUTH_TLS);
200             config.enterpriseConfig.setCaPath(WfaKeyStore.DEFAULT_WFA_CERT_DIR);
201         }
202         mNetworkId = mWifiManager.addNetwork(config);
203         if (mNetworkId < 0) {
204             Log.e(TAG, "Unable to add network");
205             return false;
206         }
207 
208         // NET_CAPABILITY_TRUSTED is added by builder by default.
209         // But for ephemeral network, the capability needs to be removed
210         // as wifi stack creates network agent without the capability.
211         // That could cause connectivity service not to find the matching agent.
212         NetworkRequest networkRequest = new NetworkRequest.Builder()
213                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
214                 .removeCapability(NET_CAPABILITY_TRUSTED)
215                 .build();
216         mConnectivityManager.requestNetwork(networkRequest, mConnectivityCallbacks, mHandler,
217                 TIMEOUT_MS);
218 
219         // TODO(b/112195429): replace it with new connectivity API.
220         if (!mWifiManager.enableNetwork(mNetworkId, true)) {
221             Log.e(TAG, "Unable to enable network " + mNetworkId);
222             disconnectIfNeeded();
223             return false;
224         }
225 
226         if (mVerboseLoggingEnabled) {
227             Log.v(TAG, "Current network ID " + mNetworkId);
228         }
229         return true;
230     }
231 
232     /**
233      * Method to update logging level in this class
234      *
235      * @param verbose enables verbose logging
236      */
enableVerboseLogging(boolean verbose)237     public void enableVerboseLogging(boolean verbose) {
238         mVerboseLoggingEnabled = verbose;
239     }
240 
241     private class ConnectivityCallbacks extends ConnectivityManager.NetworkCallback {
242         @Override
onAvailable(Network network)243         public void onAvailable(Network network) {
244             WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
245             if (wifiInfo == null) {
246                 Log.w(TAG, "wifiInfo is not valid");
247                 return;
248             }
249             if (mNetworkId < 0 || mNetworkId != wifiInfo.getNetworkId()) {
250                 Log.w(TAG, "Irrelevant network available notification for netId: "
251                         + wifiInfo.getNetworkId());
252                 return;
253             }
254             mNetwork = network;
255             mConnected = true;
256         }
257 
258         @Override
onLinkPropertiesChanged(Network network, LinkProperties linkProperties)259         public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
260             if (mVerboseLoggingEnabled) {
261                 Log.v(TAG, "onLinkPropertiesChanged for network=" + network
262                                 + " isProvisioned?" + linkProperties.isProvisioned());
263             }
264             if (mNetwork == null) {
265                 Log.w(TAG, "ignore onLinkPropertyChanged event for null network");
266                 return;
267             }
268             if (linkProperties.isProvisioned()) {
269                 if (mCallbacks != null) {
270                     mCallbacks.onConnected(network);
271                 }
272             }
273         }
274 
275         @Override
onUnavailable()276         public void onUnavailable() {
277             if (mVerboseLoggingEnabled) {
278                 Log.v(TAG, "onUnvailable ");
279             }
280             if (mCallbacks != null) {
281                 mCallbacks.onTimeOut();
282             }
283         }
284 
285         @Override
onLost(Network network)286         public void onLost(Network network) {
287             if (mVerboseLoggingEnabled) {
288                 Log.v(TAG, "onLost " + network);
289             }
290             if (network != mNetwork) {
291                 Log.w(TAG, "Irrelevant network lost notification");
292                 return;
293             }
294             if (mCallbacks != null) {
295                 mCallbacks.onDisconnected();
296             }
297         }
298     }
299 }
300 
301