WifiSettings.java revision 89403529adf7584ded2bdadf985fde12a346ec3b
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 static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
20import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
21
22import android.app.Activity;
23import android.app.AlertDialog;
24import android.app.AppGlobals;
25import android.app.Dialog;
26import android.app.admin.DevicePolicyManager;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.Intent;
30import android.content.pm.ApplicationInfo;
31import android.content.pm.IPackageManager;
32import android.content.pm.PackageManager;
33import android.content.pm.PackageManager.NameNotFoundException;
34import android.content.res.Resources;
35import android.content.res.TypedArray;
36import android.graphics.drawable.Drawable;
37import android.net.ConnectivityManager;
38import android.net.NetworkInfo;
39import android.net.NetworkInfo.State;
40import android.net.wifi.WifiConfiguration;
41import android.net.wifi.WifiManager;
42import android.net.wifi.WpsInfo;
43import android.nfc.NfcAdapter;
44import android.os.Bundle;
45import android.os.RemoteException;
46import android.os.UserHandle;
47import android.preference.Preference;
48import android.preference.PreferenceScreen;
49import android.text.Spannable;
50import android.text.style.TextAppearanceSpan;
51import android.util.Log;
52import android.view.ContextMenu;
53import android.view.ContextMenu.ContextMenuInfo;
54import android.view.Gravity;
55import android.view.Menu;
56import android.view.MenuInflater;
57import android.view.MenuItem;
58import android.view.View;
59import android.widget.AdapterView.AdapterContextMenuInfo;
60import android.widget.TextView;
61import android.widget.TextView.BufferType;
62import android.widget.Toast;
63
64import com.android.internal.logging.MetricsLogger;
65import com.android.settings.LinkifyUtils;
66import com.android.settings.R;
67import com.android.settings.RestrictedSettingsFragment;
68import com.android.settings.SettingsActivity;
69import com.android.settings.location.ScanningSettings;
70import com.android.settings.search.BaseSearchIndexProvider;
71import com.android.settings.search.Indexable;
72import com.android.settings.search.SearchIndexableRaw;
73import com.android.settingslib.wifi.AccessPoint;
74import com.android.settingslib.wifi.AccessPoint.AccessPointListener;
75import com.android.settingslib.wifi.WifiTracker;
76
77import java.util.ArrayList;
78import java.util.Collection;
79import java.util.List;
80
81/**
82 * Two types of UI are provided here.
83 *
84 * The first is for "usual Settings", appearing as any other Setup fragment.
85 *
86 * The second is for Setup Wizard, with a simplified interface that hides the action bar
87 * and menus.
88 */
89public class WifiSettings extends RestrictedSettingsFragment
90        implements DialogInterface.OnClickListener, Indexable, WifiTracker.WifiListener,
91        AccessPointListener {
92
93    private static final String TAG = "WifiSettings";
94
95    /* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST;
96    private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
97    private static final int MENU_ID_SAVED_NETWORK = Menu.FIRST + 2;
98    /* package */ static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 3;
99    private static final int MENU_ID_ADVANCED = Menu.FIRST + 4;
100    private static final int MENU_ID_SCAN = Menu.FIRST + 5;
101    private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
102    private static final int MENU_ID_FORGET = Menu.FIRST + 7;
103    private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
104    private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
105    private static final int MENU_ID_APPS = Menu.FIRST + 10;
106
107    public static final int WIFI_DIALOG_ID = 1;
108    /* package */ static final int WPS_PBC_DIALOG_ID = 2;
109    private static final int WPS_PIN_DIALOG_ID = 3;
110    private static final int WRITE_NFC_DIALOG_ID = 6;
111
112    // Instance state keys
113    private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode";
114    private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
115
116    private static boolean savedNetworksExist;
117
118    protected WifiManager mWifiManager;
119    private WifiManager.ActionListener mConnectListener;
120    private WifiManager.ActionListener mSaveListener;
121    private WifiManager.ActionListener mForgetListener;
122
123    private WifiEnabler mWifiEnabler;
124    // An access point being editted is stored here.
125    private AccessPoint mSelectedAccessPoint;
126
127    private WifiDialog mDialog;
128    private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
129
130    private TextView mEmptyView;
131
132    private boolean showAppIcons = false;
133    private MenuItem showAppMenuItem = null;
134
135    // this boolean extra specifies whether to disable the Next button when not connected. Used by
136    // account creation outside of setup wizard.
137    private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
138    // This string extra specifies a network to open the connect dialog on, so the user can enter
139    // network credentials.  This is used by quick settings for secured networks.
140    private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
141
142    // should Next button only be enabled when we have a connection?
143    private boolean mEnableNextOnConnection;
144
145    // Save the dialog details
146    private boolean mDlgEdit;
147    private AccessPoint mDlgAccessPoint;
148    private Bundle mAccessPointSavedState;
149
150    private WifiTracker mWifiTracker;
151
152    /* End of "used in Wifi Setup context" */
153
154    public WifiSettings() {
155        super(DISALLOW_CONFIG_WIFI);
156    }
157
158    @Override
159    public void onActivityCreated(Bundle savedInstanceState) {
160        super.onActivityCreated(savedInstanceState);
161
162        mWifiTracker = new WifiTracker(getActivity(), this, true, true);
163        mWifiManager = mWifiTracker.getManager();
164
165        mConnectListener = new WifiManager.ActionListener() {
166                                   @Override
167                                   public void onSuccess() {
168                                   }
169                                   @Override
170                                   public void onFailure(int reason) {
171                                       Activity activity = getActivity();
172                                       if (activity != null) {
173                                           Toast.makeText(activity,
174                                                R.string.wifi_failed_connect_message,
175                                                Toast.LENGTH_SHORT).show();
176                                       }
177                                   }
178                               };
179
180        mSaveListener = new WifiManager.ActionListener() {
181                                @Override
182                                public void onSuccess() {
183                                }
184                                @Override
185                                public void onFailure(int reason) {
186                                    Activity activity = getActivity();
187                                    if (activity != null) {
188                                        Toast.makeText(activity,
189                                            R.string.wifi_failed_save_message,
190                                            Toast.LENGTH_SHORT).show();
191                                    }
192                                }
193                            };
194
195        mForgetListener = new WifiManager.ActionListener() {
196                                   @Override
197                                   public void onSuccess() {
198                                   }
199                                   @Override
200                                   public void onFailure(int reason) {
201                                       Activity activity = getActivity();
202                                       if (activity != null) {
203                                           Toast.makeText(activity,
204                                               R.string.wifi_failed_forget_message,
205                                               Toast.LENGTH_SHORT).show();
206                                       }
207                                   }
208                               };
209
210        if (savedInstanceState != null) {
211            mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
212            if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
213                mAccessPointSavedState =
214                    savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
215            }
216        }
217
218        // if we're supposed to enable/disable the Next button based on our current connection
219        // state, start it off in the right state
220        Intent intent = getActivity().getIntent();
221        mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
222
223        if (mEnableNextOnConnection) {
224            if (hasNextButton()) {
225                final ConnectivityManager connectivity = (ConnectivityManager)
226                        getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
227                if (connectivity != null) {
228                    NetworkInfo info = connectivity.getNetworkInfo(
229                            ConnectivityManager.TYPE_WIFI);
230                    changeNextButtonState(info.isConnected());
231                }
232            }
233        }
234
235        addPreferencesFromResource(R.xml.wifi_settings);
236
237        mEmptyView = initEmptyView();
238        registerForContextMenu(getListView());
239        setHasOptionsMenu(true);
240
241        if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
242            String ssid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
243            onAccessPointsChanged();
244            PreferenceScreen preferenceScreen = getPreferenceScreen();
245            for (int i = 0; i < preferenceScreen.getPreferenceCount(); i++) {
246                Preference preference = preferenceScreen.getPreference(i);
247                if (preference instanceof AccessPointPreference) {
248                    AccessPoint accessPoint = ((AccessPointPreference) preference).getAccessPoint();
249                    if (ssid.equals(accessPoint.getSsid()) && !accessPoint.isSaved()
250                            && accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
251                        onPreferenceTreeClick(preferenceScreen, preference);
252                        break;
253                    }
254                }
255            }
256        }
257    }
258
259    @Override
260    public void onDestroyView() {
261        super.onDestroyView();
262
263        if (mWifiEnabler != null) {
264            mWifiEnabler.teardownSwitchBar();
265        }
266    }
267
268    @Override
269    public void onStart() {
270        super.onStart();
271
272        // On/off switch is hidden for Setup Wizard (returns null)
273        mWifiEnabler = createWifiEnabler();
274    }
275
276    /**
277     * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
278     */
279    /* package */ WifiEnabler createWifiEnabler() {
280        final SettingsActivity activity = (SettingsActivity) getActivity();
281        return new WifiEnabler(activity, activity.getSwitchBar());
282    }
283
284    @Override
285    public void onResume() {
286        final Activity activity = getActivity();
287        super.onResume();
288        if (mWifiEnabler != null) {
289            mWifiEnabler.resume(activity);
290        }
291
292        mWifiTracker.startTracking();
293    }
294
295    @Override
296    public void onPause() {
297        super.onPause();
298        if (mWifiEnabler != null) {
299            mWifiEnabler.pause();
300        }
301
302        mWifiTracker.stopTracking();
303    }
304
305    @Override
306    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
307        // If the user is not allowed to configure wifi, do not show the menu.
308        if (isUiRestricted()) return;
309
310        addOptionsMenuItems(menu);
311        super.onCreateOptionsMenu(menu, inflater);
312    }
313
314    /**
315     * @param menu
316     */
317    void addOptionsMenuItems(Menu menu) {
318        final boolean wifiIsEnabled = mWifiTracker.isWifiEnabled();
319        TypedArray ta = getActivity().getTheme().obtainStyledAttributes(
320                new int[] {R.attr.ic_menu_add, R.attr.ic_wps});
321        menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
322                .setIcon(ta.getDrawable(0))
323                .setEnabled(wifiIsEnabled)
324                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
325        if (savedNetworksExist) {
326            menu.add(Menu.NONE, MENU_ID_SAVED_NETWORK, 0, R.string.wifi_saved_access_points_label)
327                    .setIcon(ta.getDrawable(0))
328                    .setEnabled(wifiIsEnabled)
329                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
330        }
331        menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh)
332               .setEnabled(wifiIsEnabled)
333               .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
334        menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
335                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
336        showAppMenuItem = menu.add(Menu.NONE, MENU_ID_APPS, 0, R.string.wifi_menu_apps);
337        showAppMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
338        ta.recycle();
339    }
340
341    @Override
342    protected int getMetricsCategory() {
343        return MetricsLogger.WIFI;
344    }
345
346    @Override
347    public void onSaveInstanceState(Bundle outState) {
348        super.onSaveInstanceState(outState);
349
350        // If the dialog is showing, save its state.
351        if (mDialog != null && mDialog.isShowing()) {
352            outState.putBoolean(SAVE_DIALOG_EDIT_MODE, mDlgEdit);
353            if (mDlgAccessPoint != null) {
354                mAccessPointSavedState = new Bundle();
355                mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
356                outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
357            }
358        }
359    }
360
361    @Override
362    public boolean onOptionsItemSelected(MenuItem item) {
363        // If the user is not allowed to configure wifi, do not handle menu selections.
364        if (isUiRestricted()) return false;
365
366        switch (item.getItemId()) {
367            case MENU_ID_WPS_PBC:
368                showDialog(WPS_PBC_DIALOG_ID);
369                return true;
370                /*
371            case MENU_ID_P2P:
372                if (getActivity() instanceof SettingsActivity) {
373                    ((SettingsActivity) getActivity()).startPreferencePanel(
374                            WifiP2pSettings.class.getCanonicalName(),
375                            null,
376                            R.string.wifi_p2p_settings_title, null,
377                            this, 0);
378                } else {
379                    startFragment(this, WifiP2pSettings.class.getCanonicalName(),
380                            R.string.wifi_p2p_settings_title, -1, null);
381                }
382                return true;
383                */
384            case MENU_ID_WPS_PIN:
385                showDialog(WPS_PIN_DIALOG_ID);
386                return true;
387            case MENU_ID_SCAN:
388                MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_FORCE_SCAN);
389                mWifiTracker.forceScan();
390                return true;
391            case MENU_ID_ADD_NETWORK:
392                if (mWifiTracker.isWifiEnabled()) {
393                    onAddNetworkPressed();
394                }
395                return true;
396            case MENU_ID_SAVED_NETWORK:
397                if (getActivity() instanceof SettingsActivity) {
398                    ((SettingsActivity) getActivity()).startPreferencePanel(
399                            SavedAccessPointsWifiSettings.class.getCanonicalName(), null,
400                            R.string.wifi_saved_access_points_titlebar, null, this, 0);
401                } else {
402                    startFragment(this, SavedAccessPointsWifiSettings.class.getCanonicalName(),
403                            R.string.wifi_saved_access_points_titlebar,
404                            -1 /* Do not request a result */, null);
405                }
406                return true;
407            case MENU_ID_ADVANCED:
408                if (getActivity() instanceof SettingsActivity) {
409                    ((SettingsActivity) getActivity()).startPreferencePanel(
410                            AdvancedWifiSettings.class.getCanonicalName(), null,
411                            R.string.wifi_advanced_titlebar, null, this, 0);
412                } else {
413                    startFragment(this, AdvancedWifiSettings.class.getCanonicalName(),
414                            R.string.wifi_advanced_titlebar, -1 /* Do not request a results */,
415                            null);
416                }
417                return true;
418            case MENU_ID_APPS:
419                showAppIcons = !showAppIcons;
420
421                if (showAppIcons) {
422                    showAppMenuItem.setTitle(R.string.wifi_menu_apps_strength);
423                } else {
424                    showAppMenuItem.setTitle(R.string.wifi_menu_apps);
425                }
426                onAccessPointsChanged();
427                return true;
428        }
429        return super.onOptionsItemSelected(item);
430    }
431
432    @Override
433    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
434        if (info instanceof AdapterContextMenuInfo) {
435            Preference preference = (Preference) getListView().getItemAtPosition(
436                    ((AdapterContextMenuInfo) info).position);
437
438            if (preference instanceof AccessPointPreference) {
439                mSelectedAccessPoint = ((AccessPointPreference) preference).getAccessPoint();
440                menu.setHeaderTitle(mSelectedAccessPoint.getSsid());
441                if (mSelectedAccessPoint.isConnectable()) {
442                    menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
443                }
444
445                WifiConfiguration config = mSelectedAccessPoint.getConfig();
446                // Device Owner created configs are uneditable
447                if (isCreatorDeviceOwner(getActivity(), config)) {
448                    return;
449                }
450
451                if (mSelectedAccessPoint.isSaved() || mSelectedAccessPoint.isEphemeral()) {
452                    // Allow forgetting a network if either the network is saved or ephemerally
453                    // connected. (In the latter case, "forget" blacklists the network so it won't
454                    // be used again, ephemerally).
455                    menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
456                }
457                if (mSelectedAccessPoint.isSaved()) {
458                    menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
459                    NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
460                    if (nfcAdapter != null && nfcAdapter.isEnabled() &&
461                            mSelectedAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
462                        // Only allow writing of NFC tags for password-protected networks.
463                        menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
464                    }
465                }
466            }
467        }
468    }
469
470    @Override
471    public boolean onContextItemSelected(MenuItem item) {
472        if (mSelectedAccessPoint == null) {
473            return super.onContextItemSelected(item);
474        }
475        switch (item.getItemId()) {
476            case MENU_ID_CONNECT: {
477                if (mSelectedAccessPoint.isSaved()) {
478                    connect(mSelectedAccessPoint.getConfig());
479                } else if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) {
480                    /** Bypass dialog for unsecured networks */
481                    mSelectedAccessPoint.generateOpenNetworkConfig();
482                    connect(mSelectedAccessPoint.getConfig());
483                } else {
484                    showDialog(mSelectedAccessPoint, true);
485                }
486                return true;
487            }
488            case MENU_ID_FORGET: {
489                forget();
490                return true;
491            }
492            case MENU_ID_MODIFY: {
493                showDialog(mSelectedAccessPoint, true);
494                return true;
495            }
496            case MENU_ID_WRITE_NFC:
497                showDialog(WRITE_NFC_DIALOG_ID);
498                return true;
499
500        }
501        return super.onContextItemSelected(item);
502    }
503
504    @Override
505    public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
506        if (preference instanceof AccessPointPreference) {
507            mSelectedAccessPoint = ((AccessPointPreference) preference).getAccessPoint();
508            /** Bypass dialog for unsecured, unsaved, and inactive networks */
509            if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE &&
510                    !mSelectedAccessPoint.isSaved() && !mSelectedAccessPoint.isActive()) {
511                mSelectedAccessPoint.generateOpenNetworkConfig();
512                if (!savedNetworksExist) {
513                    savedNetworksExist = true;
514                    getActivity().invalidateOptionsMenu();
515                }
516                connect(mSelectedAccessPoint.getConfig());
517            } else {
518                showDialog(mSelectedAccessPoint, false);
519            }
520        } else {
521            return super.onPreferenceTreeClick(screen, preference);
522        }
523        return true;
524    }
525
526    private void showDialog(AccessPoint accessPoint, boolean edit) {
527        WifiConfiguration config = accessPoint.getConfig();
528        if (isCreatorDeviceOwner(getActivity(), config) && accessPoint.isActive()) {
529            final int userId = UserHandle.getUserId(config.creatorUid);
530            final PackageManager pm = getActivity().getPackageManager();
531            final IPackageManager ipm = AppGlobals.getPackageManager();
532            String appName = pm.getNameForUid(config.creatorUid);
533            try {
534                final ApplicationInfo appInfo = ipm.getApplicationInfo(appName, /* flags */ 0,
535                        userId);
536                final CharSequence label = pm.getApplicationLabel(appInfo);
537                if (label != null) {
538                    appName = label.toString();
539                }
540            } catch (RemoteException e) {
541                // leave appName as packageName
542            }
543            final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
544            builder.setTitle(accessPoint.getSsid())
545                    .setMessage(getString(R.string.wifi_alert_lockdown_by_device_owner,
546                            appName))
547                    .setPositiveButton(android.R.string.ok, null)
548                    .show();
549            return;
550        }
551
552        if (mDialog != null) {
553            removeDialog(WIFI_DIALOG_ID);
554            mDialog = null;
555        }
556
557        // Save the access point and edit mode
558        mDlgAccessPoint = accessPoint;
559        mDlgEdit = edit;
560
561        showDialog(WIFI_DIALOG_ID);
562    }
563
564    @Override
565    public Dialog onCreateDialog(int dialogId) {
566        switch (dialogId) {
567            case WIFI_DIALOG_ID:
568                AccessPoint ap = mDlgAccessPoint; // For manual launch
569                if (ap == null) { // For re-launch from saved state
570                    if (mAccessPointSavedState != null) {
571                        ap = new AccessPoint(getActivity(), mAccessPointSavedState);
572                        // For repeated orientation changes
573                        mDlgAccessPoint = ap;
574                        // Reset the saved access point data
575                        mAccessPointSavedState = null;
576                    }
577                }
578                // If it's null, fine, it's for Add Network
579                mSelectedAccessPoint = ap;
580                mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit,
581                        /* no hide submit/connect */ false,
582                        /* hide forget if config locked down */ isCreatorDeviceOwner(getActivity(),
583                                ap.getConfig()));
584                return mDialog;
585            case WPS_PBC_DIALOG_ID:
586                return new WpsDialog(getActivity(), WpsInfo.PBC);
587            case WPS_PIN_DIALOG_ID:
588                return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
589            case WRITE_NFC_DIALOG_ID:
590                if (mSelectedAccessPoint != null) {
591                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
592                            getActivity(), mSelectedAccessPoint, mWifiManager);
593                    return mWifiToNfcDialog;
594                }
595
596        }
597        return super.onCreateDialog(dialogId);
598    }
599
600    /**
601     * Shows the latest access points available with supplemental information like
602     * the strength of network and the security for it.
603     */
604    @Override
605    public void onAccessPointsChanged() {
606        // Safeguard from some delayed event handling
607        if (getActivity() == null) return;
608
609        if (isUiRestricted()) {
610            addMessagePreference(R.string.wifi_empty_list_user_restricted);
611            return;
612        }
613        final int wifiState = mWifiManager.getWifiState();
614
615        switch (wifiState) {
616            case WifiManager.WIFI_STATE_ENABLED:
617                // AccessPoints are automatically sorted with TreeSet.
618                final Collection<AccessPoint> accessPoints =
619                        mWifiTracker.getAccessPoints();
620                getPreferenceScreen().removeAll();
621                if (accessPoints.size() == 0) {
622                    addMessagePreference(R.string.wifi_empty_list_wifi_on);
623                }
624
625                for (AccessPoint accessPoint : accessPoints) {
626                    // Ignore access points that are out of range.
627                    if (accessPoint.getLevel() != -1) {
628                        AccessPointPreference preference = new AccessPointPreference(accessPoint,
629                                getActivity());
630                        if (showAppIcons) {
631                            preference.showAppIcon();
632                        }
633
634                        getPreferenceScreen().addPreference(preference);
635                        accessPoint.setListener(this);
636                    }
637                }
638                break;
639
640            case WifiManager.WIFI_STATE_ENABLING:
641                getPreferenceScreen().removeAll();
642                break;
643
644            case WifiManager.WIFI_STATE_DISABLING:
645                addMessagePreference(R.string.wifi_stopping);
646                break;
647
648            case WifiManager.WIFI_STATE_DISABLED:
649                setOffMessage();
650                break;
651        }
652        // Update "Saved Networks" menu option.
653        if (savedNetworksExist != mWifiTracker.doSavedNetworksExist()) {
654            savedNetworksExist = !savedNetworksExist;
655            getActivity().invalidateOptionsMenu();
656        }
657    }
658
659    protected TextView initEmptyView() {
660        TextView emptyView = (TextView) getActivity().findViewById(android.R.id.empty);
661        emptyView.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
662        getListView().setEmptyView(emptyView);
663        return emptyView;
664    }
665
666    private void setOffMessage() {
667        if (mEmptyView == null) {
668            return;
669        }
670
671        final CharSequence briefText = getText(R.string.wifi_empty_list_wifi_off);
672        if (isUiRestricted()) {
673            // Show only the brief text if the user is not allowed to configure scanning settings.
674            mEmptyView.setText(briefText, BufferType.SPANNABLE);
675        } else {
676            // Append the description of scanning settings with link.
677            final StringBuilder contentBuilder = new StringBuilder();
678            contentBuilder.append(briefText);
679            contentBuilder.append("\n\n");
680            contentBuilder.append(getText(R.string.wifi_scan_notify_text));
681            LinkifyUtils.linkify(mEmptyView, contentBuilder, new LinkifyUtils.OnClickListener() {
682                @Override
683                public void onClick() {
684                    final SettingsActivity activity =
685                            (SettingsActivity) WifiSettings.this.getActivity();
686                    activity.startPreferencePanel(ScanningSettings.class.getName(), null,
687                            R.string.location_scanning_screen_title, null, null, 0);
688                }
689            });
690        }
691        // Embolden and enlarge the brief description anyway.
692        Spannable boldSpan = (Spannable) mEmptyView.getText();
693        boldSpan.setSpan(
694                new TextAppearanceSpan(getActivity(), android.R.style.TextAppearance_Medium), 0,
695                briefText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
696        getPreferenceScreen().removeAll();
697    }
698
699    private void addMessagePreference(int messageId) {
700        if (mEmptyView != null) mEmptyView.setText(messageId);
701        getPreferenceScreen().removeAll();
702    }
703
704    @Override
705    public void onWifiStateChanged(int state) {
706        Activity activity = getActivity();
707        if (activity != null) {
708            activity.invalidateOptionsMenu();
709        }
710
711        switch (state) {
712            case WifiManager.WIFI_STATE_ENABLING:
713                addMessagePreference(R.string.wifi_starting);
714                break;
715
716            case WifiManager.WIFI_STATE_DISABLED:
717                setOffMessage();
718                break;
719        }
720    }
721
722    @Override
723    public void onConnectedChanged() {
724        changeNextButtonState(mWifiTracker.isConnected());
725    }
726
727    /**
728     * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
729     * Wifi setup screens, not in usual wifi settings screen.
730     *
731     * @param enabled true when the device is connected to a wifi network.
732     */
733    private void changeNextButtonState(boolean enabled) {
734        if (mEnableNextOnConnection && hasNextButton()) {
735            getNextButton().setEnabled(enabled);
736        }
737    }
738
739    @Override
740    public void onClick(DialogInterface dialogInterface, int button) {
741        if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
742            forget();
743        } else if (button == WifiDialog.BUTTON_SUBMIT) {
744            if (mDialog != null) {
745                submit(mDialog.getController());
746            }
747        }
748    }
749
750    /* package */ void submit(WifiConfigController configController) {
751
752        final WifiConfiguration config = configController.getConfig();
753
754        if (config == null) {
755            if (mSelectedAccessPoint != null
756                    && mSelectedAccessPoint.isSaved()) {
757                connect(mSelectedAccessPoint.getConfig());
758            }
759        } else if (config.networkId != INVALID_NETWORK_ID) {
760            if (mSelectedAccessPoint != null) {
761                mWifiManager.save(config, mSaveListener);
762            }
763        } else {
764            if (configController.isEdit()) {
765                mWifiManager.save(config, mSaveListener);
766            } else {
767                connect(config);
768            }
769        }
770
771        mWifiTracker.resumeScanning();
772    }
773
774    /* package */ void forget() {
775        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_FORGET);
776        if (!mSelectedAccessPoint.isSaved()) {
777            if (mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
778                // Network is active but has no network ID - must be ephemeral.
779                mWifiManager.disableEphemeralNetwork(
780                        AccessPoint.convertToQuotedString(mSelectedAccessPoint.getSsid()));
781            } else {
782                // Should not happen, but a monkey seems to trigger it
783                Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
784                return;
785            }
786        } else {
787            mWifiManager.forget(mSelectedAccessPoint.getConfig().networkId, mForgetListener);
788        }
789
790        mWifiTracker.resumeScanning();
791
792        // We need to rename/replace "Next" button in wifi setup context.
793        changeNextButtonState(false);
794    }
795
796    protected void connect(final WifiConfiguration config) {
797        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_CONNECT);
798        mWifiManager.connect(config, mConnectListener);
799    }
800
801    protected void connect(final int networkId) {
802        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_CONNECT);
803        mWifiManager.connect(networkId, mConnectListener);
804    }
805
806    /**
807     * Refreshes acccess points and ask Wifi module to scan networks again.
808     */
809    /* package */ void refreshAccessPoints() {
810        mWifiTracker.resumeScanning();
811
812        getPreferenceScreen().removeAll();
813    }
814
815    /**
816     * Called when "add network" button is pressed.
817     */
818    /* package */ void onAddNetworkPressed() {
819        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_ADD_NETWORK);
820        // No exact access point is selected.
821        mSelectedAccessPoint = null;
822        showDialog(null, true);
823    }
824
825    /* package */ int getAccessPointsCount() {
826        final boolean wifiIsEnabled = mWifiTracker.isWifiEnabled();
827        if (wifiIsEnabled) {
828            return getPreferenceScreen().getPreferenceCount();
829        } else {
830            return 0;
831        }
832    }
833
834    /**
835     * Requests wifi module to pause wifi scan. May be ignored when the module is disabled.
836     */
837    /* package */ void pauseWifiScan() {
838        mWifiTracker.pauseScanning();
839    }
840
841    /**
842     * Requests wifi module to resume wifi scan. May be ignored when the module is disabled.
843     */
844    /* package */ void resumeWifiScan() {
845        mWifiTracker.resumeScanning();
846    }
847
848    @Override
849    protected int getHelpResource() {
850        return R.string.help_url_wifi;
851    }
852
853    @Override
854    public void onAccessPointChanged(AccessPoint accessPoint) {
855        ((AccessPointPreference) accessPoint.getTag()).refresh();
856    }
857
858    @Override
859    public void onLevelChanged(AccessPoint accessPoint) {
860        ((AccessPointPreference) accessPoint.getTag()).onLevelChanged();
861    }
862
863    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
864        new BaseSearchIndexProvider() {
865            @Override
866            public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
867                final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
868                final Resources res = context.getResources();
869
870                // Add fragment title
871                SearchIndexableRaw data = new SearchIndexableRaw(context);
872                data.title = res.getString(R.string.wifi_settings);
873                data.screenTitle = res.getString(R.string.wifi_settings);
874                data.keywords = res.getString(R.string.keywords_wifi);
875                result.add(data);
876
877                // Add saved Wi-Fi access points
878                final Collection<AccessPoint> accessPoints =
879                        WifiTracker.getCurrentAccessPoints(context, true, false);
880                for (AccessPoint accessPoint : accessPoints) {
881                    data = new SearchIndexableRaw(context);
882                    data.title = accessPoint.getSsid();
883                    data.screenTitle = res.getString(R.string.wifi_settings);
884                    data.enabled = enabled;
885                    result.add(data);
886                }
887
888                return result;
889            }
890        };
891
892    /**
893     * Returns the true if the app that created this config is the device owner of the device.
894     * @param config The WiFi config.
895     * @return creator package name or null if creator package is not device owner.
896     */
897    static boolean isCreatorDeviceOwner(Context context, WifiConfiguration config) {
898        if (config == null) {
899            return false;
900        }
901        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
902                Context.DEVICE_POLICY_SERVICE);
903        final String deviceOwnerPackageName = dpm.getDeviceOwner();
904        if (deviceOwnerPackageName == null) {
905            return false;
906        }
907        final PackageManager pm = context.getPackageManager();
908        try {
909            final int deviceOwnerUid = pm.getPackageUid(deviceOwnerPackageName,
910                    UserHandle.getUserId(config.creatorUid));
911            return deviceOwnerUid == config.creatorUid;
912        } catch (NameNotFoundException e) {
913            return false;
914        }
915    }
916}
917