WifiSettings.java revision e304b9f4f7f5bf07522fcc05615d172abd6c52a3
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("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
280        menu.setHeaderTitle(state.getHumanReadableSsid());
281
282        if (state.isConnectable()) {
283            menu.add(0, CONTEXT_MENU_ID_CONNECT, 0, R.string.wifi_context_menu_connect);
284        }
285
286        if (state.isForgetable()) {
287            menu.add(0, CONTEXT_MENU_ID_FORGET, 1, R.string.wifi_context_menu_forget);
288
289            if (state.hasPassword()) {
290                menu.add(0, CONTEXT_MENU_ID_CHANGE_PASSWORD, 2,
291                        R.string.wifi_context_menu_change_password);
292            }
293        }
294    }
295
296    @Override
297    public boolean onContextItemSelected(MenuItem item) {
298
299        AccessPointState state = getStateFromMenuInfo(item.getMenuInfo());
300        if (state == null) {
301            return false;
302        }
303
304        switch (item.getItemId()) {
305
306            case CONTEXT_MENU_ID_CONNECT:
307                connectToNetwork(state);
308                return true;
309
310            case CONTEXT_MENU_ID_FORGET:
311                mWifiLayer.forgetNetwork(state);
312                return true;
313
314            case CONTEXT_MENU_ID_CHANGE_PASSWORD:
315                showAccessPointDialog(state, AccessPointDialog.MODE_CONFIGURE);
316                return true;
317
318            default:
319                return false;
320        }
321    }
322
323    /**
324     * Decides what needs to happen to connect to a particular access point. If
325     * it is secured and doesn't already have a password, it will bring up a
326     * password box. Otherwise it will just connect.
327     */
328    private void connectToNetwork(AccessPointState state) {
329        if (state.hasSecurity() && !state.hasPassword()) {
330            showAccessPointDialog(state, AccessPointDialog.MODE_INFO);
331        } else {
332            mWifiLayer.connectToNetwork(state);
333        }
334    }
335
336    private AccessPointState getStateFromMenuInfo(ContextMenuInfo menuInfo) {
337        if ((menuInfo == null) || !(menuInfo instanceof AdapterContextMenuInfo)) {
338            return null;
339        }
340
341        AdapterContextMenuInfo adapterMenuInfo = (AdapterContextMenuInfo) menuInfo;
342        Preference pref = (Preference) getPreferenceScreen().getRootAdapter().getItem(
343                adapterMenuInfo.position);
344        if (pref == null || !(pref instanceof AccessPointPreference)) {
345            return null;
346        }
347
348        return ((AccessPointPreference) pref).getAccessPointState();
349    }
350
351    //============================
352    // Preference callbacks
353    //============================
354
355    @Override
356    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
357        super.onPreferenceTreeClick(preferenceScreen, preference);
358
359        if (preference == mAddOtherNetwork) {
360            showAddOtherNetworkDialog();
361        } else if (preference == mOpenNetworkNotificationsEnabled) {
362            Settings.Secure.putInt(getContentResolver(),
363                Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
364                mOpenNetworkNotificationsEnabled.isChecked() ? 1 : 0);
365        } else if (preference instanceof AccessPointPreference) {
366            AccessPointState state = ((AccessPointPreference) preference).getAccessPointState();
367            showAccessPointDialog(state, AccessPointDialog.MODE_INFO);
368        }
369
370        return false;
371    }
372
373    //============================
374    // Wifi-related
375    //============================
376
377    public WifiLayer getWifiLayer() {
378        return mWifiLayer;
379    }
380
381    private void showAddOtherNetworkDialog() {
382        AccessPointDialog dialog = new AccessPointDialog(this, mWifiLayer);
383        dialog.setState(new AccessPointState(this));
384        dialog.setMode(AccessPointDialog.MODE_CONFIGURE);
385        dialog.setTitle(R.string.wifi_add_other_network);
386        dialog.setAutoSecurityAllowed(false);
387        mResumeMode = AccessPointDialog.MODE_CONFIGURE;
388        showDialog(dialog);
389    }
390
391    public void showAccessPointDialog(AccessPointState state, int mode) {
392        if (state.isEnterprise() && mKeyStore.test() != KeyStore.NO_ERROR) {
393            Credentials.getInstance().unlock(this);
394            mResumeState = state;
395            mResumeMode = mode;
396            return;
397        }
398        AccessPointDialog dialog = new AccessPointDialog(this, mWifiLayer);
399        dialog.setMode(mode);
400        dialog.setState(state);
401        showDialog(dialog);
402    }
403
404    private void showDialog(Dialog dialog) {
405        // Have only one dialog open at a time
406        if (mDialog != null) {
407            mDialog.dismiss();
408        }
409
410        mDialog = dialog;
411        if (dialog != null) {
412            dialog.setOnDismissListener(this);
413            dialog.show();
414        }
415    }
416
417    //============================
418    // Wifi callbacks
419    //============================
420
421    public void onError(int messageResId) {
422        Toast.makeText(this, messageResId, Toast.LENGTH_LONG).show();
423    }
424
425    public void onScanningStatusChanged(boolean started) {
426        mApCategory.setProgress(started);
427    }
428
429    public void onAccessPointSetChanged(AccessPointState ap, boolean added) {
430
431        AccessPointPreference pref = mAps.get(ap);
432
433        if (WifiLayer.LOGV) {
434            Log.v(TAG, "onAccessPointSetChanged with " + ap + " and "
435                    + (added ? "added" : "removed") + ", found pref " + pref);
436        }
437
438        if (added) {
439
440            if (pref == null) {
441                pref = new AccessPointPreference(this, ap);
442                mAps.put(ap, pref);
443            } else {
444                pref.setEnabled(true);
445            }
446
447            mApCategory.addPreference(pref);
448
449        } else {
450
451            mAps.remove(ap);
452
453            if (pref != null) {
454                mApCategory.removePreference(pref);
455            }
456
457        }
458    }
459
460    public void onAccessPointsStateChanged(boolean enabled) {
461        if (enabled) {
462            mApCategory.setEnabled(true);
463        } else {
464            mApCategory.removeAll();
465            mAps.clear();
466        }
467
468        mAddOtherNetwork.setEnabled(enabled);
469    }
470
471    public void onRetryPassword(AccessPointState ap) {
472
473        if ((mDialog != null) && mDialog.isShowing()) {
474            // If we're already showing a dialog, ignore this request
475            return;
476        }
477
478        showAccessPointDialog(ap, AccessPointDialog.MODE_RETRY_PASSWORD);
479    }
480
481}
482