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