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 notifyListener(StateMachine.RESULT_SUCCESS); 226 } else if (wifiNetworkCapabilities.hasCapability( 227 NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL)) { 228 notifyListener(StateMachine.RESULT_CAPTIVE_PORTAL); 229 } 230 } 231 } else { 232 switch (configuration.getNetworkSelectionStatus() 233 .getNetworkSelectionDisableReason()) { 234 case WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE: 235 case WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD: 236 mUserChoiceInfo.setConnectionFailedStatus( 237 UserChoiceInfo.ConnectionFailedStatus.AUTHENTICATION); 238 break; 239 case WifiConfiguration.NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION: 240 mUserChoiceInfo.setConnectionFailedStatus( 241 UserChoiceInfo.ConnectionFailedStatus.REJECTED); 242 break; 243 case WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE: 244 notifyListener(StateMachine.RESULT_UNKNOWN_ERROR); 245 break; 246 default: 247 mUserChoiceInfo.setConnectionFailedStatus( 248 UserChoiceInfo.ConnectionFailedStatus.UNKNOWN); 249 } 250 notifyListener(StateMachine.RESULT_FAILURE); 251 accessPoint.clearConfig(); 252 } 253 } 254 notifyListener(int result)255 private void notifyListener(int result) { 256 if (mStateMachine.getCurrentState() instanceof ConnectState) { 257 mStateMachine.getListener().onComplete(result); 258 } 259 } 260 261 @Nullable getActiveWifiNetworkCapabilities()262 private NetworkCapabilities getActiveWifiNetworkCapabilities() { 263 Network[] networks = mConnectivityManager.getAllNetworks(); 264 265 for (Network network : networks) { 266 NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(network); 267 if (networkInfo.isConnected() 268 && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { 269 return mConnectivityManager.getNetworkCapabilities(network); 270 } 271 } 272 return null; 273 } 274 getActiveWifiNetworkInfo()275 private NetworkInfo getActiveWifiNetworkInfo() { 276 Network[] networks = mConnectivityManager.getAllNetworks(); 277 278 for (Network network : networks) { 279 NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(network); 280 if (networkInfo.isConnected() 281 && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { 282 return networkInfo; 283 } 284 } 285 return null; 286 } 287 isNetworkConnected()288 private boolean isNetworkConnected() { 289 NetworkInfo netInfo = getActiveWifiNetworkInfo(); 290 if (netInfo == null) { 291 if (DEBUG) Log.d(TAG, "NetworkInfo is null; network is not connected"); 292 return false; 293 } 294 295 if (DEBUG) Log.d(TAG, "NetworkInfo: " + netInfo.toString()); 296 if (netInfo.isConnected() && netInfo.getType() == ConnectivityManager.TYPE_WIFI) { 297 WifiInfo currentConnection = mWifiManager.getConnectionInfo(); 298 if (DEBUG) { 299 Log.d(TAG, "Connected to " 300 + ((currentConnection == null) 301 ? "nothing" : currentConnection.getSSID())); 302 } 303 return currentConnection != null 304 && currentConnection.getSSID().equals(mWifiConfiguration.SSID); 305 } else { 306 if (DEBUG) Log.d(TAG, "Network is not connected"); 307 } 308 return false; 309 } 310 postTimeout()311 private void postTimeout() { 312 mHandler.removeMessages(MSG_TIMEOUT); 313 mHandler.sendEmptyMessageDelayed(MSG_TIMEOUT, CONNECTION_TIMEOUT); 314 } 315 316 private static class MessageHandler extends Handler { 317 318 private final WeakReference<ConnectToWifiFragment> mFragmentRef; 319 MessageHandler(ConnectToWifiFragment fragment)320 MessageHandler(ConnectToWifiFragment fragment) { 321 mFragmentRef = new WeakReference<>(fragment); 322 } 323 324 @Override handleMessage(Message msg)325 public void handleMessage(Message msg) { 326 if (DEBUG) Log.d(TAG, "Timeout waiting on supplicant state change"); 327 328 final ConnectToWifiFragment fragment = mFragmentRef.get(); 329 if (fragment == null) { 330 return; 331 } 332 333 if (fragment.isNetworkConnected()) { 334 if (DEBUG) Log.d(TAG, "Fake timeout; we're actually connected"); 335 fragment.notifyListener(StateMachine.RESULT_SUCCESS); 336 } else { 337 if (DEBUG) Log.d(TAG, "Timeout is real; telling the listener"); 338 UserChoiceInfo userChoiceInfo = ViewModelProviders 339 .of(fragment.getActivity()).get(UserChoiceInfo.class); 340 userChoiceInfo.setConnectionFailedStatus( 341 UserChoiceInfo.ConnectionFailedStatus.TIMEOUT); 342 fragment.notifyListener(StateMachine.RESULT_FAILURE); 343 } 344 } 345 } 346 } 347 } 348