1 /* 2 * Copyright (C) 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 package com.android.google.gce.gceservice; 17 18 import android.content.Context; 19 import android.net.ConnectivityManager; 20 import android.net.NetworkInfo; 21 import android.net.wifi.SupplicantState; 22 import android.net.wifi.WifiConfiguration; 23 import android.net.wifi.WifiInfo; 24 import android.net.wifi.WifiManager; 25 import android.os.Handler; 26 import android.util.Log; 27 import java.util.ArrayList; 28 import java.util.List; 29 30 /** 31 * Manage WIFI state. 32 */ 33 public class GceWifiManager extends JobBase { 34 private static final String LOG_TAG = "GceWifiManager"; 35 /* Timeout after which another attempt to re-connect wifi will be made. */ 36 private static final int WIFI_RECONNECTION_TIMEOUT_S = 3; 37 /* Maximum number of retries before giving up and marking WIFI as inoperable. */ 38 private static final int WIFI_RECONNECTION_MAX_ATTEMPTS = 10; 39 40 /** Describes possible WIFI states. 41 * WifiState is: 42 * - UNKNOWN only at the initialization time, replaced with state from 43 * from Android's WifiManager. 44 * - ENABLED when WIFI is connected and operational, 45 * - DISABLED when WIFI is turned off, 46 * - FAILED if GceWifiManager was unable to configure WIFI. 47 */ 48 public enum WifiState { 49 DISABLED, 50 ENABLED; 51 }; 52 53 private final JobExecutor mJobExecutor; 54 private final Context mContext; 55 private final WifiManager mWifiManager; 56 private final ConnectivityManager mConnManager; 57 58 private ConfigureWifi mConfigureWifiJob = new ConfigureWifi(); 59 private SetWifiState mSetInitialWifiStateJob = new SetWifiState(); 60 61 62 /** Constructor. 63 */ 64 public GceWifiManager(Context context, JobExecutor executor) { 65 super(LOG_TAG); 66 67 mContext = context; 68 mWifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); 69 mConnManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); 70 mJobExecutor = executor; 71 } 72 73 74 private boolean isMobileNetworkAvailable() { 75 for (NetworkInfo network : mConnManager.getAllNetworkInfo()) { 76 if (network.getType() == ConnectivityManager.TYPE_MOBILE) return true; 77 } 78 return false; 79 } 80 81 82 private WifiState getExpectedWifiState() { 83 // TODO(ender): we will probably want to define this differently once virtual WIFI is 84 // available again. 85 return WifiState.DISABLED; 86 } 87 88 89 /** Executed during initial configuration. 90 */ 91 @Override 92 public synchronized int execute() { 93 WifiState initialState = getExpectedWifiState(); 94 mSetInitialWifiStateJob.setState(initialState); 95 96 // Only configure wifi if expected state is ENABLED. 97 // Configuring wifi *requires* wpa_supplicant to be up. 98 // This means that in order to configure wifi, we have to enable it first. 99 if (initialState == WifiState.ENABLED) { 100 mJobExecutor.schedule(mConfigureWifiJob); 101 mJobExecutor.schedule(mSetInitialWifiStateJob, mConfigureWifiJob.getWifiConfigured()); 102 } else { 103 // If initial state is DISABLED, there's no need to wait for Wifi configuration to 104 // complete. Just shut it off. 105 mJobExecutor.schedule(mSetInitialWifiStateJob); 106 } 107 return 0; 108 } 109 110 111 @Override 112 public void onDependencyFailed(Exception e) { 113 Log.e(LOG_TAG, "Initial WIFI configuration failed due to dependency.", e); 114 getInitialWifiStateChangeReady().set(e); 115 } 116 117 118 public GceFuture<Boolean> getInitialWifiStateChangeReady() { 119 return mSetInitialWifiStateJob.getWifiReady(); 120 } 121 122 123 /* Configure WIFI network stack. 124 * 125 * Adds network configuration that covers AndroidWifi virtual hotspot. 126 */ 127 private class ConfigureWifi extends JobBase { 128 private final GceFuture<Boolean> mWifiConfigured = 129 new GceFuture<Boolean>("WIFI Configured"); 130 private boolean mReportedWaitingForSupplicant = false; 131 132 133 public ConfigureWifi() { 134 super(LOG_TAG); 135 } 136 137 138 @Override 139 public int execute() { 140 if (mWifiConfigured.isDone()) return 0; 141 142 if (!mWifiManager.pingSupplicant()) { 143 if (!mWifiManager.isWifiEnabled()) { 144 mWifiManager.setWifiEnabled(true); 145 } 146 if (!mReportedWaitingForSupplicant) { 147 Log.i(LOG_TAG, "Supplicant not ready."); 148 mReportedWaitingForSupplicant = true; 149 } 150 return WIFI_RECONNECTION_TIMEOUT_S; 151 } 152 153 WifiConfiguration conf = new WifiConfiguration(); 154 conf.SSID = "\"AndroidWifi\""; 155 conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); 156 int network_id = mWifiManager.addNetwork(conf); 157 if (network_id < 0) { 158 Log.e(LOG_TAG, "Could not update wifi network."); 159 mWifiConfigured.set(new Exception("Could not add WIFI network")); 160 } else { 161 mWifiManager.enableNetwork(network_id, false); 162 mWifiConfigured.set(true); 163 } 164 return 0; 165 } 166 167 168 @Override 169 public void onDependencyFailed(Exception e) { 170 Log.e(LOG_TAG, "Could not configure WIFI.", e); 171 mWifiConfigured.set(e); 172 } 173 174 175 public GceFuture<Boolean> getWifiConfigured() { 176 return mWifiConfigured; 177 } 178 } 179 180 181 /* Modifies Wifi state: 182 * - if wifi disable requested (state == false), simply turns off wifi. 183 * - if wifi enable requested (state == true), turns on wifi and arms the 184 * connection timeout (see startWifiReconnectionTimeout). 185 */ 186 private class SetWifiState extends JobBase { 187 private final GceFuture<Boolean> mWifiReady = 188 new GceFuture<Boolean>("WIFI Ready"); 189 private WifiState mDesiredState = WifiState.DISABLED; 190 private int mWifiStateChangeAttempt = 0; 191 private boolean mReportedWifiNotConnected = false; 192 193 194 public SetWifiState() { 195 super(LOG_TAG); 196 } 197 198 199 public void setState(WifiState state) { 200 mDesiredState = state; 201 } 202 203 204 public synchronized void cancel() { 205 if (!mWifiReady.isDone()) { 206 mWifiReady.cancel(false); 207 } 208 } 209 210 211 @Override 212 public synchronized int execute() { 213 WifiState currentState = mWifiManager.isWifiEnabled() ? 214 WifiState.ENABLED : WifiState.DISABLED; 215 216 // Could be cancelled or exception. 217 if (mWifiReady.isDone()) return 0; 218 219 if (mWifiStateChangeAttempt >= WIFI_RECONNECTION_MAX_ATTEMPTS) { 220 mWifiReady.set(new Exception( 221 String.format("Unable to change wifi state after %d attempts.", 222 WIFI_RECONNECTION_MAX_ATTEMPTS))); 223 return 0; 224 } 225 226 if (currentState == mDesiredState) { 227 switch (currentState) { 228 case ENABLED: 229 // Wifi is enabled, but probably not yet connected. Check. 230 WifiInfo info = mWifiManager.getConnectionInfo(); 231 if (info.getSupplicantState() != SupplicantState.COMPLETED) { 232 if (!mReportedWifiNotConnected) { 233 Log.w(LOG_TAG, "Wifi not yet connected."); 234 mReportedWifiNotConnected = true; 235 } 236 } else { 237 Log.i(LOG_TAG, "Wifi connected."); 238 mWifiReady.set(true); 239 } 240 break; 241 242 case DISABLED: 243 // There's nothing extra to check for disable wifi. 244 mWifiReady.set(true); 245 break; 246 } 247 248 if (mWifiReady.isDone()) { 249 return 0; 250 } 251 } 252 253 // At this point we know that: 254 // - current state is different that desired state, or 255 // - current state is enabled, but wifi is not yet connected. 256 ++mWifiStateChangeAttempt; 257 258 switch (mDesiredState) { 259 case DISABLED: 260 mWifiManager.setWifiEnabled(false); 261 break; 262 263 case ENABLED: 264 mWifiManager.setWifiEnabled(true); 265 mWifiManager.reconnect(); 266 break; 267 } 268 return WIFI_RECONNECTION_TIMEOUT_S; 269 } 270 271 272 @Override 273 public void onDependencyFailed(Exception e) { 274 Log.e(LOG_TAG, "Wifi state change failed due to failing dependency.", e); 275 mWifiReady.set(e); 276 } 277 278 279 public GceFuture<Boolean> getWifiReady() { 280 return mWifiReady; 281 } 282 } 283 } 284