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