blob: da1b3cf9afe5635a7faa5cec52bb32f13a2ea181 [file] [log] [blame]
/*
* Copyright (C) 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.google.gce.gceservice;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.SupplicantState;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Manage WIFI state.
*/
public class GceWifiManager extends JobBase {
private static final String LOG_TAG = "GceWifiManager";
/* Timeout after which another attempt to re-connect wifi will be made. */
private static final int WIFI_RECONNECTION_TIMEOUT_S = 3;
/* Maximum number of retries before giving up and marking WIFI as inoperable. */
private static final int WIFI_RECONNECTION_MAX_ATTEMPTS = 10;
/** Describes possible WIFI states.
* WifiState is:
* - UNKNOWN only at the initialization time, replaced with state from
* from Android's WifiManager.
* - ENABLED when WIFI is connected and operational,
* - DISABLED when WIFI is turned off,
* - FAILED if GceWifiManager was unable to configure WIFI.
*/
public enum WifiState {
DISABLED,
ENABLED;
};
private final JobExecutor mJobExecutor;
private final Context mContext;
private final WifiManager mWifiManager;
private final ConnectivityManager mConnManager;
private ConfigureWifi mConfigureWifiJob = new ConfigureWifi();
private SetWifiState mSetInitialWifiStateJob = new SetWifiState();
/** Constructor.
*/
public GceWifiManager(Context context, JobExecutor executor) {
super(LOG_TAG);
mContext = context;
mWifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
mConnManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
mJobExecutor = executor;
}
private boolean isMobileNetworkAvailable() {
for (NetworkInfo network : mConnManager.getAllNetworkInfo()) {
if (network.getType() == ConnectivityManager.TYPE_MOBILE) return true;
}
return false;
}
private WifiState getExpectedWifiState() {
// TODO(ender): we will probably want to define this differently once virtual WIFI is
// available again.
return WifiState.DISABLED;
}
/** Executed during initial configuration.
*/
@Override
public synchronized int execute() {
WifiState initialState = getExpectedWifiState();
mSetInitialWifiStateJob.setState(initialState);
// Only configure wifi if expected state is ENABLED.
// Configuring wifi *requires* wpa_supplicant to be up.
// This means that in order to configure wifi, we have to enable it first.
if (initialState == WifiState.ENABLED) {
mJobExecutor.schedule(mConfigureWifiJob);
mJobExecutor.schedule(mSetInitialWifiStateJob, mConfigureWifiJob.getWifiConfigured());
} else {
// If initial state is DISABLED, there's no need to wait for Wifi configuration to
// complete. Just shut it off.
mJobExecutor.schedule(mSetInitialWifiStateJob);
}
return 0;
}
@Override
public void onDependencyFailed(Exception e) {
Log.e(LOG_TAG, "Initial WIFI configuration failed due to dependency.", e);
getInitialWifiStateChangeReady().set(e);
}
public GceFuture<Boolean> getInitialWifiStateChangeReady() {
return mSetInitialWifiStateJob.getWifiReady();
}
/* Configure WIFI network stack.
*
* Adds network configuration that covers AndroidWifi virtual hotspot.
*/
private class ConfigureWifi extends JobBase {
private final GceFuture<Boolean> mWifiConfigured =
new GceFuture<Boolean>("WIFI Configured");
private boolean mReportedWaitingForSupplicant = false;
public ConfigureWifi() {
super(LOG_TAG);
}
@Override
public int execute() {
if (mWifiConfigured.isDone()) return 0;
if (!mWifiManager.pingSupplicant()) {
if (!mWifiManager.isWifiEnabled()) {
mWifiManager.setWifiEnabled(true);
}
if (!mReportedWaitingForSupplicant) {
Log.i(LOG_TAG, "Supplicant not ready.");
mReportedWaitingForSupplicant = true;
}
return WIFI_RECONNECTION_TIMEOUT_S;
}
WifiConfiguration conf = new WifiConfiguration();
conf.SSID = "\"AndroidWifi\"";
conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
int network_id = mWifiManager.addNetwork(conf);
if (network_id < 0) {
Log.e(LOG_TAG, "Could not update wifi network.");
mWifiConfigured.set(new Exception("Could not add WIFI network"));
} else {
mWifiManager.enableNetwork(network_id, false);
mWifiConfigured.set(true);
}
return 0;
}
@Override
public void onDependencyFailed(Exception e) {
Log.e(LOG_TAG, "Could not configure WIFI.", e);
mWifiConfigured.set(e);
}
public GceFuture<Boolean> getWifiConfigured() {
return mWifiConfigured;
}
}
/* Modifies Wifi state:
* - if wifi disable requested (state == false), simply turns off wifi.
* - if wifi enable requested (state == true), turns on wifi and arms the
* connection timeout (see startWifiReconnectionTimeout).
*/
private class SetWifiState extends JobBase {
private final GceFuture<Boolean> mWifiReady =
new GceFuture<Boolean>("WIFI Ready");
private WifiState mDesiredState = WifiState.DISABLED;
private int mWifiStateChangeAttempt = 0;
private boolean mReportedWifiNotConnected = false;
public SetWifiState() {
super(LOG_TAG);
}
public void setState(WifiState state) {
mDesiredState = state;
}
public synchronized void cancel() {
if (!mWifiReady.isDone()) {
mWifiReady.cancel(false);
}
}
@Override
public synchronized int execute() {
WifiState currentState = mWifiManager.isWifiEnabled() ?
WifiState.ENABLED : WifiState.DISABLED;
// Could be cancelled or exception.
if (mWifiReady.isDone()) return 0;
if (mWifiStateChangeAttempt >= WIFI_RECONNECTION_MAX_ATTEMPTS) {
mWifiReady.set(new Exception(
String.format("Unable to change wifi state after %d attempts.",
WIFI_RECONNECTION_MAX_ATTEMPTS)));
return 0;
}
if (currentState == mDesiredState) {
switch (currentState) {
case ENABLED:
// Wifi is enabled, but probably not yet connected. Check.
WifiInfo info = mWifiManager.getConnectionInfo();
if (info.getSupplicantState() != SupplicantState.COMPLETED) {
if (!mReportedWifiNotConnected) {
Log.w(LOG_TAG, "Wifi not yet connected.");
mReportedWifiNotConnected = true;
}
} else {
Log.i(LOG_TAG, "Wifi connected.");
mWifiReady.set(true);
}
break;
case DISABLED:
// There's nothing extra to check for disable wifi.
mWifiReady.set(true);
break;
}
if (mWifiReady.isDone()) {
return 0;
}
}
// At this point we know that:
// - current state is different that desired state, or
// - current state is enabled, but wifi is not yet connected.
++mWifiStateChangeAttempt;
switch (mDesiredState) {
case DISABLED:
mWifiManager.setWifiEnabled(false);
break;
case ENABLED:
mWifiManager.setWifiEnabled(true);
mWifiManager.reconnect();
break;
}
return WIFI_RECONNECTION_TIMEOUT_S;
}
@Override
public void onDependencyFailed(Exception e) {
Log.e(LOG_TAG, "Wifi state change failed due to failing dependency.", e);
mWifiReady.set(e);
}
public GceFuture<Boolean> getWifiReady() {
return mWifiReady;
}
}
}