WifiSettings.java revision eb48882e18ba9b1241a4d79c0cd45d6994b6dec6
1/*
2 * Copyright (C) 2007 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.app.Dialog;
23import android.content.DialogInterface;
24import android.content.Intent;
25import android.net.wifi.WifiManager;
26import android.os.Bundle;
27import android.preference.Preference;
28import android.preference.PreferenceActivity;
29import android.preference.PreferenceScreen;
30import android.preference.CheckBoxPreference;
31import android.provider.Settings;
32import android.security.Credentials;
33import android.security.KeyStore;
34import android.util.Log;
35import android.view.ContextMenu;
36import android.view.Menu;
37import android.view.MenuItem;
38import android.view.View;
39import android.view.ContextMenu.ContextMenuInfo;
40import android.widget.AdapterView;
41import android.widget.Toast;
42import android.widget.AdapterView.AdapterContextMenuInfo;
43
44import java.util.Set;
45import java.util.WeakHashMap;
46
47/**
48 * Settings screen for WiFi. This will be launched from the main system settings.
49 */
50public class WifiSettings extends PreferenceActivity implements WifiLayer.Callback,
51        DialogInterface.OnDismissListener {
52
53    private static final String TAG = "WifiSettings";
54
55    //============================
56    // Preference/activity member variables
57    //============================
58
59    private static final String INSTANCE_KEY_DIALOG_BUNDLE =
60            "com.android.settings.wifi.WifiSettings:dialogBundle";
61    /*
62     * We don't use Activity's dialog management because AlertDialog isn't fully
63     * able to change many of its features after it's been created, and the
64     * dialog management only creates once.
65     */
66    private Dialog mDialog;
67
68    private static final String KEY_ONLY_ACCESS_POINTS = "only_access_points";
69    private static final String KEY_ADD_OTHER_NETWORK = "add_other_network";
70
71    private static final int CONTEXT_MENU_ID_CONNECT = Menu.FIRST;
72    private static final int CONTEXT_MENU_ID_FORGET = Menu.FIRST + 1;
73    private static final int CONTEXT_MENU_ID_CHANGE_PASSWORD = Menu.FIRST + 2;
74
75    private static final int MENU_ID_SCAN = Menu.FIRST;
76    private static final int MENU_ID_ADVANCED = Menu.FIRST + 1;
77
78    private static final String KEY_WIFI_ENABLED = "wifi_enabled";
79    private static final String KEY_OPEN_NETWORK_NOTIFICATIONS_ENABLED =
80            "open_network_notifications_enabled";
81    private static final String KEY_ACCESS_POINTS = "access_points";
82
83    private ProgressCategory mApCategory;
84    private CheckBoxPreference mWifiEnabled;
85    private WifiEnabler mWifiEnabler;
86    private CheckBoxPreference mOpenNetworkNotificationsEnabled;
87    private Preference mAddOtherNetwork;
88
89    private WeakHashMap<AccessPointState, AccessPointPreference> mAps;
90
91    private KeyStore mKeyStore = KeyStore.getInstance();
92    private AccessPointState mResumeState = null;
93    private int mResumeMode;
94
95    //============================
96    // Wifi member variables
97    //============================
98
99    private WifiLayer mWifiLayer;
100
101    //============================
102    // Activity lifecycle
103    //============================
104
105    public WifiSettings() {
106        mAps = new WeakHashMap<AccessPointState, AccessPointPreference>();
107        mWifiLayer = new WifiLayer(this, this);
108    }
109
110    @Override
111    protected void onCreate(Bundle savedInstanceState) {
112        super.onCreate(savedInstanceState);
113
114        onCreatePreferences();
115        mWifiLayer.onCreate();
116
117        onCreatedWifi();
118        mWifiLayer.onCreatedCallback();
119    }
120
121    private int getPreferenceResource() {
122        if (getIntent().getBooleanExtra(KEY_ONLY_ACCESS_POINTS, false)) {
123            return R.xml.wifi_access_points;
124        } else {
125            return R.xml.wifi_settings;
126        }
127    }
128
129    /**
130     * Shouldn't have any dependency on the wifi layer.
131     */
132    private void onCreatePreferences() {
133        addPreferencesFromResource(getPreferenceResource());
134
135        final PreferenceScreen preferenceScreen = getPreferenceScreen();
136
137        mApCategory = (ProgressCategory) preferenceScreen.findPreference(KEY_ACCESS_POINTS);
138        // We don't want the ordering to be the order preferences are added,
139        // instead we want*:
140        //   1) preferred, visible APs
141        //   2) visible APs
142        //   3) preferred, APs out of range
143        //   * this ordering logic is in AccessPointPreference's compareTo
144        mApCategory.setOrderingAsAdded(false);
145
146        if (!getIntent().getBooleanExtra(KEY_ONLY_ACCESS_POINTS, false)) {
147            mWifiEnabled = (CheckBoxPreference) preferenceScreen.findPreference(KEY_WIFI_ENABLED);
148            mWifiEnabler = new WifiEnabler(this, (WifiManager) getSystemService(WIFI_SERVICE),
149                    mWifiEnabled);
150
151            mOpenNetworkNotificationsEnabled = (CheckBoxPreference) preferenceScreen
152                    .findPreference(KEY_OPEN_NETWORK_NOTIFICATIONS_ENABLED);
153            mOpenNetworkNotificationsEnabled.setChecked(Settings.Secure.getInt(getContentResolver(),
154                    Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1);
155        }
156
157        mAddOtherNetwork = preferenceScreen.findPreference(KEY_ADD_OTHER_NETWORK);
158
159        registerForContextMenu(getListView());
160    }
161
162    private void onCreatedWifi() {
163    }
164
165    @Override
166    protected void onResume() {
167        super.onResume();
168        mWifiLayer.onResume();
169        if (mWifiEnabler != null) {
170            mWifiEnabler.resume();
171        }
172        // do what we should have after keystore is unlocked.
173        if (mResumeState != null) {
174            if (mKeyStore.test() == KeyStore.NO_ERROR) {
175                showAccessPointDialog(mResumeState, mResumeMode);
176            }
177            mResumeMode = -1;
178            mResumeState = null;
179        } else {
180            if (mResumeMode == AccessPointDialog.MODE_CONFIGURE) {
181                if (mKeyStore.test() == KeyStore.NO_ERROR) {
182                    ((AccessPointDialog) mDialog).enableEnterpriseFields();
183                }
184            }
185        }
186    }
187
188    @Override
189    protected void onPause() {
190        super.onPause();
191        mWifiLayer.onPause();
192        if (mWifiEnabler != null) {
193            mWifiEnabler.pause();
194        }
195    }
196
197    @Override
198    protected void onDestroy() {
199        super.onDestroy();
200
201        if (mDialog != null) {
202            mDialog.dismiss();
203        }
204    }
205
206    @Override
207    public boolean onCreateOptionsMenu(Menu menu) {
208        super.onCreateOptionsMenu(menu);
209
210        menu.add(0, MENU_ID_SCAN, 0, R.string.scan_wifi)
211            .setIcon(R.drawable.ic_menu_scan_network);
212
213        menu.add(0, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
214            .setIcon(android.R.drawable.ic_menu_manage);
215
216        return true;
217    }
218
219    @Override
220    public boolean onOptionsItemSelected(MenuItem item) {
221        super.onOptionsItemSelected(item);
222
223        switch (item.getItemId()) {
224
225            case MENU_ID_SCAN:
226                mWifiLayer.attemptScan();
227                return true;
228
229            case MENU_ID_ADVANCED:
230                Intent intent = new Intent(this, AdvancedSettings.class);
231                startActivity(intent);
232                return true;
233
234            default:
235                return false;
236        }
237    }
238
239    @Override
240    protected void onSaveInstanceState(Bundle outState) {
241        super.onSaveInstanceState(outState);
242
243        if (mDialog != null) {
244            Bundle dialogBundle = mDialog.onSaveInstanceState();
245            outState.putBundle(INSTANCE_KEY_DIALOG_BUNDLE, dialogBundle);
246        }
247    }
248
249    @Override
250    protected void onRestoreInstanceState(Bundle state) {
251        super.onRestoreInstanceState(state);
252
253        Bundle dialogBundle = state.getBundle(INSTANCE_KEY_DIALOG_BUNDLE);
254        if (dialogBundle != null) {
255            mDialog = new AccessPointDialog(this, mWifiLayer);
256            mDialog.onRestoreInstanceState(dialogBundle);
257            showDialog(mDialog);
258        }
259    }
260
261    /**
262     * {@inheritDoc}
263     */
264    public void onDismiss(DialogInterface dialog) {
265        if (dialog == mDialog) {
266            mDialog = null;
267            mResumeMode = -1;
268        }
269    }
270
271    @Override
272    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
273        super.onCreateContextMenu(menu, v, menuInfo);
274
275        AccessPointState state = getStateFromMenuInfo(menuInfo);
276        if (state == null) {
277            return;
278        }
279        menu.setHeaderTitle(state.ssid);
280
281        if (state.isConnectable()) {
282            menu.add(0, CONTEXT_MENU_ID_CONNECT, 0, R.string.wifi_context_menu_connect);
283        }
284
285        if (state.isForgetable()) {
286            menu.add(0, CONTEXT_MENU_ID_FORGET, 1, R.string.wifi_context_menu_forget);
287
288            if (state.hasPassword()) {
289                menu.add(0, CONTEXT_MENU_ID_CHANGE_PASSWORD, 2,
290                        R.string.wifi_context_menu_change_password);
291            }
292        }
293    }
294
295    @Override
296    public boolean onContextItemSelected(MenuItem item) {
297
298        AccessPointState state = getStateFromMenuInfo(item.getMenuInfo());
299        if (state == null) {
300            return false;
301        }
302
303        switch (item.getItemId()) {
304
305            case CONTEXT_MENU_ID_CONNECT:
306                connectToNetwork(state);
307                return true;
308
309            case CONTEXT_MENU_ID_FORGET:
310                mWifiLayer.forgetNetwork(state);
311                return true;
312
313            case CONTEXT_MENU_ID_CHANGE_PASSWORD:
314                showAccessPointDialog(state, AccessPointDialog.MODE_CONFIGURE);
315                return true;
316
317            default:
318                return false;
319        }
320    }
321
322    /**
323     * Decides what needs to happen to connect to a particular access point. If
324     * it is secured and doesn't already have a password, it will bring up a
325     * password box. Otherwise it will just connect.
326     */
327    private void connectToNetwork(AccessPointState state) {
328        if (state.hasSecurity() && !state.hasPassword()) {
329            showAccessPointDialog(state, AccessPointDialog.MODE_INFO);
330        } else {
331            mWifiLayer.connectToNetwork(state);
332        }
333    }
334
335    private AccessPointState getStateFromMenuInfo(ContextMenuInfo menuInfo) {
336        if ((menuInfo == null) || !(menuInfo instanceof AdapterContextMenuInfo)) {
337            return null;
338        }
339
340        AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo;
341        Preference pref = (Preference) getPreferenceScreen().getRootAdapter().getItem(
342                adapterMenuInfo.position);
343        if (pref == null || !(pref instanceof AccessPointPreference)) {
344            return null;
345        }
346
347        return ((AccessPointPreference) pref).getAccessPointState();
348    }
349
350    //============================
351    // Preference callbacks
352    //============================
353
354    @Override
355    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
356        super.onPreferenceTreeClick(preferenceScreen, preference);
357
358        if (preference == mAddOtherNetwork) {
359            showAddOtherNetworkDialog();
360        } else if (preference == mOpenNetworkNotificationsEnabled) {
361            Settings.Secure.putInt(getContentResolver(),
362                Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
363                mOpenNetworkNotificationsEnabled.isChecked() ? 1 : 0);
364        } else if (preference instanceof AccessPointPreference) {
365            AccessPointState state = ((AccessPointPreference) preference).getAccessPointState();
366            showAccessPointDialog(state, AccessPointDialog.MODE_INFO);
367        }
368
369        return false;
370    }
371
372    //============================
373    // Wifi-related
374    //============================
375
376    public WifiLayer getWifiLayer() {
377        return mWifiLayer;
378    }
379
380    private void showAddOtherNetworkDialog() {
381        AccessPointDialog dialog = new AccessPointDialog(this, mWifiLayer);
382        dialog.setState(new AccessPointState(this));
383        dialog.setMode(AccessPointDialog.MODE_CONFIGURE);
384        dialog.setTitle(R.string.wifi_add_other_network);
385        dialog.setAutoSecurityAllowed(false);
386        mResumeMode = AccessPointDialog.MODE_CONFIGURE;
387        showDialog(dialog);
388    }
389
390    public void showAccessPointDialog(AccessPointState state, int mode) {
391        if (state.isEnterprise() && mKeyStore.test() != KeyStore.NO_ERROR) {
392            Credentials.getInstance().unlock(this);
393            mResumeState = state;
394            mResumeMode = mode;
395            return;
396        }
397        AccessPointDialog dialog = new AccessPointDialog(this, mWifiLayer);
398        dialog.setMode(mode);
399        dialog.setState(state);
400        showDialog(dialog);
401    }
402
403    private void showDialog(Dialog dialog) {
404        // Have only one dialog open at a time
405        if (mDialog != null) {
406            mDialog.dismiss();
407        }
408
409        mDialog = dialog;
410        if (dialog != null) {
411            dialog.setOnDismissListener(this);
412            dialog.show();
413        }
414    }
415
416    //============================
417    // Wifi callbacks
418    //============================
419
420    public void onError(int messageResId) {
421        Toast.makeText(this, messageResId, Toast.LENGTH_LONG).show();
422    }
423
424    public void onScanningStatusChanged(boolean started) {
425        mApCategory.setProgress(started);
426    }
427
428    public void onAccessPointSetChanged(AccessPointState ap, boolean added) {
429
430        AccessPointPreference pref = mAps.get(ap);
431
432        if (WifiLayer.LOGV) {
433            Log.v(TAG, "onAccessPointSetChanged with " + ap + " and "
434                    + (added ? "added" : "removed") + ", found pref " + pref);
435        }
436
437        if (added) {
438
439            if (pref == null) {
440                pref = new AccessPointPreference(this, ap);
441                mAps.put(ap, pref);
442            } else {
443                pref.setEnabled(true);
444            }
445
446            mApCategory.addPreference(pref);
447
448        } else {
449
450            mAps.remove(ap);
451
452            if (pref != null) {
453                mApCategory.removePreference(pref);
454            }
455
456        }
457    }
458
459    public void onAccessPointsStateChanged(boolean enabled) {
460        if (enabled) {
461            mApCategory.setEnabled(true);
462        } else {
463            mApCategory.removeAll();
464            mAps.clear();
465        }
466
467        mAddOtherNetwork.setEnabled(enabled);
468    }
469
470    public void onRetryPassword(AccessPointState ap) {
471
472        if ((mDialog != null) && mDialog.isShowing()) {
473            // If we're already showing a dialog, ignore this request
474            return;
475        }
476
477        showAccessPointDialog(ap, AccessPointDialog.MODE_RETRY_PASSWORD);
478    }
479
480}
481