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