WifiSettings.java revision 5536c4a052ed307028394fcd2cafe141d723e54b
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
17package com.android.settings.wifi;
18
19import com.android.settings.ProgressCategory;
20import com.android.settings.R;
21
22import android.content.BroadcastReceiver;
23import android.content.Context;
24import android.content.DialogInterface;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.net.ConnectivityManager;
28import android.net.NetworkInfo;
29import android.net.NetworkInfo.DetailedState;
30import android.net.wifi.ScanResult;
31import android.net.wifi.SupplicantState;
32import android.net.wifi.WifiConfiguration;
33import android.net.wifi.WifiConfiguration.KeyMgmt;
34import android.net.wifi.WifiConfiguration.Status;
35import android.net.wifi.WifiInfo;
36import android.net.wifi.WifiManager;
37import android.os.Bundle;
38import android.os.Handler;
39import android.os.Message;
40import android.preference.CheckBoxPreference;
41import android.preference.Preference;
42import android.preference.PreferenceActivity;
43import android.preference.PreferenceScreen;
44import android.provider.Settings.Secure;
45import android.security.Credentials;
46import android.security.KeyStore;
47import android.text.TextUtils;
48import android.view.ContextMenu;
49import android.view.ContextMenu.ContextMenuInfo;
50import android.view.Menu;
51import android.view.MenuItem;
52import android.view.View;
53import android.widget.AdapterView.AdapterContextMenuInfo;
54import android.widget.Toast;
55
56import java.util.ArrayList;
57import java.util.List;
58
59public class WifiSettings extends PreferenceActivity implements DialogInterface.OnClickListener {
60    private static final int MENU_ID_SCAN = Menu.FIRST;
61    private static final int MENU_ID_ADVANCED = Menu.FIRST + 1;
62    private static final int MENU_ID_CONNECT = Menu.FIRST + 2;
63    private static final int MENU_ID_FORGET = Menu.FIRST + 3;
64    private static final int MENU_ID_MODIFY = Menu.FIRST + 4;
65
66    // this boolean extra specifies whether to disable the Next button when not connected
67    private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
68
69    private final IntentFilter mFilter;
70    private final BroadcastReceiver mReceiver;
71    private final Scanner mScanner;
72
73    private WifiManager mWifiManager;
74    private WifiEnabler mWifiEnabler;
75    private CheckBoxPreference mNotifyOpenNetworks;
76    private ProgressCategory mAccessPoints;
77    private Preference mAddNetwork;
78
79    private DetailedState mLastState;
80    private WifiInfo mLastInfo;
81    private int mLastPriority;
82
83    private boolean mResetNetworks = false;
84    private int mKeyStoreNetworkId = -1;
85
86    private AccessPoint mSelected;
87    private WifiDialog mDialog;
88
89    // should Next button only be enabled when we have a connection?
90    private boolean mEnableNextOnConnection;
91
92    public WifiSettings() {
93        mFilter = new IntentFilter();
94        mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
95        mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
96        mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
97        mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
98        mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
99        mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
100
101        mReceiver = new BroadcastReceiver() {
102            @Override
103            public void onReceive(Context context, Intent intent) {
104                handleEvent(intent);
105            }
106        };
107
108        mScanner = new Scanner();
109    }
110
111    @Override
112    protected void onCreate(Bundle savedInstanceState) {
113        super.onCreate(savedInstanceState);
114
115        mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
116
117        // if we're supposed to enable/disable the Next button based on our current connection
118        // state, start it off in the right state
119        mEnableNextOnConnection = getIntent().getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
120        if (mEnableNextOnConnection && hasNextButton()) {
121            ConnectivityManager connectivity = (ConnectivityManager)
122                getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
123            if (connectivity != null) {
124                NetworkInfo info = connectivity.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
125                getNextButton().setEnabled(info.isConnected());
126            }
127        }
128
129        if (getIntent().getBooleanExtra("only_access_points", false)) {
130            addPreferencesFromResource(R.xml.wifi_access_points);
131        } else {
132            addPreferencesFromResource(R.xml.wifi_settings);
133            mWifiEnabler = new WifiEnabler(this,
134                    (CheckBoxPreference) findPreference("enable_wifi"));
135            mNotifyOpenNetworks =
136                    (CheckBoxPreference) findPreference("notify_open_networks");
137            mNotifyOpenNetworks.setChecked(Secure.getInt(getContentResolver(),
138                    Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1);
139        }
140
141        mAccessPoints = (ProgressCategory) findPreference("access_points");
142        mAccessPoints.setOrderingAsAdded(false);
143        mAddNetwork = findPreference("add_network");
144
145        registerForContextMenu(getListView());
146    }
147
148    @Override
149    protected void onResume() {
150        super.onResume();
151        if (mWifiEnabler != null) {
152            mWifiEnabler.resume();
153        }
154        registerReceiver(mReceiver, mFilter);
155        if (mKeyStoreNetworkId != -1 && KeyStore.getInstance().test() == KeyStore.NO_ERROR) {
156            connect(mKeyStoreNetworkId);
157        }
158        mKeyStoreNetworkId = -1;
159    }
160
161    @Override
162    protected void onPause() {
163        super.onPause();
164        if (mWifiEnabler != null) {
165            mWifiEnabler.pause();
166        }
167        unregisterReceiver(mReceiver);
168        mScanner.pause();
169        if (mDialog != null) {
170            mDialog.dismiss();
171            mDialog = null;
172        }
173        if (mResetNetworks) {
174            enableNetworks();
175        }
176    }
177
178    @Override
179    public boolean onCreateOptionsMenu(Menu menu) {
180        menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.wifi_menu_scan)
181                .setIcon(R.drawable.ic_menu_scan_network);
182        menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
183                .setIcon(android.R.drawable.ic_menu_manage);
184        return super.onCreateOptionsMenu(menu);
185    }
186
187    @Override
188    public boolean onOptionsItemSelected(MenuItem item) {
189        switch (item.getItemId()) {
190            case MENU_ID_SCAN:
191                if (mWifiManager.isWifiEnabled()) {
192                    mScanner.resume();
193                }
194                return true;
195            case MENU_ID_ADVANCED:
196                startActivity(new Intent(this, AdvancedSettings.class));
197                return true;
198        }
199        return super.onOptionsItemSelected(item);
200    }
201
202    @Override
203    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
204        if (info instanceof AdapterContextMenuInfo) {
205            Preference preference = (Preference) getListView().getItemAtPosition(
206                    ((AdapterContextMenuInfo) info).position);
207
208            if (preference instanceof AccessPoint) {
209                mSelected = (AccessPoint) preference;
210                menu.setHeaderTitle(mSelected.ssid);
211                if (mSelected.getLevel() != -1 && mSelected.getState() == null) {
212                    menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
213                }
214                if (mSelected.networkId != -1) {
215                    menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
216                    if (mSelected.security != AccessPoint.SECURITY_NONE) {
217                        menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
218                    }
219                }
220            }
221        }
222    }
223
224    @Override
225    public boolean onContextItemSelected(MenuItem item) {
226        if (mSelected == null) {
227            return super.onContextItemSelected(item);
228        }
229        switch (item.getItemId()) {
230            case MENU_ID_CONNECT:
231                if (mSelected.networkId != -1) {
232                    if (!requireKeyStore(mSelected.getConfig())) {
233                        connect(mSelected.networkId);
234                    }
235                } else if (mSelected.security == AccessPoint.SECURITY_NONE) {
236                    // Shortcut for open networks.
237                    WifiConfiguration config = new WifiConfiguration();
238                    config.SSID = mSelected.ssid;
239                    config.allowedKeyManagement.set(KeyMgmt.NONE);
240                    int networkId = mWifiManager.addNetwork(config);
241                    mWifiManager.enableNetwork(networkId, false);
242                    connect(networkId);
243                } else {
244                    showDialog(mSelected, false);
245                }
246                return true;
247            case MENU_ID_FORGET:
248                forget(mSelected.networkId);
249                return true;
250            case MENU_ID_MODIFY:
251                showDialog(mSelected, true);
252                return true;
253        }
254        return super.onContextItemSelected(item);
255    }
256
257    @Override
258    public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
259        if (preference instanceof AccessPoint) {
260            mSelected = (AccessPoint) preference;
261            showDialog(mSelected, false);
262        } else if (preference == mAddNetwork) {
263            mSelected = null;
264            showDialog(null, true);
265        } else if (preference == mNotifyOpenNetworks) {
266            Secure.putInt(getContentResolver(),
267                    Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
268                    mNotifyOpenNetworks.isChecked() ? 1 : 0);
269        } else {
270            return super.onPreferenceTreeClick(screen, preference);
271        }
272        return true;
273    }
274
275    public void onClick(DialogInterface dialogInterface, int button) {
276        if (button == WifiDialog.BUTTON_FORGET && mSelected != null) {
277            forget(mSelected.networkId);
278        } else if (button == WifiDialog.BUTTON_SUBMIT && mDialog != null) {
279            WifiConfiguration config = mDialog.getConfig();
280
281            if (config == null) {
282                if (mSelected != null && !requireKeyStore(mSelected.getConfig())) {
283                    connect(mSelected.networkId);
284                }
285            } else if (config.networkId != -1) {
286                if (mSelected != null) {
287                    mWifiManager.updateNetwork(config);
288                    saveNetworks();
289                }
290            } else {
291                int networkId = mWifiManager.addNetwork(config);
292                if (networkId != -1) {
293                    mWifiManager.enableNetwork(networkId, false);
294                    config.networkId = networkId;
295                    if (mDialog.edit || requireKeyStore(config)) {
296                        saveNetworks();
297                    } else {
298                        connect(networkId);
299                    }
300                }
301            }
302        }
303    }
304
305    private void showDialog(AccessPoint accessPoint, boolean edit) {
306        if (mDialog != null) {
307            mDialog.dismiss();
308        }
309        mDialog = new WifiDialog(this, this, accessPoint, edit);
310        mDialog.show();
311    }
312
313    private boolean requireKeyStore(WifiConfiguration config) {
314        if (WifiDialog.requireKeyStore(config) &&
315                KeyStore.getInstance().test() != KeyStore.NO_ERROR) {
316            mKeyStoreNetworkId = config.networkId;
317            Credentials.getInstance().unlock(this);
318            return true;
319        }
320        return false;
321    }
322
323    private void forget(int networkId) {
324        mWifiManager.removeNetwork(networkId);
325        saveNetworks();
326    }
327
328    private void connect(int networkId) {
329        if (networkId == -1) {
330            return;
331        }
332
333        // Reset the priority of each network if it goes too high.
334        if (mLastPriority > 1000000) {
335            for (int i = mAccessPoints.getPreferenceCount() - 1; i >= 0; --i) {
336                AccessPoint accessPoint = (AccessPoint) mAccessPoints.getPreference(i);
337                if (accessPoint.networkId != -1) {
338                    WifiConfiguration config = new WifiConfiguration();
339                    config.networkId = accessPoint.networkId;
340                    config.priority = 0;
341                    mWifiManager.updateNetwork(config);
342                }
343            }
344            mLastPriority = 0;
345        }
346
347        // Set to the highest priority and save the configuration.
348        WifiConfiguration config = new WifiConfiguration();
349        config.networkId = networkId;
350        config.priority = ++mLastPriority;
351        mWifiManager.updateNetwork(config);
352        saveNetworks();
353
354        // Connect to network by disabling others.
355        mWifiManager.enableNetwork(networkId, true);
356        mWifiManager.reconnect();
357        mResetNetworks = true;
358    }
359
360    private void enableNetworks() {
361        for (int i = mAccessPoints.getPreferenceCount() - 1; i >= 0; --i) {
362            WifiConfiguration config = ((AccessPoint) mAccessPoints.getPreference(i)).getConfig();
363            if (config != null && config.status != Status.ENABLED) {
364                mWifiManager.enableNetwork(config.networkId, false);
365            }
366        }
367        mResetNetworks = false;
368    }
369
370    private void saveNetworks() {
371        // Always save the configuration with all networks enabled.
372        enableNetworks();
373        mWifiManager.saveConfiguration();
374        updateAccessPoints();
375    }
376
377    private void updateAccessPoints() {
378        List<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
379
380        List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
381        if (configs != null) {
382            mLastPriority = 0;
383            for (WifiConfiguration config : configs) {
384                if (config.priority > mLastPriority) {
385                    mLastPriority = config.priority;
386                }
387
388                // Shift the status to make enableNetworks() more efficient.
389                if (config.status == Status.CURRENT) {
390                    config.status = Status.ENABLED;
391                } else if (mResetNetworks && config.status == Status.DISABLED) {
392                    config.status = Status.CURRENT;
393                }
394
395                AccessPoint accessPoint = new AccessPoint(this, config);
396                accessPoint.update(mLastInfo, mLastState);
397                accessPoints.add(accessPoint);
398            }
399        }
400
401        List<ScanResult> results = mWifiManager.getScanResults();
402        if (results != null) {
403            for (ScanResult result : results) {
404                // Ignore hidden and ad-hoc networks.
405                if (result.SSID == null || result.SSID.length() == 0 ||
406                        result.capabilities.contains("[IBSS]")) {
407                    continue;
408                }
409
410                boolean found = false;
411                for (AccessPoint accessPoint : accessPoints) {
412                    if (accessPoint.update(result)) {
413                        found = true;
414                    }
415                }
416                if (!found) {
417                    accessPoints.add(new AccessPoint(this, result));
418                }
419            }
420        }
421
422        mAccessPoints.removeAll();
423        for (AccessPoint accessPoint : accessPoints) {
424            mAccessPoints.addPreference(accessPoint);
425        }
426    }
427
428    private void handleEvent(Intent intent) {
429        String action = intent.getAction();
430        if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
431            updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
432                    WifiManager.WIFI_STATE_UNKNOWN));
433        } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
434            updateAccessPoints();
435        } else if (WifiManager.NETWORK_IDS_CHANGED_ACTION.equals(action)) {
436            if (mSelected != null && mSelected.networkId != -1) {
437                mSelected = null;
438            }
439            updateAccessPoints();
440        } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) {
441            updateConnectionState(WifiInfo.getDetailedStateOf((SupplicantState)
442                    intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE)));
443        } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
444            NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
445                    WifiManager.EXTRA_NETWORK_INFO);
446            if (mEnableNextOnConnection && hasNextButton()) {
447                getNextButton().setEnabled(info.isConnected());
448            }
449            updateConnectionState(info.getDetailedState());
450        } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
451            updateConnectionState(null);
452        }
453    }
454
455    private void updateConnectionState(DetailedState state) {
456        /* sticky broadcasts can call this when wifi is disabled */
457        if (!mWifiManager.isWifiEnabled())
458            return;
459
460        if (state == DetailedState.OBTAINING_IPADDR) {
461            mScanner.pause();
462        } else {
463            mScanner.resume();
464        }
465
466        mLastInfo = mWifiManager.getConnectionInfo();
467        if (state != null) {
468            mLastState = state;
469        }
470
471        for (int i = mAccessPoints.getPreferenceCount() - 1; i >= 0; --i) {
472            ((AccessPoint) mAccessPoints.getPreference(i)).update(mLastInfo, mLastState);
473        }
474
475        if (mResetNetworks && (state == DetailedState.CONNECTED ||
476                state == DetailedState.DISCONNECTED || state == DetailedState.FAILED)) {
477            updateAccessPoints();
478            enableNetworks();
479        }
480    }
481
482    private void updateWifiState(int state) {
483        if (state == WifiManager.WIFI_STATE_ENABLED) {
484            mScanner.resume();
485            updateAccessPoints();
486        } else {
487            mScanner.pause();
488            mAccessPoints.removeAll();
489        }
490    }
491
492    private class Scanner extends Handler {
493        private int mRetry = 0;
494
495        void resume() {
496            if (!hasMessages(0)) {
497                sendEmptyMessage(0);
498            }
499        }
500
501        void pause() {
502            mRetry = 0;
503            mAccessPoints.setProgress(false);
504            removeMessages(0);
505        }
506
507        @Override
508        public void handleMessage(Message message) {
509            if (mWifiManager.startScanActive()) {
510                mRetry = 0;
511            } else if (++mRetry >= 3) {
512                mRetry = 0;
513                Toast.makeText(WifiSettings.this, R.string.wifi_fail_to_scan,
514                        Toast.LENGTH_LONG).show();
515            }
516            mAccessPoints.setProgress(mRetry != 0);
517            sendEmptyMessageDelayed(0, 6000);
518        }
519    }
520}
521