Home | History | Annotate | Download | only in gceservice
      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