WifiSettings.java revision f5a6e5bfa9497e41d518c24d1b5f3b235962a379
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                mScanner.resume();
192                return true;
193            case MENU_ID_ADVANCED:
194                startActivity(new Intent(this, AdvancedSettings.class));
195                return true;
196        }
197        return super.onOptionsItemSelected(item);
198    }
199
200    @Override
201    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
202        if (info instanceof AdapterContextMenuInfo) {
203            Preference preference = (Preference) getListView().getItemAtPosition(
204                    ((AdapterContextMenuInfo) info).position);
205
206            if (preference instanceof AccessPoint) {
207                mSelected = (AccessPoint) preference;
208                menu.setHeaderTitle(mSelected.ssid);
209                if (mSelected.getLevel() != -1 && mSelected.getState() == null) {
210                    menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
211                }
212                if (mSelected.networkId != -1) {
213                    menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
214                    if (mSelected.security != AccessPoint.SECURITY_NONE) {
215                        menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
216                    }
217                }
218            }
219        }
220    }
221
222    @Override
223    public boolean onContextItemSelected(MenuItem item) {
224        if (mSelected == null) {
225            return super.onContextItemSelected(item);
226        }
227        switch (item.getItemId()) {
228            case MENU_ID_CONNECT:
229                if (mSelected.networkId != -1) {
230                    if (!requireKeyStore(mSelected.getConfig())) {
231                        connect(mSelected.networkId);
232                    }
233                } else if (mSelected.security == AccessPoint.SECURITY_NONE) {
234                    // Shortcut for open networks.
235                    WifiConfiguration config = new WifiConfiguration();
236                    config.SSID = mSelected.ssid;
237                    config.allowedKeyManagement.set(KeyMgmt.NONE);
238                    int networkId = mWifiManager.addNetwork(config);
239                    mWifiManager.enableNetwork(networkId, false);
240                    connect(networkId);
241                } else {
242                    showDialog(mSelected, false);
243                }
244                return true;
245            case MENU_ID_FORGET:
246                forget(mSelected.networkId);
247                return true;
248            case MENU_ID_MODIFY:
249                showDialog(mSelected, true);
250                return true;
251        }
252        return super.onContextItemSelected(item);
253    }
254
255    @Override
256    public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
257        if (preference instanceof AccessPoint) {
258            mSelected = (AccessPoint) preference;
259            showDialog(mSelected, false);
260        } else if (preference == mAddNetwork) {
261            mSelected = null;
262            showDialog(null, true);
263        } else if (preference == mNotifyOpenNetworks) {
264            Secure.putInt(getContentResolver(),
265                    Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
266                    mNotifyOpenNetworks.isChecked() ? 1 : 0);
267        } else {
268            return super.onPreferenceTreeClick(screen, preference);
269        }
270        return true;
271    }
272
273    public void onClick(DialogInterface dialogInterface, int button) {
274        if (button == WifiDialog.BUTTON_FORGET && mSelected != null) {
275            forget(mSelected.networkId);
276        } else if (button == WifiDialog.BUTTON_SUBMIT && mDialog != null) {
277            WifiConfiguration config = mDialog.getConfig();
278
279            if (config == null) {
280                if (mSelected != null && !requireKeyStore(mSelected.getConfig())) {
281                    connect(mSelected.networkId);
282                }
283            } else if (config.networkId != -1) {
284                if (mSelected != null) {
285                    mWifiManager.updateNetwork(config);
286                    saveNetworks();
287                }
288            } else {
289                int networkId = mWifiManager.addNetwork(config);
290                if (networkId != -1) {
291                    mWifiManager.enableNetwork(networkId, false);
292                    config.networkId = networkId;
293                    if (mDialog.edit || requireKeyStore(config)) {
294                        saveNetworks();
295                    } else {
296                        connect(networkId);
297                    }
298                }
299            }
300        }
301    }
302
303    private void showDialog(AccessPoint accessPoint, boolean edit) {
304        if (mDialog != null) {
305            mDialog.dismiss();
306        }
307        mDialog = new WifiDialog(this, this, accessPoint, edit);
308        mDialog.show();
309    }
310
311    private boolean requireKeyStore(WifiConfiguration config) {
312        if (WifiDialog.requireKeyStore(config) &&
313                KeyStore.getInstance().test() != KeyStore.NO_ERROR) {
314            mKeyStoreNetworkId = config.networkId;
315            Credentials.getInstance().unlock(this);
316            return true;
317        }
318        return false;
319    }
320
321    private void forget(int networkId) {
322        mWifiManager.removeNetwork(networkId);
323        saveNetworks();
324    }
325
326    private void connect(int networkId) {
327        if (networkId == -1) {
328            return;
329        }
330
331        // Reset the priority of each network if it goes too high.
332        if (mLastPriority > 1000000) {
333            for (int i = mAccessPoints.getPreferenceCount() - 1; i >= 0; --i) {
334                AccessPoint accessPoint = (AccessPoint) mAccessPoints.getPreference(i);
335                if (accessPoint.networkId != -1) {
336                    WifiConfiguration config = new WifiConfiguration();
337                    config.networkId = accessPoint.networkId;
338                    config.priority = 0;
339                    mWifiManager.updateNetwork(config);
340                }
341            }
342            mLastPriority = 0;
343        }
344
345        // Set to the highest priority and save the configuration.
346        WifiConfiguration config = new WifiConfiguration();
347        config.networkId = networkId;
348        config.priority = ++mLastPriority;
349        mWifiManager.updateNetwork(config);
350        saveNetworks();
351
352        // Connect to network by disabling others.
353        mWifiManager.enableNetwork(networkId, true);
354        mWifiManager.reconnect();
355        mResetNetworks = true;
356    }
357
358    private void enableNetworks() {
359        for (int i = mAccessPoints.getPreferenceCount() - 1; i >= 0; --i) {
360            WifiConfiguration config = ((AccessPoint) mAccessPoints.getPreference(i)).getConfig();
361            if (config != null && config.status != Status.ENABLED) {
362                mWifiManager.enableNetwork(config.networkId, false);
363            }
364        }
365        mResetNetworks = false;
366    }
367
368    private void saveNetworks() {
369        // Always save the configuration with all networks enabled.
370        enableNetworks();
371        mWifiManager.saveConfiguration();
372        updateAccessPoints();
373    }
374
375    private void updateAccessPoints() {
376        List<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
377
378        List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
379        if (configs != null) {
380            mLastPriority = 0;
381            for (WifiConfiguration config : configs) {
382                if (config.priority > mLastPriority) {
383                    mLastPriority = config.priority;
384                }
385
386                // Shift the status to make enableNetworks() more efficient.
387                if (config.status == Status.CURRENT) {
388                    config.status = Status.ENABLED;
389                } else if (mResetNetworks && config.status == Status.DISABLED) {
390                    config.status = Status.CURRENT;
391                }
392
393                AccessPoint accessPoint = new AccessPoint(this, config);
394                accessPoint.update(mLastInfo, mLastState);
395                accessPoints.add(accessPoint);
396            }
397        }
398
399        List<ScanResult> results = mWifiManager.getScanResults();
400        if (results != null) {
401            for (ScanResult result : results) {
402                // Ignore hidden and ad-hoc networks.
403                if (result.SSID == null || result.SSID.length() == 0 ||
404                        result.capabilities.contains("[IBSS]")) {
405                    continue;
406                }
407
408                boolean found = false;
409                for (AccessPoint accessPoint : accessPoints) {
410                    if (accessPoint.update(result)) {
411                        found = true;
412                    }
413                }
414                if (!found) {
415                    accessPoints.add(new AccessPoint(this, result));
416                }
417            }
418        }
419
420        mAccessPoints.removeAll();
421        for (AccessPoint accessPoint : accessPoints) {
422            mAccessPoints.addPreference(accessPoint);
423        }
424    }
425
426    private void handleEvent(Intent intent) {
427        String action = intent.getAction();
428        if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
429            updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
430                    WifiManager.WIFI_STATE_UNKNOWN));
431        } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
432            updateAccessPoints();
433        } else if (WifiManager.NETWORK_IDS_CHANGED_ACTION.equals(action)) {
434            if (mSelected != null && mSelected.networkId != -1) {
435                mSelected = null;
436            }
437            updateAccessPoints();
438        } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) {
439            updateConnectionState(WifiInfo.getDetailedStateOf((SupplicantState)
440                    intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE)));
441        } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
442            NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
443                    WifiManager.EXTRA_NETWORK_INFO);
444            if (mEnableNextOnConnection && hasNextButton()) {
445                getNextButton().setEnabled(info.isConnected());
446            }
447            updateConnectionState(info.getDetailedState());
448        } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
449            updateConnectionState(null);
450        }
451    }
452
453    private void updateConnectionState(DetailedState state) {
454        if (state == DetailedState.OBTAINING_IPADDR) {
455            mScanner.pause();
456        } else {
457            mScanner.resume();
458        }
459
460        mLastInfo = mWifiManager.getConnectionInfo();
461        if (state != null) {
462            mLastState = state;
463        }
464
465        for (int i = mAccessPoints.getPreferenceCount() - 1; i >= 0; --i) {
466            ((AccessPoint) mAccessPoints.getPreference(i)).update(mLastInfo, mLastState);
467        }
468
469        if (mResetNetworks && (state == DetailedState.CONNECTED ||
470                state == DetailedState.DISCONNECTED || state == DetailedState.FAILED)) {
471            updateAccessPoints();
472            enableNetworks();
473        }
474    }
475
476    private void updateWifiState(int state) {
477        if (state == WifiManager.WIFI_STATE_ENABLED) {
478            mScanner.resume();
479            updateAccessPoints();
480        } else {
481            mScanner.pause();
482            mAccessPoints.removeAll();
483        }
484    }
485
486    private class Scanner extends Handler {
487        private int mRetry = 0;
488
489        void resume() {
490            if (!hasMessages(0)) {
491                sendEmptyMessage(0);
492            }
493        }
494
495        void pause() {
496            mRetry = 0;
497            mAccessPoints.setProgress(false);
498            removeMessages(0);
499        }
500
501        @Override
502        public void handleMessage(Message message) {
503            if (mWifiManager.startScanActive()) {
504                mRetry = 0;
505            } else if (++mRetry >= 3) {
506                mRetry = 0;
507                Toast.makeText(WifiSettings.this, R.string.wifi_fail_to_scan,
508                        Toast.LENGTH_LONG).show();
509            }
510            mAccessPoints.setProgress(mRetry != 0);
511            sendEmptyMessageDelayed(0, 6000);
512        }
513    }
514}
515