1 /*
2  * Copyright (C) 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.tv.settings.connectivity;
18 
19 import com.android.tv.settings.connectivity.ConnectivityListener;
20 import com.android.tv.settings.connectivity.SaveWifiConfigurationFragment.Listener;
21 import com.android.tv.settings.connectivity.setup.MessageWizardFragment;
22 import com.android.tv.settings.form.FormPage;
23 
24 import android.app.Activity;
25 import android.app.Fragment;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.net.ConnectivityManager;
31 import android.net.NetworkInfo;
32 import android.net.wifi.SupplicantState;
33 import android.net.wifi.WifiConfiguration;
34 import android.net.wifi.WifiInfo;
35 import android.net.wifi.WifiManager;
36 import android.os.Bundle;
37 import android.os.Handler;
38 import android.os.Message;
39 import android.util.Log;
40 
41 import java.util.List;
42 
43 /**
44  * Connects to the wifi network specified by the given configuration.
45  */
46 public class ConnectToWifiFragment extends MessageWizardFragment
47         implements ConnectivityListener.Listener {
48 
49     public interface Listener {
onConnectToWifiCompleted(int reason)50         void onConnectToWifiCompleted(int reason);
51     }
52 
53     private static final String TAG = "ConnectToWifiFragment";
54     private static final boolean DEBUG = false;
55 
56     public static final int RESULT_SUCCESS = 0;
57     public static final int RESULT_UNKNOWN_ERROR= 1;
58     public static final int RESULT_TIMEOUT = 2;
59     public static final int RESULT_BAD_AUTHENTICATION = 3;
60     public static final int RESULT_REJECTED_BY_AP = 4;
61 
62     private static final String EXTRA_CONFIGURATION = "configuration";
63     private static final int MSG_TIMEOUT = 1;
64     private static final int CONNECTION_TIMEOUT = 15000;
65 
newInstance(String title, boolean showProgressIndicator, WifiConfiguration configuration)66     public static ConnectToWifiFragment newInstance(String title, boolean showProgressIndicator,
67             WifiConfiguration configuration) {
68         ConnectToWifiFragment fragment = new ConnectToWifiFragment();
69         Bundle args = new Bundle();
70         args.putParcelable(EXTRA_CONFIGURATION, configuration);
71         addArguments(args, title, showProgressIndicator);
72         fragment.setArguments(args);
73         return fragment;
74     }
75 
76     private Listener mListener;
77     private ConnectivityListener mConnectivityListener;
78     private WifiConfiguration mWifiConfiguration;
79     private WifiManager mWifiManager;
80     private Handler mHandler;
81     private BroadcastReceiver mReceiver;
82     private boolean mWasAssociating;
83     private boolean mWasAssociated;
84     private boolean mWasHandshaking;
85     private boolean mConnected;
86 
87     @Override
onAttach(Activity activity)88     public void onAttach(Activity activity) {
89         if (activity instanceof Listener) {
90             mListener = (Listener) activity;
91         } else {
92             throw new IllegalArgumentException("Activity must implement "
93                     + "ConnectToWifiFragment.Listener to use this fragment.");
94         }
95         super.onAttach(activity);
96     }
97 
98     @Override
onDetach()99     public void onDetach() {
100         super.onDetach();
101         mListener = null;
102     }
103 
104     @Override
onCreate(Bundle icicle)105     public void onCreate(Bundle icicle) {
106         super.onCreate(icicle);
107 
108         mConnectivityListener = new ConnectivityListener(getActivity(), this);
109         mWifiConfiguration = (WifiConfiguration) getArguments().getParcelable(EXTRA_CONFIGURATION);
110         mWifiManager = ((WifiManager) getActivity().getSystemService(Context.WIFI_SERVICE));
111         mHandler = new Handler() {
112             @Override
113             public void handleMessage(Message msg) {
114                 if (DEBUG) Log.d(TAG, "Timeout waiting on supplicant state change");
115                 if (isNetworkConnected()) {
116                     if (DEBUG) Log.d(TAG, "Fake timeout; we're actually connected");
117                     mConnected = true;
118                     notifyListener(RESULT_SUCCESS);
119                 } else {
120                     if (DEBUG) Log.d(TAG, "Timeout is real; telling the listener");
121                     notifyListener(RESULT_TIMEOUT);
122                 }
123             }
124         };
125 
126         IntentFilter filter = new IntentFilter();
127         filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
128         mReceiver = new BroadcastReceiver() {
129             @Override
130             public void onReceive(Context context, Intent intent) {
131                 if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(intent.getAction())) {
132                     SupplicantState state = (SupplicantState) intent.getParcelableExtra(
133                             WifiManager.EXTRA_NEW_STATE);
134                     if (DEBUG) {
135                         Log.d(TAG, "Got supplicant state: " + state.name());
136                     }
137                     switch (state) {
138                         case ASSOCIATING:
139                             mWasAssociating = true;
140                             break;
141                         case ASSOCIATED:
142                             mWasAssociated = true;
143                             break;
144                         case COMPLETED:
145                             // this just means the supplicant has connected, now
146                             // we wait for the rest of the framework to catch up
147                             break;
148                         case DISCONNECTED:
149                         case DORMANT:
150                             if (mWasAssociated || mWasHandshaking) {
151                                 notifyListener(mWasHandshaking ? RESULT_BAD_AUTHENTICATION
152                                         : RESULT_UNKNOWN_ERROR);
153                             }
154                             break;
155                         case INTERFACE_DISABLED:
156                         case UNINITIALIZED:
157                             notifyListener(RESULT_UNKNOWN_ERROR);
158                             break;
159                         case FOUR_WAY_HANDSHAKE:
160                         case GROUP_HANDSHAKE:
161                             mWasHandshaking = true;
162                             break;
163                         case INACTIVE:
164                             if (mWasAssociating && !mWasAssociated) {
165                                 // If we go inactive after 'associating' without ever having
166                                 // been 'associated', the AP(s) must have rejected us.
167                                 notifyListener(RESULT_REJECTED_BY_AP);
168                                 break;
169                             }
170                         case INVALID:
171                         case SCANNING:
172                         default:
173                             return;
174                     }
175                     mHandler.removeMessages(MSG_TIMEOUT);
176                     mHandler.sendEmptyMessageDelayed(MSG_TIMEOUT, CONNECTION_TIMEOUT);
177                 }
178             }
179         };
180         getActivity().registerReceiver(mReceiver, filter);
181         mConnectivityListener.start();
182 
183         if (isNetworkConnected()) {
184             mConnected = true;
185             notifyListener(RESULT_SUCCESS);
186         } else {
187             int networkId = mWifiManager.addNetwork(mWifiConfiguration);
188             if (networkId == -1) {
189                 if (DEBUG) {
190                     Log.d(TAG, "Failed to add network!");
191                 }
192                 notifyListener(RESULT_UNKNOWN_ERROR);
193             } else if (!mWifiManager.enableNetwork(networkId, true)) {
194                 if (DEBUG) {
195                     Log.d(TAG, "Failed to enable network id " + networkId + "!");
196                 }
197                 notifyListener(RESULT_UNKNOWN_ERROR);
198             } else if (!mWifiManager.reconnect()) {
199                 if (DEBUG) {
200                     Log.d(TAG, "Failed to reconnect!");
201                 }
202                 notifyListener(RESULT_UNKNOWN_ERROR);
203             } else {
204                 mHandler.sendEmptyMessageDelayed(MSG_TIMEOUT, CONNECTION_TIMEOUT);
205             }
206         }
207     }
208 
209     @Override
onDestroy()210     public void onDestroy() {
211         if (!mConnected) {
212             mWifiManager.disconnect();
213         }
214         getActivity().unregisterReceiver(mReceiver);
215         mConnectivityListener.stop();
216         mHandler.removeMessages(MSG_TIMEOUT);
217         super.onDestroy();
218     }
219 
220     @Override
onConnectivityChange(Intent intent)221     public void onConnectivityChange(Intent intent) {
222         if (DEBUG) Log.d(TAG, "Connectivity changed");
223         if (isNetworkConnected()) {
224             mConnected = true;
225             notifyListener(RESULT_SUCCESS);
226         }
227     }
228 
notifyListener(int result)229     private void notifyListener(int result) {
230         if (mListener != null) {
231             mListener.onConnectToWifiCompleted(result);
232             mListener = null;
233         }
234     }
235 
isNetworkConnected()236     private boolean isNetworkConnected() {
237         ConnectivityManager connMan =
238                 (ConnectivityManager) getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
239         NetworkInfo netInfo = connMan.getActiveNetworkInfo();
240         if (netInfo == null) {
241             if (DEBUG) Log.d(TAG, "NetworkInfo is null; network is not connected");
242             return false;
243         }
244 
245         if (DEBUG) Log.d(TAG, "NetworkInfo: " + netInfo.toString());
246         if (netInfo.isConnected() && netInfo.getType() == ConnectivityManager.TYPE_WIFI) {
247             WifiInfo currentConnection = mWifiManager.getConnectionInfo();
248             if (DEBUG) {
249                 Log.d(TAG, "Connected to "
250                         + ((currentConnection == null) ? "nothing" : currentConnection.getSSID()));
251             }
252             if (currentConnection != null
253                     && currentConnection.getSSID().equals(mWifiConfiguration.SSID)) {
254                 return true;
255             }
256         } else {
257             if (DEBUG) Log.d(TAG, "Network is not connected");
258         }
259         return false;
260     }
261 }
262