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
17package com.android.server.wifi.hotspot2;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.net.ConnectivityManager;
24import android.net.LinkProperties;
25import android.net.Network;
26import android.net.NetworkCapabilities;
27import android.net.NetworkRequest;
28import android.net.wifi.WifiConfiguration;
29import android.net.wifi.WifiManager;
30import android.net.wifi.WifiSsid;
31import android.os.Handler;
32import android.text.TextUtils;
33import android.util.Log;
34
35/**
36 * Responsible for setup/monitor on Wi-Fi state and connection to the OSU AP.
37 */
38public class OsuNetworkConnection {
39    private static final String TAG = "OsuNetworkConnection";
40    private static final int TIMEOUT_MS = 10000;
41
42    private final Context mContext;
43
44    private boolean mVerboseLoggingEnabled = false;
45    private WifiManager mWifiManager;
46    private ConnectivityManager mConnectivityManager;
47    private ConnectivityCallbacks mConnectivityCallbacks;
48    private Callbacks mCallbacks;
49    private Handler mHandler;
50    private Network mNetwork = null;
51    private boolean mConnected = false;
52    private int mNetworkId = -1;
53    private boolean mWifiEnabled = false;
54
55    /**
56     * Callbacks on Wi-Fi connection state changes.
57     */
58    public interface Callbacks {
59        /**
60         * Invoked when network connection is established with IP connectivity.
61         *
62         * @param network {@link Network} associated with the connected network.
63         */
64        void onConnected(Network network);
65
66        /**
67         * Invoked when the targeted network is disconnected.
68         */
69        void onDisconnected();
70
71        /**
72         * Invoked when a timer tracking connection request is not reset by successfull connection.
73         */
74        void onTimeOut();
75
76        /**
77         * Invoked when Wifi is enabled.
78         */
79        void onWifiEnabled();
80
81        /**
82         * Invoked when Wifi is disabled.
83         */
84        void onWifiDisabled();
85    }
86
87    /**
88     * Create an instance of {@link NetworkConnection} for the specified Wi-Fi network.
89     * @param context The application context
90     */
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     */
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     */
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        mWifiManager.removeNetwork(mNetworkId);
142        mNetworkId = -1;
143        mNetwork = null;
144        mConnected = false;
145    }
146
147    /**
148     * Register for network and Wifi state events
149     * @param callbacks The callbacks to be invoked on network change events
150     */
151    public void setEventCallback(Callbacks callbacks) {
152        mCallbacks = callbacks;
153    }
154
155    /**
156     * Connect to a OSU Wi-Fi network specified by the given SSID. The security type of the Wi-Fi
157     * network is either open or OSEN (OSU Server-only authenticated layer 2 Encryption Network).
158     * When network access identifier is provided, OSEN is used.
159     *
160     * @param ssid The SSID to connect to
161     * @param nai Network access identifier of the network
162     *
163     * @return boolean true if connection was successfully initiated
164     */
165    public boolean connect(WifiSsid ssid, String nai) {
166        if (mConnected) {
167            if (mVerboseLoggingEnabled) {
168                // Already connected
169                Log.v(TAG, "Connect called twice");
170            }
171            return true;
172        }
173        if (!mWifiEnabled) {
174            Log.w(TAG, "Wifi is not enabled");
175            return false;
176        }
177        WifiConfiguration config = new WifiConfiguration();
178        config.SSID = "\"" + ssid.toString() + "\"";
179        if (TextUtils.isEmpty(nai)) {
180            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
181        } else {
182            // TODO: Handle OSEN.
183            Log.w(TAG, "OSEN not supported");
184            return false;
185        }
186        mNetworkId = mWifiManager.addNetwork(config);
187        if (mNetworkId < 0) {
188            Log.e(TAG, "Unable to add network");
189            return false;
190        }
191        NetworkRequest networkRequest = null;
192        networkRequest = new NetworkRequest.Builder()
193                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
194        mConnectivityManager.requestNetwork(networkRequest, mConnectivityCallbacks, mHandler,
195                TIMEOUT_MS);
196        if (!mWifiManager.enableNetwork(mNetworkId, true)) {
197            Log.e(TAG, "Unable to enable network " + mNetworkId);
198            disconnectIfNeeded();
199            return false;
200        }
201        if (mVerboseLoggingEnabled) {
202            Log.v(TAG, "Current network ID " + mNetworkId);
203        }
204        return true;
205    }
206
207    /**
208     * Method to update logging level in this class
209     * @param verbose more than 0 enables verbose logging
210     */
211    public void enableVerboseLogging(int verbose) {
212        mVerboseLoggingEnabled = verbose > 0 ? true : false;
213    }
214
215    private class ConnectivityCallbacks extends ConnectivityManager.NetworkCallback {
216        @Override
217        public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
218            if (mVerboseLoggingEnabled) {
219                Log.v(TAG, "onLinkPropertiesChanged for network=" + network
220                        + " isProvisioned?" + linkProperties.isProvisioned());
221            }
222            if (linkProperties.isProvisioned() && mNetwork == null) {
223                mNetwork = network;
224                mConnected = true;
225                if (mCallbacks != null) {
226                    mCallbacks.onConnected(network);
227                }
228            }
229        }
230
231        @Override
232        public void onUnavailable() {
233            if (mVerboseLoggingEnabled) {
234                Log.v(TAG, "onUnvailable ");
235            }
236            if (mCallbacks != null) {
237                mCallbacks.onTimeOut();
238            }
239        }
240
241        @Override
242        public void onLost(Network network) {
243            if (mVerboseLoggingEnabled) {
244                Log.v(TAG, "onLost " + network);
245            }
246            if (network != mNetwork) {
247                Log.w(TAG, "Irrelevant network lost notification");
248                return;
249            }
250            if (mCallbacks != null) {
251                mCallbacks.onDisconnected();
252            }
253        }
254    }
255}
256
257