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 17 package com.android.tv.settings.connectivity.setup; 18 19 import android.arch.lifecycle.ViewModelProviders; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.net.ConnectivityManager; 25 import android.net.NetworkInfo; 26 import android.net.wifi.SupplicantState; 27 import android.net.wifi.WifiConfiguration; 28 import android.net.wifi.WifiInfo; 29 import android.net.wifi.WifiManager; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.Message; 33 import android.support.annotation.VisibleForTesting; 34 import android.support.v4.app.Fragment; 35 import android.support.v4.app.FragmentActivity; 36 import android.util.Log; 37 38 import com.android.tv.settings.R; 39 import com.android.tv.settings.connectivity.ConnectivityListener; 40 import com.android.tv.settings.connectivity.WifiConfigHelper; 41 import com.android.tv.settings.connectivity.util.State; 42 import com.android.tv.settings.connectivity.util.StateMachine; 43 44 import java.lang.ref.WeakReference; 45 46 /** 47 * State responsible for showing the connect page. 48 */ 49 public class ConnectState implements State { 50 private final FragmentActivity mActivity; 51 private Fragment mFragment; 52 53 public ConnectState(FragmentActivity wifiSetupActivity) { 54 this.mActivity = wifiSetupActivity; 55 } 56 57 @Override 58 public void processForward() { 59 UserChoiceInfo userChoiceInfo = ViewModelProviders.of(mActivity).get(UserChoiceInfo.class); 60 WifiConfiguration wifiConfig = userChoiceInfo.getWifiConfiguration(); 61 String title = mActivity.getString( 62 R.string.wifi_connecting, 63 wifiConfig.getPrintableSsid()); 64 mFragment = ConnectToWifiFragment.newInstance(title, true); 65 if (!WifiConfigHelper.isNetworkSaved(wifiConfig)) { 66 AdvancedOptionsFlowInfo advFlowInfo = 67 ViewModelProviders.of(mActivity).get(AdvancedOptionsFlowInfo.class); 68 if (advFlowInfo.getIpConfiguration() != null) { 69 wifiConfig.setIpConfiguration(advFlowInfo.getIpConfiguration()); 70 } 71 } 72 FragmentChangeListener listener = (FragmentChangeListener) mActivity; 73 if (listener != null) { 74 listener.onFragmentChange(mFragment, true); 75 } 76 } 77 78 @Override 79 public void processBackward() { 80 StateMachine stateMachine = ViewModelProviders.of(mActivity).get(StateMachine.class); 81 stateMachine.back(); 82 } 83 84 @Override 85 public Fragment getFragment() { 86 return mFragment; 87 } 88 89 /** 90 * Connects to the wifi network specified by the given configuration. 91 */ 92 public static class ConnectToWifiFragment extends MessageFragment 93 implements ConnectivityListener.Listener { 94 95 @VisibleForTesting 96 static final int MSG_TIMEOUT = 1; 97 @VisibleForTesting 98 static final int CONNECTION_TIMEOUT = 15000; 99 private static final String TAG = "ConnectToWifiFragment"; 100 private static final boolean DEBUG = false; 101 @VisibleForTesting 102 StateMachine mStateMachine; 103 @VisibleForTesting 104 WifiConfiguration mWifiConfiguration; 105 @VisibleForTesting 106 WifiManager mWifiManager; 107 @VisibleForTesting 108 Handler mHandler; 109 private ConnectivityListener mConnectivityListener; 110 private BroadcastReceiver mReceiver; 111 private boolean mWasAssociating; 112 private boolean mWasAssociated; 113 private boolean mWasHandshaking; 114 private boolean mConnected; 115 116 /** 117 * Obtain a new instance of ConnectToWifiFragment. 118 * 119 * @param title title of fragment. 120 * @param showProgressIndicator whether show progress indicator. 121 * @return new instance. 122 */ 123 public static ConnectToWifiFragment newInstance(String title, 124 boolean showProgressIndicator) { 125 ConnectToWifiFragment fragment = new ConnectToWifiFragment(); 126 Bundle args = new Bundle(); 127 addArguments(args, title, showProgressIndicator); 128 fragment.setArguments(args); 129 return fragment; 130 } 131 132 @Override 133 public void onCreate(Bundle icicle) { 134 super.onCreate(icicle); 135 mConnectivityListener = new ConnectivityListener(getActivity(), this); 136 UserChoiceInfo userChoiceInfo = ViewModelProviders 137 .of(getActivity()).get(UserChoiceInfo.class); 138 mWifiConfiguration = userChoiceInfo.getWifiConfiguration(); 139 mStateMachine = ViewModelProviders 140 .of(getActivity()).get(StateMachine.class); 141 142 mWifiManager = ((WifiManager) getActivity().getApplicationContext() 143 .getSystemService(Context.WIFI_SERVICE)); 144 mHandler = new MessageHandler(this); 145 146 IntentFilter filter = new IntentFilter(); 147 filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); 148 mReceiver = new BroadcastReceiver() { 149 @Override 150 public void onReceive(Context context, Intent intent) { 151 if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(intent.getAction())) { 152 SupplicantState state = intent.getParcelableExtra( 153 WifiManager.EXTRA_NEW_STATE); 154 if (DEBUG) { 155 Log.d(TAG, "Got supplicant state: " + state.name()); 156 } 157 switch (state) { 158 case ASSOCIATING: 159 mWasAssociating = true; 160 break; 161 case ASSOCIATED: 162 mWasAssociated = true; 163 break; 164 case COMPLETED: 165 // this just means the supplicant has connected, now 166 // we wait for the rest of the framework to catch up 167 break; 168 case DISCONNECTED: 169 case DORMANT: 170 if (mWasAssociated || mWasHandshaking) { 171 notifyListener(mWasHandshaking ? StateMachine.RESULT_BAD_AUTH 172 : StateMachine.RESULT_UNKNOWN_ERROR); 173 } 174 break; 175 case INTERFACE_DISABLED: 176 case UNINITIALIZED: 177 notifyListener(StateMachine.RESULT_UNKNOWN_ERROR); 178 break; 179 case FOUR_WAY_HANDSHAKE: 180 case GROUP_HANDSHAKE: 181 mWasHandshaking = true; 182 break; 183 case INACTIVE: 184 if (mWasAssociating && !mWasAssociated) { 185 // If we go inactive after 'associating' without ever having 186 // been 'associated', the AP(s) must have rejected us. 187 notifyListener(StateMachine.RESULT_REJECTED_BY_AP); 188 break; 189 } 190 case INVALID: 191 break; 192 case SCANNING: 193 break; 194 default: 195 return; 196 } 197 mHandler.removeMessages(MSG_TIMEOUT); 198 mHandler.sendEmptyMessageDelayed(MSG_TIMEOUT, CONNECTION_TIMEOUT); 199 } 200 } 201 }; 202 getActivity().registerReceiver(mReceiver, filter); 203 mConnectivityListener.start(); 204 } 205 206 @Override 207 public void onResume() { 208 super.onResume(); 209 proceedDependOnNetworkState(); 210 } 211 212 @VisibleForTesting 213 void proceedDependOnNetworkState() { 214 if (isNetworkConnected()) { 215 mConnected = true; 216 notifyListener(StateMachine.RESULT_SUCCESS); 217 } else { 218 int networkId = mWifiManager.addNetwork(mWifiConfiguration); 219 if (networkId == -1) { 220 if (DEBUG) { 221 Log.d(TAG, "Failed to add network!"); 222 } 223 notifyListener(StateMachine.RESULT_UNKNOWN_ERROR); 224 } else if (!mWifiManager.enableNetwork(networkId, true)) { 225 if (DEBUG) { 226 Log.d(TAG, "Failed to enable network id " + networkId + "!"); 227 } 228 notifyListener(StateMachine.RESULT_UNKNOWN_ERROR); 229 } else if (!mWifiManager.reconnect()) { 230 if (DEBUG) { 231 Log.d(TAG, "Failed to reconnect!"); 232 } 233 notifyListener(StateMachine.RESULT_UNKNOWN_ERROR); 234 } else { 235 mHandler.sendEmptyMessageDelayed(MSG_TIMEOUT, CONNECTION_TIMEOUT); 236 } 237 } 238 } 239 240 @Override 241 public void onDestroy() { 242 if (!mConnected) { 243 mWifiManager.disconnect(); 244 } 245 getActivity().unregisterReceiver(mReceiver); 246 mConnectivityListener.stop(); 247 mConnectivityListener.destroy(); 248 mHandler.removeMessages(MSG_TIMEOUT); 249 super.onDestroy(); 250 } 251 252 @Override 253 public void onConnectivityChange() { 254 if (DEBUG) Log.d(TAG, "Connectivity changed"); 255 if (!isResumed()) { 256 return; 257 } 258 if (isNetworkConnected()) { 259 mConnected = true; 260 notifyListener(StateMachine.RESULT_SUCCESS); 261 } 262 } 263 264 private void notifyListener(int result) { 265 if (mStateMachine.getCurrentState() instanceof ConnectState) { 266 mStateMachine.getListener().onComplete(result); 267 } 268 } 269 270 private boolean isNetworkConnected() { 271 ConnectivityManager connMan = getActivity().getSystemService(ConnectivityManager.class); 272 NetworkInfo netInfo = connMan.getActiveNetworkInfo(); 273 if (netInfo == null) { 274 if (DEBUG) Log.d(TAG, "NetworkInfo is null; network is not connected"); 275 return false; 276 } 277 278 if (DEBUG) Log.d(TAG, "NetworkInfo: " + netInfo.toString()); 279 if (netInfo.isConnected() && netInfo.getType() == ConnectivityManager.TYPE_WIFI) { 280 WifiInfo currentConnection = mWifiManager.getConnectionInfo(); 281 if (DEBUG) { 282 Log.d(TAG, "Connected to " 283 + ((currentConnection == null) 284 ? "nothing" : currentConnection.getSSID())); 285 } 286 if (currentConnection != null 287 && currentConnection.getSSID().equals(mWifiConfiguration.SSID)) { 288 return true; 289 } 290 } else { 291 if (DEBUG) Log.d(TAG, "Network is not connected"); 292 } 293 return false; 294 } 295 296 private static class MessageHandler extends Handler { 297 298 private final WeakReference<ConnectToWifiFragment> mFragmentRef; 299 300 MessageHandler(ConnectToWifiFragment fragment) { 301 mFragmentRef = new WeakReference<>(fragment); 302 } 303 304 @Override 305 public void handleMessage(Message msg) { 306 if (DEBUG) Log.d(TAG, "Timeout waiting on supplicant state change"); 307 308 final ConnectToWifiFragment fragment = mFragmentRef.get(); 309 if (fragment == null) { 310 return; 311 } 312 313 if (fragment.isNetworkConnected()) { 314 if (DEBUG) Log.d(TAG, "Fake timeout; we're actually connected"); 315 fragment.mConnected = true; 316 fragment.notifyListener(StateMachine.RESULT_SUCCESS); 317 } else { 318 if (DEBUG) Log.d(TAG, "Timeout is real; telling the listener"); 319 fragment.notifyListener(StateMachine.RESULT_TIMEOUT); 320 } 321 } 322 } 323 } 324 } 325