• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.settings.R;
23 import com.android.settings.RestrictedSettingsFragment;
24 import com.android.settings.wifi.p2p.WifiP2pSettings;
25 
26 import android.app.ActionBar;
27 import android.app.Activity;
28 import android.app.AlertDialog;
29 import android.app.Dialog;
30 import android.content.BroadcastReceiver;
31 import android.content.Context;
32 import android.content.DialogInterface;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.content.pm.PackageManager;
36 import android.content.res.Resources;
37 import android.content.res.TypedArray;
38 import android.location.LocationManager;
39 import android.net.ConnectivityManager;
40 import android.net.NetworkInfo;
41 import android.net.NetworkInfo.DetailedState;
42 import android.net.wifi.ScanResult;
43 import android.net.wifi.SupplicantState;
44 import android.net.wifi.WifiConfiguration;
45 import android.net.wifi.WifiInfo;
46 import android.net.wifi.WifiManager;
47 import android.net.wifi.WpsInfo;
48 import android.os.Bundle;
49 import android.os.Handler;
50 import android.os.Message;
51 import android.preference.Preference;
52 import android.preference.PreferenceActivity;
53 import android.preference.PreferenceScreen;
54 import android.provider.Settings;
55 import android.util.AttributeSet;
56 import android.util.Log;
57 import android.view.ContextMenu;
58 import android.view.ContextMenu.ContextMenuInfo;
59 import android.view.Gravity;
60 import android.view.LayoutInflater;
61 import android.view.Menu;
62 import android.view.MenuInflater;
63 import android.view.MenuItem;
64 import android.view.View;
65 import android.view.View.OnClickListener;
66 import android.view.ViewGroup;
67 import android.widget.AdapterView.AdapterContextMenuInfo;
68 import android.widget.Button;
69 import android.widget.ImageButton;
70 import android.widget.PopupMenu;
71 import android.widget.PopupMenu.OnMenuItemClickListener;
72 import android.widget.RelativeLayout;
73 import android.widget.Switch;
74 import android.widget.TextView;
75 import android.widget.Toast;
76 
77 import java.util.ArrayList;
78 import java.util.Collection;
79 import java.util.Collections;
80 import java.util.HashMap;
81 import java.util.List;
82 import java.util.concurrent.atomic.AtomicBoolean;
83 
84 /**
85  * Two types of UI are provided here.
86  *
87  * The first is for "usual Settings", appearing as any other Setup fragment.
88  *
89  * The second is for Setup Wizard, with a simplified interface that hides the action bar
90  * and menus.
91  */
92 public class WifiSettings extends RestrictedSettingsFragment
93         implements DialogInterface.OnClickListener  {
94     private static final String TAG = "WifiSettings";
95     private static final int MENU_ID_WPS_PBC = Menu.FIRST;
96     private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
97     private static final int MENU_ID_P2P = Menu.FIRST + 2;
98     private static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 3;
99     private static final int MENU_ID_ADVANCED = Menu.FIRST + 4;
100     private static final int MENU_ID_SCAN = Menu.FIRST + 5;
101     private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
102     private static final int MENU_ID_FORGET = Menu.FIRST + 7;
103     private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
104 
105     private static final int WIFI_DIALOG_ID = 1;
106     private static final int WPS_PBC_DIALOG_ID = 2;
107     private static final int WPS_PIN_DIALOG_ID = 3;
108     private static final int WIFI_SKIPPED_DIALOG_ID = 4;
109     private static final int WIFI_AND_MOBILE_SKIPPED_DIALOG_ID = 5;
110 
111     // Combo scans can take 5-6s to complete - set to 10s.
112     private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
113 
114     // Instance state keys
115     private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode";
116     private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
117 
118     // Activity result when pressing the Skip button
119     private static final int RESULT_SKIP = Activity.RESULT_FIRST_USER;
120 
121     private final IntentFilter mFilter;
122     private final BroadcastReceiver mReceiver;
123     private final Scanner mScanner;
124 
125     private WifiManager mWifiManager;
126     private WifiManager.ActionListener mConnectListener;
127     private WifiManager.ActionListener mSaveListener;
128     private WifiManager.ActionListener mForgetListener;
129     private boolean mP2pSupported;
130 
131     private WifiEnabler mWifiEnabler;
132     // An access point being editted is stored here.
133     private AccessPoint mSelectedAccessPoint;
134 
135     private DetailedState mLastState;
136     private WifiInfo mLastInfo;
137 
138     private final AtomicBoolean mConnected = new AtomicBoolean(false);
139 
140     private WifiDialog mDialog;
141 
142     private TextView mEmptyView;
143 
144     /* Used in Wifi Setup context */
145 
146     // this boolean extra specifies whether to disable the Next button when not connected
147     private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
148 
149     // this boolean extra specifies whether to auto finish when connection is established
150     private static final String EXTRA_AUTO_FINISH_ON_CONNECT = "wifi_auto_finish_on_connect";
151 
152     // this boolean extra shows a custom button that we can control
153     protected static final String EXTRA_SHOW_CUSTOM_BUTTON = "wifi_show_custom_button";
154 
155     // show a text regarding data charges when wifi connection is required during setup wizard
156     protected static final String EXTRA_SHOW_WIFI_REQUIRED_INFO = "wifi_show_wifi_required_info";
157 
158     // this boolean extra is set if we are being invoked by the Setup Wizard
159     private static final String EXTRA_IS_FIRST_RUN = "firstRun";
160 
161     // should Next button only be enabled when we have a connection?
162     private boolean mEnableNextOnConnection;
163 
164     // should activity finish once we have a connection?
165     private boolean mAutoFinishOnConnection;
166 
167     // Save the dialog details
168     private boolean mDlgEdit;
169     private AccessPoint mDlgAccessPoint;
170     private Bundle mAccessPointSavedState;
171 
172     // the action bar uses a different set of controls for Setup Wizard
173     private boolean mSetupWizardMode;
174 
175     /* End of "used in Wifi Setup context" */
176 
WifiSettings()177     public WifiSettings() {
178         super(DISALLOW_CONFIG_WIFI);
179         mFilter = new IntentFilter();
180         mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
181         mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
182         mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
183         mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
184         mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
185         mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
186         mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
187         mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
188 
189         mReceiver = new BroadcastReceiver() {
190             @Override
191             public void onReceive(Context context, Intent intent) {
192                 handleEvent(context, intent);
193             }
194         };
195 
196         mScanner = new Scanner();
197     }
198 
199     @Override
onCreate(Bundle icicle)200     public void onCreate(Bundle icicle) {
201         // Set this flag early, as it's needed by getHelpResource(), which is called by super
202         mSetupWizardMode = getActivity().getIntent().getBooleanExtra(EXTRA_IS_FIRST_RUN, false);
203 
204         super.onCreate(icicle);
205     }
206 
207     @Override
onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)208     public View onCreateView(final LayoutInflater inflater, ViewGroup container,
209             Bundle savedInstanceState) {
210         if (mSetupWizardMode) {
211             View view = inflater.inflate(R.layout.setup_preference, container, false);
212             View other = view.findViewById(R.id.other_network);
213             other.setOnClickListener(new OnClickListener() {
214                 @Override
215                 public void onClick(View v) {
216                     if (mWifiManager.isWifiEnabled()) {
217                         onAddNetworkPressed();
218                     }
219                 }
220             });
221             final ImageButton b = (ImageButton) view.findViewById(R.id.more);
222             if (b != null) {
223                 b.setOnClickListener(new OnClickListener() {
224                     @Override
225                     public void onClick(View v) {
226                         if (mWifiManager.isWifiEnabled()) {
227                             PopupMenu pm = new PopupMenu(inflater.getContext(), b);
228                             pm.inflate(R.menu.wifi_setup);
229                             pm.setOnMenuItemClickListener(new OnMenuItemClickListener() {
230                                 @Override
231                                 public boolean onMenuItemClick(MenuItem item) {
232                                     if (R.id.wifi_wps == item.getItemId()) {
233                                         showDialog(WPS_PBC_DIALOG_ID);
234                                         return true;
235                                     }
236                                     return false;
237                                 }
238                             });
239                             pm.show();
240                         }
241                     }
242                 });
243             }
244 
245             Intent intent = getActivity().getIntent();
246             if (intent.getBooleanExtra(EXTRA_SHOW_CUSTOM_BUTTON, false)) {
247                 view.findViewById(R.id.button_bar).setVisibility(View.VISIBLE);
248                 view.findViewById(R.id.back_button).setVisibility(View.INVISIBLE);
249                 view.findViewById(R.id.skip_button).setVisibility(View.INVISIBLE);
250                 view.findViewById(R.id.next_button).setVisibility(View.INVISIBLE);
251 
252                 Button customButton = (Button) view.findViewById(R.id.custom_button);
253                 customButton.setVisibility(View.VISIBLE);
254                 customButton.setOnClickListener(new OnClickListener() {
255                     @Override
256                     public void onClick(View v) {
257                         boolean isConnected = false;
258                         Activity activity = getActivity();
259                         final ConnectivityManager connectivity = (ConnectivityManager)
260                                 activity.getSystemService(Context.CONNECTIVITY_SERVICE);
261                         if (connectivity != null) {
262                             final NetworkInfo info = connectivity.getActiveNetworkInfo();
263                             isConnected = (info != null) && info.isConnected();
264                         }
265                         if (isConnected) {
266                             // Warn of possible data charges
267                             showDialog(WIFI_SKIPPED_DIALOG_ID);
268                         } else {
269                             // Warn of lack of updates
270                             showDialog(WIFI_AND_MOBILE_SKIPPED_DIALOG_ID);
271                         }
272                     }
273                 });
274             }
275 
276             if (intent.getBooleanExtra(EXTRA_SHOW_WIFI_REQUIRED_INFO, false)) {
277                 view.findViewById(R.id.wifi_required_info).setVisibility(View.VISIBLE);
278             }
279 
280             return view;
281         } else {
282             return super.onCreateView(inflater, container, savedInstanceState);
283         }
284     }
285 
286     @Override
onActivityCreated(Bundle savedInstanceState)287     public void onActivityCreated(Bundle savedInstanceState) {
288         super.onActivityCreated(savedInstanceState);
289 
290         mP2pSupported = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT);
291         mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
292 
293         mConnectListener = new WifiManager.ActionListener() {
294                                    @Override
295                                    public void onSuccess() {
296                                    }
297                                    @Override
298                                    public void onFailure(int reason) {
299                                        Activity activity = getActivity();
300                                        if (activity != null) {
301                                            Toast.makeText(activity,
302                                                 R.string.wifi_failed_connect_message,
303                                                 Toast.LENGTH_SHORT).show();
304                                        }
305                                    }
306                                };
307 
308         mSaveListener = new WifiManager.ActionListener() {
309                                 @Override
310                                 public void onSuccess() {
311                                 }
312                                 @Override
313                                 public void onFailure(int reason) {
314                                     Activity activity = getActivity();
315                                     if (activity != null) {
316                                         Toast.makeText(activity,
317                                             R.string.wifi_failed_save_message,
318                                             Toast.LENGTH_SHORT).show();
319                                     }
320                                 }
321                             };
322 
323         mForgetListener = new WifiManager.ActionListener() {
324                                    @Override
325                                    public void onSuccess() {
326                                    }
327                                    @Override
328                                    public void onFailure(int reason) {
329                                        Activity activity = getActivity();
330                                        if (activity != null) {
331                                            Toast.makeText(activity,
332                                                R.string.wifi_failed_forget_message,
333                                                Toast.LENGTH_SHORT).show();
334                                        }
335                                    }
336                                };
337 
338         if (savedInstanceState != null
339                 && savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
340             mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
341             mAccessPointSavedState = savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
342         }
343 
344         final Activity activity = getActivity();
345         final Intent intent = activity.getIntent();
346 
347         // first if we're supposed to finish once we have a connection
348         mAutoFinishOnConnection = intent.getBooleanExtra(EXTRA_AUTO_FINISH_ON_CONNECT, false);
349 
350         if (mAutoFinishOnConnection) {
351             // Hide the next button
352             if (hasNextButton()) {
353                 getNextButton().setVisibility(View.GONE);
354             }
355 
356             final ConnectivityManager connectivity = (ConnectivityManager)
357                     activity.getSystemService(Context.CONNECTIVITY_SERVICE);
358             if (connectivity != null
359                     && connectivity.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected()) {
360                 activity.setResult(Activity.RESULT_OK);
361                 activity.finish();
362                 return;
363             }
364         }
365 
366         // if we're supposed to enable/disable the Next button based on our current connection
367         // state, start it off in the right state
368         mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
369 
370         if (mEnableNextOnConnection) {
371             if (hasNextButton()) {
372                 final ConnectivityManager connectivity = (ConnectivityManager)
373                         activity.getSystemService(Context.CONNECTIVITY_SERVICE);
374                 if (connectivity != null) {
375                     NetworkInfo info = connectivity.getNetworkInfo(
376                             ConnectivityManager.TYPE_WIFI);
377                     changeNextButtonState(info.isConnected());
378                 }
379             }
380         }
381 
382         addPreferencesFromResource(R.xml.wifi_settings);
383 
384         if (mSetupWizardMode) {
385             getView().setSystemUiVisibility(
386 //                    View.STATUS_BAR_DISABLE_BACK |
387                     View.STATUS_BAR_DISABLE_HOME |
388                     View.STATUS_BAR_DISABLE_RECENT |
389                     View.STATUS_BAR_DISABLE_NOTIFICATION_ALERTS |
390                     View.STATUS_BAR_DISABLE_CLOCK);
391         }
392 
393         // On/off switch is hidden for Setup Wizard
394         if (!mSetupWizardMode) {
395             Switch actionBarSwitch = new Switch(activity);
396 
397             if (activity instanceof PreferenceActivity) {
398                 PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
399                 if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) {
400                     final int padding = activity.getResources().getDimensionPixelSize(
401                             R.dimen.action_bar_switch_padding);
402                     actionBarSwitch.setPaddingRelative(0, 0, padding, 0);
403                     activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
404                             ActionBar.DISPLAY_SHOW_CUSTOM);
405                     activity.getActionBar().setCustomView(actionBarSwitch, new ActionBar.LayoutParams(
406                             ActionBar.LayoutParams.WRAP_CONTENT,
407                             ActionBar.LayoutParams.WRAP_CONTENT,
408                             Gravity.CENTER_VERTICAL | Gravity.END));
409                 }
410             }
411 
412             mWifiEnabler = new WifiEnabler(activity, actionBarSwitch);
413         }
414 
415         mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
416         getListView().setEmptyView(mEmptyView);
417 
418         if (!mSetupWizardMode) {
419             registerForContextMenu(getListView());
420         }
421         setHasOptionsMenu(true);
422     }
423 
424     @Override
onResume()425     public void onResume() {
426         super.onResume();
427         if (mWifiEnabler != null) {
428             mWifiEnabler.resume();
429         }
430 
431         getActivity().registerReceiver(mReceiver, mFilter);
432         updateAccessPoints();
433     }
434 
435     @Override
onPause()436     public void onPause() {
437         super.onPause();
438         if (mWifiEnabler != null) {
439             mWifiEnabler.pause();
440         }
441         getActivity().unregisterReceiver(mReceiver);
442         mScanner.pause();
443     }
444 
445     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)446     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
447         // If the user is not allowed to configure wifi, do not show the menu.
448         if (isRestrictedAndNotPinProtected()) return;
449 
450         final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
451         TypedArray ta = getActivity().getTheme().obtainStyledAttributes(
452                 new int[] {R.attr.ic_menu_add, R.attr.ic_wps});
453         if (mSetupWizardMode) {
454             menu.add(Menu.NONE, MENU_ID_WPS_PBC, 0, R.string.wifi_menu_wps_pbc)
455                     .setIcon(ta.getDrawable(1))
456                     .setEnabled(wifiIsEnabled)
457                     .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
458             menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
459                     .setEnabled(wifiIsEnabled)
460                     .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
461         } else {
462             menu.add(Menu.NONE, MENU_ID_WPS_PBC, 0, R.string.wifi_menu_wps_pbc)
463                     .setIcon(ta.getDrawable(1))
464                     .setEnabled(wifiIsEnabled)
465                     .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
466             menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
467                     .setIcon(ta.getDrawable(0))
468                     .setEnabled(wifiIsEnabled)
469                     .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
470             menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.wifi_menu_scan)
471                     //.setIcon(R.drawable.ic_menu_scan_network)
472                     .setEnabled(wifiIsEnabled)
473                     .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
474             menu.add(Menu.NONE, MENU_ID_WPS_PIN, 0, R.string.wifi_menu_wps_pin)
475                     .setEnabled(wifiIsEnabled)
476                     .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
477             if (mP2pSupported) {
478                 menu.add(Menu.NONE, MENU_ID_P2P, 0, R.string.wifi_menu_p2p)
479                         .setEnabled(wifiIsEnabled)
480                         .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
481             }
482             menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
483                     //.setIcon(android.R.drawable.ic_menu_manage)
484                     .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
485         }
486         ta.recycle();
487         super.onCreateOptionsMenu(menu, inflater);
488     }
489 
490     @Override
onSaveInstanceState(Bundle outState)491     public void onSaveInstanceState(Bundle outState) {
492         super.onSaveInstanceState(outState);
493 
494         // If the dialog is showing, save its state.
495         if (mDialog != null && mDialog.isShowing()) {
496             outState.putBoolean(SAVE_DIALOG_EDIT_MODE, mDlgEdit);
497             if (mDlgAccessPoint != null) {
498                 mAccessPointSavedState = new Bundle();
499                 mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
500                 outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
501             }
502         }
503     }
504 
505     @Override
onOptionsItemSelected(MenuItem item)506     public boolean onOptionsItemSelected(MenuItem item) {
507         // If the user is not allowed to configure wifi, do not handle menu selections.
508         if (isRestrictedAndNotPinProtected()) return false;
509 
510         switch (item.getItemId()) {
511             case MENU_ID_WPS_PBC:
512                 showDialog(WPS_PBC_DIALOG_ID);
513                 return true;
514             case MENU_ID_P2P:
515                 if (getActivity() instanceof PreferenceActivity) {
516                     ((PreferenceActivity) getActivity()).startPreferencePanel(
517                             WifiP2pSettings.class.getCanonicalName(),
518                             null,
519                             R.string.wifi_p2p_settings_title, null,
520                             this, 0);
521                 } else {
522                     startFragment(this, WifiP2pSettings.class.getCanonicalName(), -1, null);
523                 }
524                 return true;
525             case MENU_ID_WPS_PIN:
526                 showDialog(WPS_PIN_DIALOG_ID);
527                 return true;
528             case MENU_ID_SCAN:
529                 if (mWifiManager.isWifiEnabled()) {
530                     mScanner.forceScan();
531                 }
532                 return true;
533             case MENU_ID_ADD_NETWORK:
534                 if (mWifiManager.isWifiEnabled()) {
535                     onAddNetworkPressed();
536                 }
537                 return true;
538             case MENU_ID_ADVANCED:
539                 if (getActivity() instanceof PreferenceActivity) {
540                     ((PreferenceActivity) getActivity()).startPreferencePanel(
541                             AdvancedWifiSettings.class.getCanonicalName(),
542                             null,
543                             R.string.wifi_advanced_titlebar, null,
544                             this, 0);
545                 } else {
546                     startFragment(this, AdvancedWifiSettings.class.getCanonicalName(), -1, null);
547                 }
548                 return true;
549         }
550         return super.onOptionsItemSelected(item);
551     }
552 
553     @Override
onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info)554     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
555         if (info instanceof AdapterContextMenuInfo) {
556             Preference preference = (Preference) getListView().getItemAtPosition(
557                     ((AdapterContextMenuInfo) info).position);
558 
559             if (preference instanceof AccessPoint) {
560                 mSelectedAccessPoint = (AccessPoint) preference;
561                 menu.setHeaderTitle(mSelectedAccessPoint.ssid);
562                 if (mSelectedAccessPoint.getLevel() != -1
563                         && mSelectedAccessPoint.getState() == null) {
564                     menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
565                 }
566                 if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
567                     menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
568                     menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
569                 }
570             }
571         }
572     }
573 
574     @Override
onContextItemSelected(MenuItem item)575     public boolean onContextItemSelected(MenuItem item) {
576         if (mSelectedAccessPoint == null) {
577             return super.onContextItemSelected(item);
578         }
579         switch (item.getItemId()) {
580             case MENU_ID_CONNECT: {
581                 if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
582                     mWifiManager.connect(mSelectedAccessPoint.networkId,
583                             mConnectListener);
584                 } else if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE) {
585                     /** Bypass dialog for unsecured networks */
586                     mSelectedAccessPoint.generateOpenNetworkConfig();
587                     mWifiManager.connect(mSelectedAccessPoint.getConfig(),
588                             mConnectListener);
589                 } else {
590                     showDialog(mSelectedAccessPoint, true);
591                 }
592                 return true;
593             }
594             case MENU_ID_FORGET: {
595                 mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener);
596                 return true;
597             }
598             case MENU_ID_MODIFY: {
599                 showDialog(mSelectedAccessPoint, true);
600                 return true;
601             }
602         }
603         return super.onContextItemSelected(item);
604     }
605 
606     @Override
onPreferenceTreeClick(PreferenceScreen screen, Preference preference)607     public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
608         if (preference instanceof AccessPoint) {
609             mSelectedAccessPoint = (AccessPoint) preference;
610             /** Bypass dialog for unsecured, unsaved networks */
611             if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE &&
612                     mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
613                 mSelectedAccessPoint.generateOpenNetworkConfig();
614                 mWifiManager.connect(mSelectedAccessPoint.getConfig(), mConnectListener);
615             } else {
616                 showDialog(mSelectedAccessPoint, false);
617             }
618         } else {
619             return super.onPreferenceTreeClick(screen, preference);
620         }
621         return true;
622     }
623 
showDialog(AccessPoint accessPoint, boolean edit)624     private void showDialog(AccessPoint accessPoint, boolean edit) {
625         if (mDialog != null) {
626             removeDialog(WIFI_DIALOG_ID);
627             mDialog = null;
628         }
629 
630         // Save the access point and edit mode
631         mDlgAccessPoint = accessPoint;
632         mDlgEdit = edit;
633 
634         showDialog(WIFI_DIALOG_ID);
635     }
636 
637     @Override
onCreateDialog(int dialogId)638     public Dialog onCreateDialog(int dialogId) {
639         switch (dialogId) {
640             case WIFI_DIALOG_ID:
641                 AccessPoint ap = mDlgAccessPoint; // For manual launch
642                 if (ap == null) { // For re-launch from saved state
643                     if (mAccessPointSavedState != null) {
644                         ap = new AccessPoint(getActivity(), mAccessPointSavedState);
645                         // For repeated orientation changes
646                         mDlgAccessPoint = ap;
647                         // Reset the saved access point data
648                         mAccessPointSavedState = null;
649                     }
650                 }
651                 // If it's still null, fine, it's for Add Network
652                 mSelectedAccessPoint = ap;
653                 mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit);
654                 return mDialog;
655             case WPS_PBC_DIALOG_ID:
656                 return new WpsDialog(getActivity(), WpsInfo.PBC);
657             case WPS_PIN_DIALOG_ID:
658                 return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
659             case WIFI_SKIPPED_DIALOG_ID:
660                 return new AlertDialog.Builder(getActivity())
661                             .setMessage(R.string.wifi_skipped_message)
662                             .setCancelable(false)
663                             .setNegativeButton(R.string.wifi_skip_anyway,
664                                     new DialogInterface.OnClickListener() {
665                                 @Override
666                                 public void onClick(DialogInterface dialog, int id) {
667                                     getActivity().setResult(RESULT_SKIP);
668                                     getActivity().finish();
669                                 }
670                             })
671                             .setPositiveButton(R.string.wifi_dont_skip,
672                                     new DialogInterface.OnClickListener() {
673                                 @Override
674                                 public void onClick(DialogInterface dialog, int id) {
675                                 }
676                             })
677                             .create();
678             case WIFI_AND_MOBILE_SKIPPED_DIALOG_ID:
679                 return new AlertDialog.Builder(getActivity())
680                             .setMessage(R.string.wifi_and_mobile_skipped_message)
681                             .setCancelable(false)
682                             .setNegativeButton(R.string.wifi_skip_anyway,
683                                     new DialogInterface.OnClickListener() {
684                                 @Override
685                                 public void onClick(DialogInterface dialog, int id) {
686                                     getActivity().setResult(RESULT_SKIP);
687                                     getActivity().finish();
688                                 }
689                             })
690                             .setPositiveButton(R.string.wifi_dont_skip,
691                                     new DialogInterface.OnClickListener() {
692                                 @Override
693                                 public void onClick(DialogInterface dialog, int id) {
694                                 }
695                             })
696                             .create();
697 
698         }
699         return super.onCreateDialog(dialogId);
700     }
701 
702     /**
703      * Shows the latest access points available with supplimental information like
704      * the strength of network and the security for it.
705      */
706     private void updateAccessPoints() {
707         // Safeguard from some delayed event handling
708         if (getActivity() == null) return;
709 
710         if (isRestrictedAndNotPinProtected()) {
711             addMessagePreference(R.string.wifi_empty_list_user_restricted);
712             return;
713         }
714         final int wifiState = mWifiManager.getWifiState();
715 
716         switch (wifiState) {
717             case WifiManager.WIFI_STATE_ENABLED:
718                 // AccessPoints are automatically sorted with TreeSet.
719                 final Collection<AccessPoint> accessPoints = constructAccessPoints();
720                 getPreferenceScreen().removeAll();
721                 if(accessPoints.size() == 0) {
722                     addMessagePreference(R.string.wifi_empty_list_wifi_on);
723                 }
724                 for (AccessPoint accessPoint : accessPoints) {
725                     getPreferenceScreen().addPreference(accessPoint);
726                 }
727                 break;
728 
729             case WifiManager.WIFI_STATE_ENABLING:
730                 getPreferenceScreen().removeAll();
731                 break;
732 
733             case WifiManager.WIFI_STATE_DISABLING:
734                 addMessagePreference(R.string.wifi_stopping);
735                 break;
736 
737             case WifiManager.WIFI_STATE_DISABLED:
738                 setOffMessage();
739                 break;
740         }
741     }
742 
743     private void setOffMessage() {
744         if (mEmptyView != null) {
745             mEmptyView.setText(R.string.wifi_empty_list_wifi_off);
746             if (Settings.Global.getInt(getActivity().getContentResolver(),
747                     Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1) {
748                 mEmptyView.append("\n\n");
749                 int resId;
750                 if (Settings.Secure.isLocationProviderEnabled(getActivity().getContentResolver(),
751                         LocationManager.NETWORK_PROVIDER)) {
752                     resId = R.string.wifi_scan_notify_text_location_on;
753                 } else {
754                     resId = R.string.wifi_scan_notify_text_location_off;
755                 }
756                 CharSequence charSeq = getText(resId);
757                 mEmptyView.append(charSeq);
758             }
759         }
760         getPreferenceScreen().removeAll();
761     }
762 
763     private void addMessagePreference(int messageId) {
764         if (mEmptyView != null) mEmptyView.setText(messageId);
765         getPreferenceScreen().removeAll();
766     }
767 
768     /** Returns sorted list of access points */
769     private List<AccessPoint> constructAccessPoints() {
770         ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
771         /** Lookup table to more quickly update AccessPoints by only considering objects with the
772          * correct SSID.  Maps SSID -> List of AccessPoints with the given SSID.  */
773         Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
774 
775         final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
776         if (configs != null) {
777             for (WifiConfiguration config : configs) {
778                 AccessPoint accessPoint = new AccessPoint(getActivity(), config);
779                 accessPoint.update(mLastInfo, mLastState);
780                 accessPoints.add(accessPoint);
781                 apMap.put(accessPoint.ssid, accessPoint);
782             }
783         }
784 
785         final List<ScanResult> results = mWifiManager.getScanResults();
786         if (results != null) {
787             for (ScanResult result : results) {
788                 // Ignore hidden and ad-hoc networks.
789                 if (result.SSID == null || result.SSID.length() == 0 ||
790                         result.capabilities.contains("[IBSS]")) {
791                     continue;
792                 }
793 
794                 boolean found = false;
795                 for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
796                     if (accessPoint.update(result))
797                         found = true;
798                 }
799                 if (!found) {
800                     AccessPoint accessPoint = new AccessPoint(getActivity(), result);
801                     accessPoints.add(accessPoint);
802                     apMap.put(accessPoint.ssid, accessPoint);
803                 }
804             }
805         }
806 
807         // Pre-sort accessPoints to speed preference insertion
808         Collections.sort(accessPoints);
809         return accessPoints;
810     }
811 
812     /** A restricted multimap for use in constructAccessPoints */
813     private class Multimap<K,V> {
814         private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
815         /** retrieve a non-null list of values with key K */
816         List<V> getAll(K key) {
817             List<V> values = store.get(key);
818             return values != null ? values : Collections.<V>emptyList();
819         }
820 
821         void put(K key, V val) {
822             List<V> curVals = store.get(key);
823             if (curVals == null) {
824                 curVals = new ArrayList<V>(3);
825                 store.put(key, curVals);
826             }
827             curVals.add(val);
828         }
829     }
830 
831     private void handleEvent(Context context, Intent intent) {
832         String action = intent.getAction();
833         if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
834             updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
835                     WifiManager.WIFI_STATE_UNKNOWN));
836         } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
837                 WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
838                 WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
839                 updateAccessPoints();
840         } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) {
841             //Ignore supplicant state changes when network is connected
842             //TODO: we should deprecate SUPPLICANT_STATE_CHANGED_ACTION and
843             //introduce a broadcast that combines the supplicant and network
844             //network state change events so the apps dont have to worry about
845             //ignoring supplicant state change when network is connected
846             //to get more fine grained information.
847             SupplicantState state = (SupplicantState) intent.getParcelableExtra(
848                     WifiManager.EXTRA_NEW_STATE);
849             if (!mConnected.get() && SupplicantState.isHandshakeState(state)) {
850                 updateConnectionState(WifiInfo.getDetailedStateOf(state));
851              } else {
852                  // During a connect, we may have the supplicant
853                  // state change affect the detailed network state.
854                  // Make sure a lost connection is updated as well.
855                  updateConnectionState(null);
856              }
857         } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
858             NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
859                     WifiManager.EXTRA_NETWORK_INFO);
860             mConnected.set(info.isConnected());
861             changeNextButtonState(info.isConnected());
862             updateAccessPoints();
863             updateConnectionState(info.getDetailedState());
864             if (mAutoFinishOnConnection && info.isConnected()) {
865                 Activity activity = getActivity();
866                 if (activity != null) {
867                     activity.setResult(Activity.RESULT_OK);
868                     activity.finish();
869                 }
870                 return;
871             }
872         } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
873             updateConnectionState(null);
874         }
875     }
876 
877     private void updateConnectionState(DetailedState state) {
878         /* sticky broadcasts can call this when wifi is disabled */
879         if (!mWifiManager.isWifiEnabled()) {
880             mScanner.pause();
881             return;
882         }
883 
884         if (state == DetailedState.OBTAINING_IPADDR) {
885             mScanner.pause();
886         } else {
887             mScanner.resume();
888         }
889 
890         mLastInfo = mWifiManager.getConnectionInfo();
891         if (state != null) {
892             mLastState = state;
893         }
894 
895         for (int i = getPreferenceScreen().getPreferenceCount() - 1; i >= 0; --i) {
896             // Maybe there's a WifiConfigPreference
897             Preference preference = getPreferenceScreen().getPreference(i);
898             if (preference instanceof AccessPoint) {
899                 final AccessPoint accessPoint = (AccessPoint) preference;
900                 accessPoint.update(mLastInfo, mLastState);
901             }
902         }
903     }
904 
905     private void updateWifiState(int state) {
906         Activity activity = getActivity();
907         if (activity != null) {
908             activity.invalidateOptionsMenu();
909         }
910 
911         switch (state) {
912             case WifiManager.WIFI_STATE_ENABLED:
913                 mScanner.resume();
914                 return; // not break, to avoid the call to pause() below
915 
916             case WifiManager.WIFI_STATE_ENABLING:
917                 addMessagePreference(R.string.wifi_starting);
918                 break;
919 
920             case WifiManager.WIFI_STATE_DISABLED:
921                 setOffMessage();
922                 break;
923         }
924 
925         mLastInfo = null;
926         mLastState = null;
927         mScanner.pause();
928     }
929 
930     private class Scanner extends Handler {
931         private int mRetry = 0;
932 
933         void resume() {
934             if (!hasMessages(0)) {
935                 sendEmptyMessage(0);
936             }
937         }
938 
939         void forceScan() {
940             removeMessages(0);
941             sendEmptyMessage(0);
942         }
943 
944         void pause() {
945             mRetry = 0;
946             removeMessages(0);
947         }
948 
949         @Override
950         public void handleMessage(Message message) {
951             if (mWifiManager.startScan()) {
952                 mRetry = 0;
953             } else if (++mRetry >= 3) {
954                 mRetry = 0;
955                 Activity activity = getActivity();
956                 if (activity != null) {
957                     Toast.makeText(activity, R.string.wifi_fail_to_scan,
958                             Toast.LENGTH_LONG).show();
959                 }
960                 return;
961             }
962             sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
963         }
964     }
965 
966     /**
967      * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
968      * Wifi setup screens, not in usual wifi settings screen.
969      *
970      * @param connected true when the device is connected to a wifi network.
971      */
972     private void changeNextButtonState(boolean connected) {
973         if (mEnableNextOnConnection && hasNextButton()) {
974             getNextButton().setEnabled(connected);
975         }
976     }
977 
978     @Override
979     public void onClick(DialogInterface dialogInterface, int button) {
980         if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
981             forget();
982         } else if (button == WifiDialog.BUTTON_SUBMIT) {
983             if (mDialog != null) {
984                 submit(mDialog.getController());
985             }
986         }
987     }
988 
989     /* package */ void submit(WifiConfigController configController) {
990 
991         final WifiConfiguration config = configController.getConfig();
992 
993         if (config == null) {
994             if (mSelectedAccessPoint != null
995                     && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
996                 mWifiManager.connect(mSelectedAccessPoint.networkId,
997                         mConnectListener);
998             }
999         } else if (config.networkId != INVALID_NETWORK_ID) {
1000             if (mSelectedAccessPoint != null) {
1001                 mWifiManager.save(config, mSaveListener);
1002             }
1003         } else {
1004             if (configController.isEdit()) {
1005                 mWifiManager.save(config, mSaveListener);
1006             } else {
1007                 mWifiManager.connect(config, mConnectListener);
1008             }
1009         }
1010 
1011         if (mWifiManager.isWifiEnabled()) {
1012             mScanner.resume();
1013         }
1014         updateAccessPoints();
1015     }
1016 
1017     /* package */ void forget() {
1018         if (mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
1019             // Should not happen, but a monkey seems to triger it
1020             Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
1021             return;
1022         }
1023 
1024         mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener);
1025 
1026         if (mWifiManager.isWifiEnabled()) {
1027             mScanner.resume();
1028         }
1029         updateAccessPoints();
1030 
1031         // We need to rename/replace "Next" button in wifi setup context.
1032         changeNextButtonState(false);
1033     }
1034 
1035     /**
1036      * Refreshes acccess points and ask Wifi module to scan networks again.
1037      */
1038     /* package */ void refreshAccessPoints() {
1039         if (mWifiManager.isWifiEnabled()) {
1040             mScanner.resume();
1041         }
1042 
1043         getPreferenceScreen().removeAll();
1044     }
1045 
1046     /**
1047      * Called when "add network" button is pressed.
1048      */
1049     /* package */ void onAddNetworkPressed() {
1050         // No exact access point is selected.
1051         mSelectedAccessPoint = null;
1052         showDialog(null, true);
1053     }
1054 
1055     /* package */ int getAccessPointsCount() {
1056         final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
1057         if (wifiIsEnabled) {
1058             return getPreferenceScreen().getPreferenceCount();
1059         } else {
1060             return 0;
1061         }
1062     }
1063 
1064     /**
1065      * Requests wifi module to pause wifi scan. May be ignored when the module is disabled.
1066      */
1067     /* package */ void pauseWifiScan() {
1068         if (mWifiManager.isWifiEnabled()) {
1069             mScanner.pause();
1070         }
1071     }
1072 
1073     /**
1074      * Requests wifi module to resume wifi scan. May be ignored when the module is disabled.
1075      */
1076     /* package */ void resumeWifiScan() {
1077         if (mWifiManager.isWifiEnabled()) {
1078             mScanner.resume();
1079         }
1080     }
1081 
1082     @Override
1083     protected int getHelpResource() {
1084         if (mSetupWizardMode) {
1085             return 0;
1086         }
1087         return R.string.help_url_wifi;
1088     }
1089 
1090     /**
1091      * Used as the outer frame of all setup wizard pages that need to adjust their margins based
1092      * on the total size of the available display. (e.g. side margins set to 10% of total width.)
1093      */
1094     public static class ProportionalOuterFrame extends RelativeLayout {
1095         public ProportionalOuterFrame(Context context) {
1096             super(context);
1097         }
1098         public ProportionalOuterFrame(Context context, AttributeSet attrs) {
1099             super(context, attrs);
1100         }
1101         public ProportionalOuterFrame(Context context, AttributeSet attrs, int defStyle) {
1102             super(context, attrs, defStyle);
1103         }
1104 
1105         /**
1106          * Set our margins and title area height proportionally to the available display size
1107          */
1108         @Override
1109         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1110             int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
1111             int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
1112             final Resources resources = getContext().getResources();
1113             float titleHeight = resources.getFraction(R.dimen.setup_title_height, 1, 1);
1114             float sideMargin = resources.getFraction(R.dimen.setup_border_width, 1, 1);
1115             int bottom = resources.getDimensionPixelSize(R.dimen.setup_margin_bottom);
1116             setPaddingRelative(
1117                     (int) (parentWidth * sideMargin),
1118                     0,
1119                     (int) (parentWidth * sideMargin),
1120                     bottom);
1121             View title = findViewById(R.id.title_area);
1122             if (title != null) {
1123                 title.setMinimumHeight((int) (parentHeight * titleHeight));
1124             }
1125             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1126         }
1127     }
1128 
1129 }
1130