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