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 static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; 20 21 import android.annotation.Nullable; 22 import android.content.Context; 23 import android.net.ConnectivityManager; 24 import android.net.Network; 25 import android.net.NetworkCapabilities; 26 import android.net.NetworkInfo; 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.util.Log; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.widget.TextView; 38 39 import androidx.annotation.VisibleForTesting; 40 import androidx.fragment.app.Fragment; 41 import androidx.fragment.app.FragmentActivity; 42 import androidx.lifecycle.ViewModelProviders; 43 44 import com.android.settingslib.wifi.AccessPoint; 45 import com.android.tv.settings.R; 46 import com.android.tv.settings.connectivity.ConnectivityListener; 47 import com.android.tv.settings.connectivity.security.WifiSecurityHelper; 48 import com.android.tv.settings.connectivity.util.State; 49 import com.android.tv.settings.connectivity.util.StateMachine; 50 51 import java.lang.ref.WeakReference; 52 import java.util.List; 53 54 /** 55 * State responsible for showing the connect page. 56 */ 57 public class ConnectState implements State { 58 private final FragmentActivity mActivity; 59 private Fragment mFragment; 60 ConnectState(FragmentActivity wifiSetupActivity)61 public ConnectState(FragmentActivity wifiSetupActivity) { 62 this.mActivity = wifiSetupActivity; 63 } 64 65 @Override processForward()66 public void processForward() { 67 FragmentChangeListener listener = (FragmentChangeListener) mActivity; 68 mFragment = ConnectToWifiFragment.newInstance("", true); 69 if (listener != null) { 70 listener.onFragmentChange(mFragment, true); 71 } 72 } 73 74 @Override processBackward()75 public void processBackward() { 76 StateMachine stateMachine = ViewModelProviders.of(mActivity).get(StateMachine.class); 77 stateMachine.back(); 78 } 79 80 @Override getFragment()81 public Fragment getFragment() { 82 return mFragment; 83 } 84 85 /** 86 * Connects to the wifi network specified by the given configuration. 87 */ 88 public static class ConnectToWifiFragment extends MessageFragment 89 implements ConnectivityListener.WifiNetworkListener { 90 91 @VisibleForTesting 92 static final int MSG_TIMEOUT = 1; 93 @VisibleForTesting 94 static final int CONNECTION_TIMEOUT = 60000; 95 private static final String TAG = "ConnectToWifiFragment"; 96 private static final boolean DEBUG = false; 97 @VisibleForTesting 98 StateMachine mStateMachine; 99 @VisibleForTesting 100 WifiConfiguration mWifiConfiguration; 101 @VisibleForTesting 102 WifiManager mWifiManager; 103 @VisibleForTesting 104 Handler mHandler; 105 private ConnectivityListener mConnectivityListener; 106 private ConnectivityManager mConnectivityManager; 107 private UserChoiceInfo mUserChoiceInfo; 108 109 /** 110 * Obtain a new instance of ConnectToWifiFragment. 111 * 112 * @param title title of fragment. 113 * @param showProgressIndicator whether show progress indicator. 114 * @return new instance. 115 */ newInstance(String title, boolean showProgressIndicator)116 public static ConnectToWifiFragment newInstance(String title, 117 boolean showProgressIndicator) { 118 ConnectToWifiFragment fragment = new ConnectToWifiFragment(); 119 Bundle args = new Bundle(); 120 addArguments(args, title, showProgressIndicator); 121 fragment.setArguments(args); 122 return fragment; 123 } 124 125 @Override onCreate(Bundle icicle)126 public void onCreate(Bundle icicle) { 127 super.onCreate(icicle); 128 mUserChoiceInfo = ViewModelProviders.of(getActivity()).get(UserChoiceInfo.class); 129 mConnectivityListener = new ConnectivityListener(getActivity(), null); 130 mConnectivityListener.start(); 131 mConnectivityManager = (ConnectivityManager) getActivity().getSystemService( 132 Context.CONNECTIVITY_SERVICE); 133 134 mUserChoiceInfo = ViewModelProviders.of(getActivity()).get(UserChoiceInfo.class); 135 mWifiConfiguration = WifiSecurityHelper.getConfig(getActivity()); 136 137 mStateMachine = ViewModelProviders 138 .of(getActivity()).get(StateMachine.class); 139 140 mWifiManager = ((WifiManager) getActivity().getApplicationContext() 141 .getSystemService(Context.WIFI_SERVICE)); 142 mHandler = new MessageHandler(this); 143 mConnectivityListener.setWifiListener(this); 144 } 145 146 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle)147 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle) { 148 View view = super.onCreateView(inflater, container, icicle); 149 ((TextView) view.findViewById(R.id.status_text)).setText( 150 getContext().getString(R.string.wifi_connecting, 151 WifiSecurityHelper.getSsid(getActivity()))); 152 return view; 153 } 154 155 @Override onResume()156 public void onResume() { 157 super.onResume(); 158 postTimeout(); 159 proceedDependOnNetworkState(); 160 } 161 162 @VisibleForTesting proceedDependOnNetworkState()163 void proceedDependOnNetworkState() { 164 if (isNetworkConnected()) { 165 mWifiManager.disconnect(); 166 } 167 168 int easyConnectNetworkId = mUserChoiceInfo.getEasyConnectNetworkId(); 169 if (easyConnectNetworkId != -1) { 170 if (DEBUG) Log.d(TAG, "Starting to connect via EasyConnect"); 171 172 mWifiManager.connect(easyConnectNetworkId, new WifiManager.ActionListener() { 173 @Override 174 public void onSuccess() { 175 if (DEBUG) Log.d(TAG, "EasyConnect: onSuccess"); 176 } 177 178 @Override 179 public void onFailure(int reason) { 180 if (DEBUG) Log.d(TAG, "EasyConnect: onFailure, reason = " + reason); 181 } 182 }); 183 } else { 184 mWifiManager.addNetwork(mWifiConfiguration); 185 mWifiManager.connect(mWifiConfiguration, null); 186 } 187 } 188 189 @Override onDestroy()190 public void onDestroy() { 191 if (!isNetworkConnected()) { 192 mWifiManager.disconnect(); 193 } 194 195 mConnectivityListener.stop(); 196 mConnectivityListener.destroy(); 197 mHandler.removeMessages(MSG_TIMEOUT); 198 super.onDestroy(); 199 } 200 201 @Override onWifiListChanged()202 public void onWifiListChanged() { 203 List<AccessPoint> accessPointList = mConnectivityListener.getAvailableNetworks(); 204 if (accessPointList != null) { 205 for (AccessPoint accessPoint : accessPointList) { 206 if (accessPoint != null && AccessPoint.convertToQuotedString( 207 accessPoint.getSsidStr()).equals(mWifiConfiguration.SSID)) { 208 inferConnectionStatus(accessPoint); 209 } 210 } 211 } 212 } 213 inferConnectionStatus(AccessPoint accessPoint)214 private void inferConnectionStatus(AccessPoint accessPoint) { 215 WifiConfiguration configuration = accessPoint.getConfig(); 216 if (configuration == null) { 217 return; 218 } 219 if (configuration.getNetworkSelectionStatus().getNetworkSelectionStatus() 220 == NETWORK_SELECTION_ENABLED) { 221 NetworkCapabilities wifiNetworkCapabilities = getActiveWifiNetworkCapabilities(); 222 if (wifiNetworkCapabilities != null) { 223 if (wifiNetworkCapabilities.hasCapability( 224 NetworkCapabilities.NET_CAPABILITY_VALIDATED) || 225 wifiNetworkCapabilities.hasCapability( 226 NetworkCapabilities.NET_CAPABILITY_INTERNET)) { 227 notifyListener(StateMachine.RESULT_SUCCESS); 228 } else if (wifiNetworkCapabilities.hasCapability( 229 NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL)) { 230 notifyListener(StateMachine.RESULT_CAPTIVE_PORTAL); 231 } 232 233 } 234 } else { 235 switch (configuration.getNetworkSelectionStatus() 236 .getNetworkSelectionDisableReason()) { 237 case WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE: 238 case WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD: 239 mUserChoiceInfo.setConnectionFailedStatus( 240 UserChoiceInfo.ConnectionFailedStatus.AUTHENTICATION); 241 break; 242 case WifiConfiguration.NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION: 243 mUserChoiceInfo.setConnectionFailedStatus( 244 UserChoiceInfo.ConnectionFailedStatus.REJECTED); 245 break; 246 case WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE: 247 notifyListener(StateMachine.RESULT_UNKNOWN_ERROR); 248 break; 249 default: 250 mUserChoiceInfo.setConnectionFailedStatus( 251 UserChoiceInfo.ConnectionFailedStatus.UNKNOWN); 252 } 253 notifyListener(StateMachine.RESULT_FAILURE); 254 accessPoint.clearConfig(); 255 } 256 } 257 notifyListener(int result)258 private void notifyListener(int result) { 259 if (mStateMachine.getCurrentState() instanceof ConnectState) { 260 mStateMachine.getListener().onComplete(result); 261 } 262 } 263 264 @Nullable getActiveWifiNetworkCapabilities()265 private NetworkCapabilities getActiveWifiNetworkCapabilities() { 266 Network[] networks = mConnectivityManager.getAllNetworks(); 267 268 for (Network network : networks) { 269 NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(network); 270 if (networkInfo.isConnected() 271 && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { 272 return mConnectivityManager.getNetworkCapabilities(network); 273 } 274 } 275 return null; 276 } 277 getActiveWifiNetworkInfo()278 private NetworkInfo getActiveWifiNetworkInfo() { 279 Network[] networks = mConnectivityManager.getAllNetworks(); 280 281 for (Network network : networks) { 282 NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(network); 283 if (networkInfo.isConnected() 284 && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { 285 return networkInfo; 286 } 287 } 288 return null; 289 } 290 isNetworkConnected()291 private boolean isNetworkConnected() { 292 NetworkInfo netInfo = getActiveWifiNetworkInfo(); 293 if (netInfo == null) { 294 if (DEBUG) Log.d(TAG, "NetworkInfo is null; network is not connected"); 295 return false; 296 } 297 298 if (DEBUG) Log.d(TAG, "NetworkInfo: " + netInfo.toString()); 299 if (netInfo.isConnected() && netInfo.getType() == ConnectivityManager.TYPE_WIFI) { 300 WifiInfo currentConnection = mWifiManager.getConnectionInfo(); 301 if (DEBUG) { 302 Log.d(TAG, "Connected to " 303 + ((currentConnection == null) 304 ? "nothing" : currentConnection.getSSID())); 305 } 306 return currentConnection != null 307 && currentConnection.getSSID().equals(mWifiConfiguration.SSID); 308 } else { 309 if (DEBUG) Log.d(TAG, "Network is not connected"); 310 } 311 return false; 312 } 313 postTimeout()314 private void postTimeout() { 315 mHandler.removeMessages(MSG_TIMEOUT); 316 mHandler.sendEmptyMessageDelayed(MSG_TIMEOUT, CONNECTION_TIMEOUT); 317 } 318 319 private static class MessageHandler extends Handler { 320 321 private final WeakReference<ConnectToWifiFragment> mFragmentRef; 322 MessageHandler(ConnectToWifiFragment fragment)323 MessageHandler(ConnectToWifiFragment fragment) { 324 mFragmentRef = new WeakReference<>(fragment); 325 } 326 327 @Override handleMessage(Message msg)328 public void handleMessage(Message msg) { 329 if (DEBUG) Log.d(TAG, "Timeout waiting on supplicant state change"); 330 331 final ConnectToWifiFragment fragment = mFragmentRef.get(); 332 if (fragment == null) { 333 return; 334 } 335 336 if (fragment.isNetworkConnected()) { 337 if (DEBUG) Log.d(TAG, "Fake timeout; we're actually connected"); 338 fragment.notifyListener(StateMachine.RESULT_SUCCESS); 339 } else { 340 if (DEBUG) Log.d(TAG, "Timeout is real; telling the listener"); 341 UserChoiceInfo userChoiceInfo = ViewModelProviders 342 .of(fragment.getActivity()).get(UserChoiceInfo.class); 343 userChoiceInfo.setConnectionFailedStatus( 344 UserChoiceInfo.ConnectionFailedStatus.TIMEOUT); 345 fragment.notifyListener(StateMachine.RESULT_FAILURE); 346 } 347 } 348 } 349 } 350 } 351