1 /*
2  * Copyright 2014, 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.managedprovisioning.task;
18 
19 import static com.android.internal.util.Preconditions.checkNotNull;
20 
21 import android.content.Context;
22 import android.net.ConnectivityManager;
23 import android.net.wifi.WifiConfiguration;
24 import android.net.wifi.WifiInfo;
25 import android.net.wifi.WifiManager;
26 import android.os.Handler;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.managedprovisioning.analytics.MetricsWriterFactory;
30 import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker;
31 import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences;
32 import com.android.managedprovisioning.common.ProvisionLogger;
33 import com.android.managedprovisioning.common.SettingsFacade;
34 import com.android.managedprovisioning.common.Utils;
35 import com.android.managedprovisioning.model.ProvisioningParams;
36 import com.android.managedprovisioning.task.wifi.NetworkMonitor;
37 import com.android.managedprovisioning.task.wifi.WifiConfigurationProvider;
38 
39 /**
40  * Adds a wifi network to the system and waits for it to successfully connect. If the system does
41  * not support wifi, the adding or connection times out {@link #error(int)} will be called.
42  */
43 public class AddWifiNetworkTask extends AbstractProvisioningTask
44         implements NetworkMonitor.NetworkConnectedCallback {
45     private static final int RETRY_SLEEP_DURATION_BASE_MS = 500;
46     private static final int RETRY_SLEEP_MULTIPLIER = 2;
47     private static final int MAX_RETRIES = 6;
48     private static final int RECONNECT_TIMEOUT_MS = 60000;
49     @VisibleForTesting  static final int ADD_NETWORK_FAIL = -1;
50 
51     private final WifiConfigurationProvider mWifiConfigurationProvider;
52     private final WifiManager mWifiManager;
53     private final NetworkMonitor mNetworkMonitor;
54 
55     private Handler mHandler;
56     private boolean mTaskDone = false;
57 
58     private final Utils mUtils;
59     private Runnable mTimeoutRunnable;
60     private Injector mInjector;
61 
AddWifiNetworkTask( Context context, ProvisioningParams provisioningParams, Callback callback)62     public AddWifiNetworkTask(
63             Context context,
64             ProvisioningParams provisioningParams,
65             Callback callback) {
66         this(
67                 new NetworkMonitor(context, /* waitForValidated */ false),
68                 new WifiConfigurationProvider(),
69                 context, provisioningParams, callback, new Utils(), new Injector(),
70                 new ProvisioningAnalyticsTracker(
71                         MetricsWriterFactory.getMetricsWriter(context, new SettingsFacade()),
72                         new ManagedProvisioningSharedPreferences(context)));
73     }
74 
75     @VisibleForTesting
AddWifiNetworkTask( NetworkMonitor networkMonitor, WifiConfigurationProvider wifiConfigurationProvider, Context context, ProvisioningParams provisioningParams, Callback callback, Utils utils, Injector injector, ProvisioningAnalyticsTracker provisioningAnalyticsTracker)76     AddWifiNetworkTask(
77             NetworkMonitor networkMonitor,
78             WifiConfigurationProvider wifiConfigurationProvider,
79             Context context,
80             ProvisioningParams provisioningParams,
81             Callback callback,
82             Utils utils,
83             Injector injector,
84             ProvisioningAnalyticsTracker provisioningAnalyticsTracker) {
85         super(context, provisioningParams, callback, provisioningAnalyticsTracker);
86 
87         mNetworkMonitor = checkNotNull(networkMonitor);
88         mWifiConfigurationProvider = checkNotNull(wifiConfigurationProvider);
89         mWifiManager  = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
90         mUtils = checkNotNull(utils);
91         mInjector = checkNotNull(injector);
92     }
93 
94     @Override
run(int userId)95     public void run(int userId) {
96         if (mProvisioningParams.wifiInfo == null) {
97             success();
98             return;
99         }
100 
101         if (mWifiManager == null || !enableWifi()) {
102             ProvisionLogger.loge("Failed to enable wifi");
103             error(0);
104             return;
105         }
106 
107         if (isConnectedToSpecifiedWifi()) {
108             success();
109             return;
110         }
111 
112         mTaskDone = false;
113         mHandler = new Handler();
114         mNetworkMonitor.startListening(this);
115         connectToProvidedNetwork();
116     }
117 
connectToProvidedNetwork()118     private void connectToProvidedNetwork() {
119         WifiConfiguration wifiConf =
120                 mWifiConfigurationProvider.generateWifiConfiguration(mProvisioningParams.wifiInfo);
121 
122         if (wifiConf == null) {
123             ProvisionLogger.loge("WifiConfiguration is null");
124             error(0);
125             return;
126         }
127 
128         int netId = tryAddingNetwork(wifiConf);
129 
130         if (netId == ADD_NETWORK_FAIL) {
131             ProvisionLogger.loge("Unable to add network after trying " +  MAX_RETRIES + " times.");
132             error(0);
133             return;
134         }
135 
136         // Setting disableOthers to 'true' should trigger a connection attempt.
137         mWifiManager.enableNetwork(netId, true);
138         mWifiManager.saveConfiguration();
139 
140         // Network was successfully saved, now connect to it.
141         if (!mWifiManager.reconnect()) {
142             ProvisionLogger.loge("Unable to connect to wifi");
143             error(0);
144             return;
145         }
146 
147         // NetworkMonitor will call onNetworkConnected when in Wifi mode.
148         // Post time out event in case the NetworkMonitor doesn't call back.
149         mTimeoutRunnable = () -> finishTask(false);
150         mHandler.postDelayed(mTimeoutRunnable, RECONNECT_TIMEOUT_MS);
151     }
152 
tryAddingNetwork(WifiConfiguration wifiConf)153     private int tryAddingNetwork(WifiConfiguration wifiConf) {
154         int netId = mWifiManager.addNetwork(wifiConf);
155         int retriesLeft = MAX_RETRIES;
156         int durationNextSleep = RETRY_SLEEP_DURATION_BASE_MS;
157 
158         while(netId == -1 && retriesLeft > 0) {
159             ProvisionLogger.loge("Retrying in " + durationNextSleep + " ms.");
160             try {
161                 mInjector.threadSleep(durationNextSleep);
162             } catch (InterruptedException e) {
163                 ProvisionLogger.loge("Retry interrupted.");
164             }
165             durationNextSleep *= RETRY_SLEEP_MULTIPLIER;
166             retriesLeft--;
167             netId = mWifiManager.addNetwork(wifiConf);
168         }
169         return netId;
170     }
171 
enableWifi()172     private boolean enableWifi() {
173         return mWifiManager.isWifiEnabled() || mWifiManager.setWifiEnabled(true);
174     }
175 
176     @Override
onNetworkConnected()177     public void onNetworkConnected() {
178         ProvisionLogger.logd("onNetworkConnected");
179         if (isConnectedToSpecifiedWifi()) {
180             ProvisionLogger.logd("Connected to the correct network");
181             finishTask(true);
182             // Remove time out callback.
183             mHandler.removeCallbacks(mTimeoutRunnable);
184         }
185     }
186 
finishTask(boolean isSuccess)187     private synchronized void finishTask(boolean isSuccess) {
188         if (mTaskDone) {
189             return;
190         }
191 
192         mTaskDone = true;
193         mNetworkMonitor.stopListening();
194         if (isSuccess) {
195             success();
196         } else {
197             error(0);
198         }
199     }
200 
isConnectedToSpecifiedWifi()201     private boolean isConnectedToSpecifiedWifi() {
202         if (!mUtils.isNetworkTypeConnected(mContext, ConnectivityManager.TYPE_WIFI)) {
203             ProvisionLogger.logd("Not connected to WIFI");
204             return false;
205         }
206         WifiInfo connectionInfo = mWifiManager.getConnectionInfo();
207         if (connectionInfo == null) {
208             ProvisionLogger.logd("connection info is null");
209             return false;
210         }
211         String connectedSSID = mWifiManager.getConnectionInfo().getSSID();
212         if (!mProvisioningParams.wifiInfo.ssid.equals(connectedSSID)) {
213             ProvisionLogger.logd("Wanted to connect SSID " + mProvisioningParams.wifiInfo.ssid
214                     + ", but it is now connected to " + connectedSSID);
215             return false;
216         }
217         return true;
218     }
219 
220     @VisibleForTesting
221     static class Injector {
threadSleep(long milliseconds)222         public void threadSleep(long milliseconds) throws InterruptedException {
223             Thread.sleep(milliseconds);
224         }
225     }
226 }
227