1 /*
2  * Copyright (C) 2010 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.settings.wifi;
18 
19 import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
20 import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
21 
22 import android.app.Activity;
23 import android.app.ActivityManager;
24 import android.app.Dialog;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.DialogInterface;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.res.Resources;
31 import android.content.res.TypedArray;
32 import android.location.LocationManager;
33 import android.net.ConnectivityManager;
34 import android.net.NetworkInfo;
35 import android.net.NetworkInfo.DetailedState;
36 import android.net.NetworkInfo.State;
37 import android.net.wifi.ScanResult;
38 import android.net.wifi.WifiConfiguration;
39 import android.net.wifi.WifiInfo;
40 import android.net.wifi.WifiManager;
41 import android.net.wifi.WpsInfo;
42 import android.nfc.NfcAdapter;
43 import android.os.Bundle;
44 import android.os.Handler;
45 import android.os.Message;
46 import android.os.UserHandle;
47 import android.preference.Preference;
48 import android.preference.PreferenceScreen;
49 import android.util.Log;
50 import android.view.ContextMenu;
51 import android.view.ContextMenu.ContextMenuInfo;
52 import android.view.Menu;
53 import android.view.MenuInflater;
54 import android.view.MenuItem;
55 import android.view.View;
56 import android.widget.AdapterView.AdapterContextMenuInfo;
57 import android.widget.TextView;
58 import android.widget.Toast;
59 
60 import com.android.settings.R;
61 import com.android.settings.RestrictedSettingsFragment;
62 import com.android.settings.SettingsActivity;
63 import com.android.settings.search.BaseSearchIndexProvider;
64 import com.android.settings.search.Indexable;
65 import com.android.settings.search.SearchIndexableRaw;
66 
67 import java.util.ArrayList;
68 import java.util.Collection;
69 import java.util.Collections;
70 import java.util.HashMap;
71 import java.util.List;
72 import java.util.concurrent.atomic.AtomicBoolean;
73 
74 /**
75  * Two types of UI are provided here.
76  *
77  * The first is for "usual Settings", appearing as any other Setup fragment.
78  *
79  * The second is for Setup Wizard, with a simplified interface that hides the action bar
80  * and menus.
81  */
82 public class WifiSettings extends RestrictedSettingsFragment
83         implements DialogInterface.OnClickListener, Indexable  {
84 
85     private static final String TAG = "WifiSettings";
86 
87     /* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST;
88     private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
89     private static final int MENU_ID_SAVED_NETWORK = Menu.FIRST + 2;
90     /* package */ static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 3;
91     private static final int MENU_ID_ADVANCED = Menu.FIRST + 4;
92     private static final int MENU_ID_SCAN = Menu.FIRST + 5;
93     private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
94     private static final int MENU_ID_FORGET = Menu.FIRST + 7;
95     private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
96     private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
97 
98     public static final int WIFI_DIALOG_ID = 1;
99     /* package */ static final int WPS_PBC_DIALOG_ID = 2;
100     private static final int WPS_PIN_DIALOG_ID = 3;
101     private static final int WRITE_NFC_DIALOG_ID = 6;
102 
103     // Combo scans can take 5-6s to complete - set to 10s.
104     private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
105 
106     // Instance state keys
107     private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode";
108     private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
109 
110     private static boolean savedNetworksExist;
111 
112     private final IntentFilter mFilter;
113     private final BroadcastReceiver mReceiver;
114     private final Scanner mScanner;
115 
116     /* package */ WifiManager mWifiManager;
117     private WifiManager.ActionListener mConnectListener;
118     private WifiManager.ActionListener mSaveListener;
119     private WifiManager.ActionListener mForgetListener;
120 
121     private WifiEnabler mWifiEnabler;
122     // An access point being editted is stored here.
123     private AccessPoint mSelectedAccessPoint;
124 
125     private NetworkInfo mLastNetworkInfo;
126     private WifiInfo mLastInfo;
127 
128     private final AtomicBoolean mConnected = new AtomicBoolean(false);
129 
130     private WifiDialog mDialog;
131     private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
132 
133     private TextView mEmptyView;
134 
135     // this boolean extra specifies whether to disable the Next button when not connected. Used by
136     // account creation outside of setup wizard.
137     private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
138     // This string extra specifies a network to open the connect dialog on, so the user can enter
139     // network credentials.  This is used by quick settings for secured networks.
140     private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
141 
142     // should Next button only be enabled when we have a connection?
143     private boolean mEnableNextOnConnection;
144 
145     // Save the dialog details
146     private boolean mDlgEdit;
147     private AccessPoint mDlgAccessPoint;
148     private Bundle mAccessPointSavedState;
149 
150     /** verbose logging flag. this flag is set thru developer debugging options
151      * and used so as to assist with in-the-field WiFi connectivity debugging  */
152     public static int mVerboseLogging = 0;
153 
154     /* End of "used in Wifi Setup context" */
155 
156     /** A restricted multimap for use in constructAccessPoints */
157     private static class Multimap<K,V> {
158         private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
159         /** retrieve a non-null list of values with key K */
getAll(K key)160         List<V> getAll(K key) {
161             List<V> values = store.get(key);
162             return values != null ? values : Collections.<V>emptyList();
163         }
164 
put(K key, V val)165         void put(K key, V val) {
166             List<V> curVals = store.get(key);
167             if (curVals == null) {
168                 curVals = new ArrayList<V>(3);
169                 store.put(key, curVals);
170             }
171             curVals.add(val);
172         }
173     }
174 
175     private static class Scanner extends Handler {
176         private int mRetry = 0;
177         private WifiSettings mWifiSettings = null;
178 
Scanner(WifiSettings wifiSettings)179         Scanner(WifiSettings wifiSettings) {
180             mWifiSettings = wifiSettings;
181         }
182 
resume()183         void resume() {
184             if (!hasMessages(0)) {
185                 sendEmptyMessage(0);
186             }
187         }
188 
forceScan()189         void forceScan() {
190             removeMessages(0);
191             sendEmptyMessage(0);
192         }
193 
pause()194         void pause() {
195             mRetry = 0;
196             removeMessages(0);
197         }
198 
199         @Override
handleMessage(Message message)200         public void handleMessage(Message message) {
201             if (mWifiSettings.mWifiManager.startScan()) {
202                 mRetry = 0;
203             } else if (++mRetry >= 3) {
204                 mRetry = 0;
205                 Activity activity = mWifiSettings.getActivity();
206                 if (activity != null) {
207                     Toast.makeText(activity, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
208                 }
209                 return;
210             }
211             sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
212         }
213     }
214 
WifiSettings()215     public WifiSettings() {
216         super(DISALLOW_CONFIG_WIFI);
217         mFilter = new IntentFilter();
218         mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
219         mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
220         mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
221         mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
222         mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
223         mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
224         mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
225         mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
226 
227         mReceiver = new BroadcastReceiver() {
228             @Override
229             public void onReceive(Context context, Intent intent) {
230                 handleEvent(intent);
231             }
232         };
233 
234         mScanner = new Scanner(this);
235     }
236 
237     @Override
onActivityCreated(Bundle savedInstanceState)238     public void onActivityCreated(Bundle savedInstanceState) {
239         super.onActivityCreated(savedInstanceState);
240 
241         mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
242 
243         mConnectListener = new WifiManager.ActionListener() {
244                                    @Override
245                                    public void onSuccess() {
246                                    }
247                                    @Override
248                                    public void onFailure(int reason) {
249                                        Activity activity = getActivity();
250                                        if (activity != null) {
251                                            Toast.makeText(activity,
252                                                 R.string.wifi_failed_connect_message,
253                                                 Toast.LENGTH_SHORT).show();
254                                        }
255                                    }
256                                };
257 
258         mSaveListener = new WifiManager.ActionListener() {
259                                 @Override
260                                 public void onSuccess() {
261                                 }
262                                 @Override
263                                 public void onFailure(int reason) {
264                                     Activity activity = getActivity();
265                                     if (activity != null) {
266                                         Toast.makeText(activity,
267                                             R.string.wifi_failed_save_message,
268                                             Toast.LENGTH_SHORT).show();
269                                     }
270                                 }
271                             };
272 
273         mForgetListener = new WifiManager.ActionListener() {
274                                    @Override
275                                    public void onSuccess() {
276                                    }
277                                    @Override
278                                    public void onFailure(int reason) {
279                                        Activity activity = getActivity();
280                                        if (activity != null) {
281                                            Toast.makeText(activity,
282                                                R.string.wifi_failed_forget_message,
283                                                Toast.LENGTH_SHORT).show();
284                                        }
285                                    }
286                                };
287 
288         if (savedInstanceState != null) {
289             mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
290             if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
291                 mAccessPointSavedState =
292                     savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
293             }
294         }
295 
296         // if we're supposed to enable/disable the Next button based on our current connection
297         // state, start it off in the right state
298         Intent intent = getActivity().getIntent();
299         mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
300 
301         if (mEnableNextOnConnection) {
302             if (hasNextButton()) {
303                 final ConnectivityManager connectivity = (ConnectivityManager)
304                         getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
305                 if (connectivity != null) {
306                     NetworkInfo info = connectivity.getNetworkInfo(
307                             ConnectivityManager.TYPE_WIFI);
308                     changeNextButtonState(info.isConnected());
309                 }
310             }
311         }
312 
313         addPreferencesFromResource(R.xml.wifi_settings);
314 
315         mEmptyView = initEmptyView();
316         registerForContextMenu(getListView());
317         setHasOptionsMenu(true);
318 
319         if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
320             String ssid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
321             updateAccessPoints();
322             PreferenceScreen preferenceScreen = getPreferenceScreen();
323             for (int i = 0; i < preferenceScreen.getPreferenceCount(); i++) {
324                 Preference preference = preferenceScreen.getPreference(i);
325                 if (preference instanceof AccessPoint) {
326                     AccessPoint accessPoint = (AccessPoint) preference;
327                     if (ssid.equals(accessPoint.ssid) && accessPoint.networkId == -1
328                             && accessPoint.security != AccessPoint.SECURITY_NONE) {
329                         onPreferenceTreeClick(preferenceScreen, preference);
330                         break;
331                     }
332                 }
333             }
334         }
335     }
336 
337     @Override
onDestroyView()338     public void onDestroyView() {
339         super.onDestroyView();
340 
341         if (mWifiEnabler != null) {
342             mWifiEnabler.teardownSwitchBar();
343         }
344     }
345 
346     @Override
onStart()347     public void onStart() {
348         super.onStart();
349 
350         // On/off switch is hidden for Setup Wizard (returns null)
351         mWifiEnabler = createWifiEnabler();
352     }
353 
354     /**
355      * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
356      */
createWifiEnabler()357     /* package */ WifiEnabler createWifiEnabler() {
358         final SettingsActivity activity = (SettingsActivity) getActivity();
359         return new WifiEnabler(activity, activity.getSwitchBar());
360     }
361 
362     @Override
onResume()363     public void onResume() {
364         final Activity activity = getActivity();
365         super.onResume();
366         if (mWifiEnabler != null) {
367             mWifiEnabler.resume(activity);
368         }
369 
370         activity.registerReceiver(mReceiver, mFilter);
371         updateAccessPoints();
372     }
373 
374     @Override
onPause()375     public void onPause() {
376         super.onPause();
377         if (mWifiEnabler != null) {
378             mWifiEnabler.pause();
379         }
380 
381         getActivity().unregisterReceiver(mReceiver);
382         mScanner.pause();
383     }
384 
385     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)386     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
387         // If the user is not allowed to configure wifi, do not show the menu.
388         if (isUiRestricted()) return;
389 
390         addOptionsMenuItems(menu);
391         super.onCreateOptionsMenu(menu, inflater);
392     }
393 
394     /**
395      * @param menu
396      */
addOptionsMenuItems(Menu menu)397     void addOptionsMenuItems(Menu menu) {
398         final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
399         TypedArray ta = getActivity().getTheme().obtainStyledAttributes(
400                 new int[] {R.attr.ic_menu_add, R.attr.ic_wps});
401         menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
402                 .setIcon(ta.getDrawable(0))
403                 .setEnabled(wifiIsEnabled)
404                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
405         if (savedNetworksExist) {
406             menu.add(Menu.NONE, MENU_ID_SAVED_NETWORK, 0, R.string.wifi_saved_access_points_label)
407                     .setIcon(ta.getDrawable(0))
408                     .setEnabled(wifiIsEnabled)
409                     .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
410         }
411         menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh)
412                .setEnabled(wifiIsEnabled)
413                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
414         menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
415                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
416         ta.recycle();
417     }
418 
419     @Override
onSaveInstanceState(Bundle outState)420     public void onSaveInstanceState(Bundle outState) {
421         super.onSaveInstanceState(outState);
422 
423         // If the dialog is showing, save its state.
424         if (mDialog != null && mDialog.isShowing()) {
425             outState.putBoolean(SAVE_DIALOG_EDIT_MODE, mDlgEdit);
426             if (mDlgAccessPoint != null) {
427                 mAccessPointSavedState = new Bundle();
428                 mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
429                 outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
430             }
431         }
432     }
433 
434     @Override
onOptionsItemSelected(MenuItem item)435     public boolean onOptionsItemSelected(MenuItem item) {
436         // If the user is not allowed to configure wifi, do not handle menu selections.
437         if (isUiRestricted()) return false;
438 
439         switch (item.getItemId()) {
440             case MENU_ID_WPS_PBC:
441                 showDialog(WPS_PBC_DIALOG_ID);
442                 return true;
443                 /*
444             case MENU_ID_P2P:
445                 if (getActivity() instanceof SettingsActivity) {
446                     ((SettingsActivity) getActivity()).startPreferencePanel(
447                             WifiP2pSettings.class.getCanonicalName(),
448                             null,
449                             R.string.wifi_p2p_settings_title, null,
450                             this, 0);
451                 } else {
452                     startFragment(this, WifiP2pSettings.class.getCanonicalName(),
453                             R.string.wifi_p2p_settings_title, -1, null);
454                 }
455                 return true;
456                 */
457             case MENU_ID_WPS_PIN:
458                 showDialog(WPS_PIN_DIALOG_ID);
459                 return true;
460             case MENU_ID_SCAN:
461                 if (mWifiManager.isWifiEnabled()) {
462                     mScanner.forceScan();
463                 }
464                 return true;
465             case MENU_ID_ADD_NETWORK:
466                 if (mWifiManager.isWifiEnabled()) {
467                     onAddNetworkPressed();
468                 }
469                 return true;
470             case MENU_ID_SAVED_NETWORK:
471                 if (getActivity() instanceof SettingsActivity) {
472                     ((SettingsActivity) getActivity()).startPreferencePanel(
473                             SavedAccessPointsWifiSettings.class.getCanonicalName(), null,
474                             R.string.wifi_saved_access_points_titlebar, null, this, 0);
475                 } else {
476                     startFragment(this, SavedAccessPointsWifiSettings.class.getCanonicalName(),
477                             R.string.wifi_saved_access_points_titlebar,
478                             -1 /* Do not request a result */, null);
479                 }
480                 return true;
481             case MENU_ID_ADVANCED:
482                 if (getActivity() instanceof SettingsActivity) {
483                     ((SettingsActivity) getActivity()).startPreferencePanel(
484                             AdvancedWifiSettings.class.getCanonicalName(), null,
485                             R.string.wifi_advanced_titlebar, null, this, 0);
486                 } else {
487                     startFragment(this, AdvancedWifiSettings.class.getCanonicalName(),
488                             R.string.wifi_advanced_titlebar, -1 /* Do not request a results */,
489                             null);
490                 }
491                 return true;
492         }
493         return super.onOptionsItemSelected(item);
494     }
495 
496     @Override
onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info)497     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
498         if (info instanceof AdapterContextMenuInfo) {
499             Preference preference = (Preference) getListView().getItemAtPosition(
500                     ((AdapterContextMenuInfo) info).position);
501 
502             if (preference instanceof AccessPoint) {
503                 mSelectedAccessPoint = (AccessPoint) preference;
504                 menu.setHeaderTitle(mSelectedAccessPoint.ssid);
505                 if (mSelectedAccessPoint.getLevel() != -1) {
506                     if (mSelectedAccessPoint.getState() == null) {
507                         menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
508                     }
509                 }
510 
511                 if (ActivityManager.getCurrentUser() == UserHandle.USER_OWNER &&
512                         (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID ||
513                         (mSelectedAccessPoint.getNetworkInfo() != null &&
514                         mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED))) {
515                     // Allow forgetting a network if the current user is the owner and either the
516                     // network is saved or ephemerally connected. (In the latter case, "forget"
517                     // blacklists the network so it won't be used again, ephemerally).
518                     menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
519                 }
520                 if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
521                     menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
522                     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
523                     if (nfcAdapter != null && nfcAdapter.isEnabled() &&
524                             mSelectedAccessPoint.security != AccessPoint.SECURITY_NONE) {
525                         // Only allow writing of NFC tags for password-protected networks.
526                         menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
527                     }
528                 }
529             }
530         }
531     }
532 
533     @Override
onContextItemSelected(MenuItem item)534     public boolean onContextItemSelected(MenuItem item) {
535         if (mSelectedAccessPoint == null) {
536             return super.onContextItemSelected(item);
537         }
538         switch (item.getItemId()) {
539             case MENU_ID_CONNECT: {
540                 if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
541                     connect(mSelectedAccessPoint.networkId);
542                 } else if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE) {
543                     /** Bypass dialog for unsecured networks */
544                     mSelectedAccessPoint.generateOpenNetworkConfig();
545                     connect(mSelectedAccessPoint.getConfig());
546                 } else {
547                     showDialog(mSelectedAccessPoint, true);
548                 }
549                 return true;
550             }
551             case MENU_ID_FORGET: {
552                 forget();
553                 return true;
554             }
555             case MENU_ID_MODIFY: {
556                 showDialog(mSelectedAccessPoint, true);
557                 return true;
558             }
559             case MENU_ID_WRITE_NFC:
560                 showDialog(WRITE_NFC_DIALOG_ID);
561                 return true;
562 
563         }
564         return super.onContextItemSelected(item);
565     }
566 
567     @Override
onPreferenceTreeClick(PreferenceScreen screen, Preference preference)568     public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
569         if (preference instanceof AccessPoint) {
570             mSelectedAccessPoint = (AccessPoint) preference;
571             /** Bypass dialog for unsecured, unsaved, and inactive networks */
572             if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE &&
573                     mSelectedAccessPoint.networkId == INVALID_NETWORK_ID &&
574                     !mSelectedAccessPoint.isActive()) {
575                 mSelectedAccessPoint.generateOpenNetworkConfig();
576                 if (!savedNetworksExist) {
577                     savedNetworksExist = true;
578                     getActivity().invalidateOptionsMenu();
579                 }
580                 connect(mSelectedAccessPoint.getConfig());
581             } else {
582                 showDialog(mSelectedAccessPoint, false);
583             }
584         } else {
585             return super.onPreferenceTreeClick(screen, preference);
586         }
587         return true;
588     }
589 
showDialog(AccessPoint accessPoint, boolean edit)590     private void showDialog(AccessPoint accessPoint, boolean edit) {
591         if (mDialog != null) {
592             removeDialog(WIFI_DIALOG_ID);
593             mDialog = null;
594         }
595 
596         // Save the access point and edit mode
597         mDlgAccessPoint = accessPoint;
598         mDlgEdit = edit;
599 
600         showDialog(WIFI_DIALOG_ID);
601     }
602 
603     @Override
onCreateDialog(int dialogId)604     public Dialog onCreateDialog(int dialogId) {
605         switch (dialogId) {
606             case WIFI_DIALOG_ID:
607                 AccessPoint ap = mDlgAccessPoint; // For manual launch
608                 if (ap == null) { // For re-launch from saved state
609                     if (mAccessPointSavedState != null) {
610                         ap = new AccessPoint(getActivity(), mAccessPointSavedState);
611                         // For repeated orientation changes
612                         mDlgAccessPoint = ap;
613                         // Reset the saved access point data
614                         mAccessPointSavedState = null;
615                     }
616                 }
617                 // If it's null, fine, it's for Add Network
618                 mSelectedAccessPoint = ap;
619                 mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit);
620                 return mDialog;
621             case WPS_PBC_DIALOG_ID:
622                 return new WpsDialog(getActivity(), WpsInfo.PBC);
623             case WPS_PIN_DIALOG_ID:
624                 return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
625             case WRITE_NFC_DIALOG_ID:
626                 if (mSelectedAccessPoint != null) {
627                     mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
628                             getActivity(), mSelectedAccessPoint, mWifiManager);
629                     return mWifiToNfcDialog;
630                 }
631 
632         }
633         return super.onCreateDialog(dialogId);
634     }
635 
636     /**
637      * Shows the latest access points available with supplemental information like
638      * the strength of network and the security for it.
639      */
updateAccessPoints()640     private void updateAccessPoints() {
641         // Safeguard from some delayed event handling
642         if (getActivity() == null) return;
643 
644         if (isUiRestricted()) {
645             addMessagePreference(R.string.wifi_empty_list_user_restricted);
646             return;
647         }
648         final int wifiState = mWifiManager.getWifiState();
649 
650         //when we update the screen, check if verbose logging has been turned on or off
651         mVerboseLogging = mWifiManager.getVerboseLoggingLevel();
652 
653         switch (wifiState) {
654             case WifiManager.WIFI_STATE_ENABLED:
655                 // AccessPoints are automatically sorted with TreeSet.
656                 final Collection<AccessPoint> accessPoints =
657                         constructAccessPoints(getActivity(), mWifiManager, mLastInfo,
658                                 mLastNetworkInfo);
659                 getPreferenceScreen().removeAll();
660                 if (accessPoints.size() == 0) {
661                     addMessagePreference(R.string.wifi_empty_list_wifi_on);
662                 }
663 
664                 for (AccessPoint accessPoint : accessPoints) {
665                     // Ignore access points that are out of range.
666                     if (accessPoint.getLevel() != -1) {
667                         getPreferenceScreen().addPreference(accessPoint);
668                     }
669                 }
670                 break;
671 
672             case WifiManager.WIFI_STATE_ENABLING:
673                 getPreferenceScreen().removeAll();
674                 break;
675 
676             case WifiManager.WIFI_STATE_DISABLING:
677                 addMessagePreference(R.string.wifi_stopping);
678                 break;
679 
680             case WifiManager.WIFI_STATE_DISABLED:
681                 setOffMessage();
682                 break;
683         }
684     }
685 
initEmptyView()686     protected TextView initEmptyView() {
687         TextView emptyView = (TextView) getActivity().findViewById(android.R.id.empty);
688         getListView().setEmptyView(emptyView);
689         return emptyView;
690     }
691 
setOffMessage()692     private void setOffMessage() {
693         if (mEmptyView != null) {
694             mEmptyView.setText(R.string.wifi_empty_list_wifi_off);
695             if (android.provider.Settings.Global.getInt(getActivity().getContentResolver(),
696                     android.provider.Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1) {
697                 mEmptyView.append("\n\n");
698                 int resId;
699                 if (android.provider.Settings.Secure.isLocationProviderEnabled(
700                         getActivity().getContentResolver(), LocationManager.NETWORK_PROVIDER)) {
701                     resId = R.string.wifi_scan_notify_text_location_on;
702                 } else {
703                     resId = R.string.wifi_scan_notify_text_location_off;
704                 }
705                 CharSequence charSeq = getText(resId);
706                 mEmptyView.append(charSeq);
707             }
708         }
709         getPreferenceScreen().removeAll();
710     }
711 
addMessagePreference(int messageId)712     private void addMessagePreference(int messageId) {
713         if (mEmptyView != null) mEmptyView.setText(messageId);
714         getPreferenceScreen().removeAll();
715     }
716 
717     /** Returns sorted list of access points */
constructAccessPoints(Context context, WifiManager wifiManager, WifiInfo lastInfo, NetworkInfo lastNetworkInfo)718     private static List<AccessPoint> constructAccessPoints(Context context,
719             WifiManager wifiManager, WifiInfo lastInfo, NetworkInfo lastNetworkInfo) {
720         ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
721         /** Lookup table to more quickly update AccessPoints by only considering objects with the
722          * correct SSID.  Maps SSID -> List of AccessPoints with the given SSID.  */
723         Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
724 
725         final List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks();
726         if (configs != null) {
727             // Update "Saved Networks" menu option.
728             if (savedNetworksExist != (configs.size() > 0)) {
729                 savedNetworksExist = !savedNetworksExist;
730                 if (context instanceof Activity) {
731                     ((Activity) context).invalidateOptionsMenu();
732                 }
733             }
734             for (WifiConfiguration config : configs) {
735                 if (config.selfAdded && config.numAssociation == 0) {
736                     continue;
737                 }
738                 AccessPoint accessPoint = new AccessPoint(context, config);
739                 if (lastInfo != null && lastNetworkInfo != null) {
740                     accessPoint.update(lastInfo, lastNetworkInfo);
741                 }
742                 accessPoints.add(accessPoint);
743                 apMap.put(accessPoint.ssid, accessPoint);
744             }
745         }
746 
747         final List<ScanResult> results = wifiManager.getScanResults();
748         if (results != null) {
749             for (ScanResult result : results) {
750                 // Ignore hidden and ad-hoc networks.
751                 if (result.SSID == null || result.SSID.length() == 0 ||
752                         result.capabilities.contains("[IBSS]")) {
753                     continue;
754                 }
755 
756                 boolean found = false;
757                 for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
758                     if (accessPoint.update(result))
759                         found = true;
760                 }
761                 if (!found) {
762                     AccessPoint accessPoint = new AccessPoint(context, result);
763                     if (lastInfo != null && lastNetworkInfo != null) {
764                         accessPoint.update(lastInfo, lastNetworkInfo);
765                     }
766                     accessPoints.add(accessPoint);
767                     apMap.put(accessPoint.ssid, accessPoint);
768                 }
769             }
770         }
771 
772         // Pre-sort accessPoints to speed preference insertion
773         Collections.sort(accessPoints);
774         return accessPoints;
775     }
776 
handleEvent(Intent intent)777     private void handleEvent(Intent intent) {
778         String action = intent.getAction();
779         if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
780             updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
781                     WifiManager.WIFI_STATE_UNKNOWN));
782         } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
783                 WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
784                 WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
785                 updateAccessPoints();
786         } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
787             NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
788                     WifiManager.EXTRA_NETWORK_INFO);
789             mConnected.set(info.isConnected());
790             changeNextButtonState(info.isConnected());
791             updateAccessPoints();
792             updateNetworkInfo(info);
793         } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
794             updateNetworkInfo(null);
795         }
796     }
797 
updateNetworkInfo(NetworkInfo networkInfo)798     private void updateNetworkInfo(NetworkInfo networkInfo) {
799         /* sticky broadcasts can call this when wifi is disabled */
800         if (!mWifiManager.isWifiEnabled()) {
801             mScanner.pause();
802             return;
803         }
804 
805         if (networkInfo != null &&
806                 networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) {
807             mScanner.pause();
808         } else {
809             mScanner.resume();
810         }
811 
812         mLastInfo = mWifiManager.getConnectionInfo();
813         if (networkInfo != null) {
814             mLastNetworkInfo = networkInfo;
815         }
816 
817         for (int i = getPreferenceScreen().getPreferenceCount() - 1; i >= 0; --i) {
818             // Maybe there's a WifiConfigPreference
819             Preference preference = getPreferenceScreen().getPreference(i);
820             if (preference instanceof AccessPoint) {
821                 final AccessPoint accessPoint = (AccessPoint) preference;
822                 accessPoint.update(mLastInfo, mLastNetworkInfo);
823             }
824         }
825     }
826 
updateWifiState(int state)827     private void updateWifiState(int state) {
828         Activity activity = getActivity();
829         if (activity != null) {
830             activity.invalidateOptionsMenu();
831         }
832 
833         switch (state) {
834             case WifiManager.WIFI_STATE_ENABLED:
835                 mScanner.resume();
836                 return; // not break, to avoid the call to pause() below
837 
838             case WifiManager.WIFI_STATE_ENABLING:
839                 addMessagePreference(R.string.wifi_starting);
840                 break;
841 
842             case WifiManager.WIFI_STATE_DISABLED:
843                 setOffMessage();
844                 break;
845         }
846 
847         mLastInfo = null;
848         mLastNetworkInfo = null;
849         mScanner.pause();
850     }
851 
852     /**
853      * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
854      * Wifi setup screens, not in usual wifi settings screen.
855      *
856      * @param enabled true when the device is connected to a wifi network.
857      */
changeNextButtonState(boolean enabled)858     private void changeNextButtonState(boolean enabled) {
859         if (mEnableNextOnConnection && hasNextButton()) {
860             getNextButton().setEnabled(enabled);
861         }
862     }
863 
864     @Override
onClick(DialogInterface dialogInterface, int button)865     public void onClick(DialogInterface dialogInterface, int button) {
866         if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
867             forget();
868         } else if (button == WifiDialog.BUTTON_SUBMIT) {
869             if (mDialog != null) {
870                 submit(mDialog.getController());
871             }
872         }
873     }
874 
submit(WifiConfigController configController)875     /* package */ void submit(WifiConfigController configController) {
876 
877         final WifiConfiguration config = configController.getConfig();
878 
879         if (config == null) {
880             if (mSelectedAccessPoint != null
881                     && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
882                 connect(mSelectedAccessPoint.networkId);
883             }
884         } else if (config.networkId != INVALID_NETWORK_ID) {
885             if (mSelectedAccessPoint != null) {
886                 mWifiManager.save(config, mSaveListener);
887             }
888         } else {
889             if (configController.isEdit()) {
890                 mWifiManager.save(config, mSaveListener);
891             } else {
892                 connect(config);
893             }
894         }
895 
896         if (mWifiManager.isWifiEnabled()) {
897             mScanner.resume();
898         }
899         updateAccessPoints();
900     }
901 
forget()902     /* package */ void forget() {
903         if (mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
904             if (mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
905                 // Network is active but has no network ID - must be ephemeral.
906                 mWifiManager.disableEphemeralNetwork(
907                         AccessPoint.convertToQuotedString(mSelectedAccessPoint.ssid));
908             } else {
909                 // Should not happen, but a monkey seems to trigger it
910                 Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
911                 return;
912             }
913         } else {
914             mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener);
915         }
916 
917 
918         if (mWifiManager.isWifiEnabled()) {
919             mScanner.resume();
920         }
921         updateAccessPoints();
922 
923         // We need to rename/replace "Next" button in wifi setup context.
924         changeNextButtonState(false);
925     }
926 
connect(final WifiConfiguration config)927     protected void connect(final WifiConfiguration config) {
928         mWifiManager.connect(config, mConnectListener);
929     }
930 
connect(final int networkId)931     protected void connect(final int networkId) {
932         mWifiManager.connect(networkId, mConnectListener);
933     }
934 
935     /**
936      * Refreshes acccess points and ask Wifi module to scan networks again.
937      */
refreshAccessPoints()938     /* package */ void refreshAccessPoints() {
939         if (mWifiManager.isWifiEnabled()) {
940             mScanner.resume();
941         }
942 
943         getPreferenceScreen().removeAll();
944     }
945 
946     /**
947      * Called when "add network" button is pressed.
948      */
onAddNetworkPressed()949     /* package */ void onAddNetworkPressed() {
950         // No exact access point is selected.
951         mSelectedAccessPoint = null;
952         showDialog(null, true);
953     }
954 
getAccessPointsCount()955     /* package */ int getAccessPointsCount() {
956         final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
957         if (wifiIsEnabled) {
958             return getPreferenceScreen().getPreferenceCount();
959         } else {
960             return 0;
961         }
962     }
963 
964     /**
965      * Requests wifi module to pause wifi scan. May be ignored when the module is disabled.
966      */
pauseWifiScan()967     /* package */ void pauseWifiScan() {
968         if (mWifiManager.isWifiEnabled()) {
969             mScanner.pause();
970         }
971     }
972 
973     /**
974      * Requests wifi module to resume wifi scan. May be ignored when the module is disabled.
975      */
resumeWifiScan()976     /* package */ void resumeWifiScan() {
977         if (mWifiManager.isWifiEnabled()) {
978             mScanner.resume();
979         }
980     }
981 
982     @Override
getHelpResource()983     protected int getHelpResource() {
984         return R.string.help_url_wifi;
985     }
986 
987     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
988         new BaseSearchIndexProvider() {
989             @Override
990             public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
991                 final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
992                 final Resources res = context.getResources();
993 
994                 // Add fragment title
995                 SearchIndexableRaw data = new SearchIndexableRaw(context);
996                 data.title = res.getString(R.string.wifi_settings);
997                 data.screenTitle = res.getString(R.string.wifi_settings);
998                 data.keywords = res.getString(R.string.keywords_wifi);
999                 result.add(data);
1000 
1001                 // Add available Wi-Fi access points
1002                 WifiManager wifiManager =
1003                         (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
1004                 final Collection<AccessPoint> accessPoints =
1005                         constructAccessPoints(context, wifiManager, null, null);
1006                 for (AccessPoint accessPoint : accessPoints) {
1007                     // We are indexing only the saved Wi-Fi networks.
1008                     if (accessPoint.getConfig() == null) continue;
1009                     data = new SearchIndexableRaw(context);
1010                     data.title = accessPoint.getTitle().toString();
1011                     data.screenTitle = res.getString(R.string.wifi_settings);
1012                     data.enabled = enabled;
1013                     result.add(data);
1014                 }
1015 
1016                 return result;
1017             }
1018         };
1019 }
1020