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