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.ActivityManager;
24import android.app.Dialog;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.DialogInterface;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.SharedPreferences;
31import android.content.res.Resources;
32import android.content.res.TypedArray;
33import android.location.LocationManager;
34import android.net.ConnectivityManager;
35import android.net.NetworkInfo;
36import android.net.NetworkInfo.DetailedState;
37import android.net.NetworkScoreManager;
38import android.net.NetworkScorerAppManager;
39import android.net.NetworkScorerAppManager.NetworkScorerAppData;
40import android.net.wifi.ScanResult;
41import android.net.wifi.WifiConfiguration;
42import android.net.wifi.WifiInfo;
43import android.net.wifi.WifiManager;
44import android.net.wifi.WpsInfo;
45import android.os.Build;
46import android.os.Bundle;
47import android.os.Handler;
48import android.os.Message;
49import android.os.UserHandle;
50import android.preference.Preference;
51import android.preference.PreferenceScreen;
52import android.util.Log;
53import android.view.ContextMenu;
54import android.view.ContextMenu.ContextMenuInfo;
55import android.view.LayoutInflater;
56import android.view.Menu;
57import android.view.MenuInflater;
58import android.view.MenuItem;
59import android.view.View;
60import android.view.View.OnClickListener;
61import android.widget.AdapterView.AdapterContextMenuInfo;
62import android.widget.Button;
63import android.widget.TextView;
64import android.widget.Toast;
65
66import com.android.settings.R;
67import com.android.settings.RestrictedSettingsFragment;
68import com.android.settings.SettingsActivity;
69import com.android.settings.search.BaseSearchIndexProvider;
70import com.android.settings.search.Indexable;
71import com.android.settings.search.SearchIndexableRaw;
72
73import java.util.ArrayList;
74import java.util.Collection;
75import java.util.Collections;
76import java.util.HashMap;
77import java.util.List;
78import java.util.concurrent.atomic.AtomicBoolean;
79
80/**
81 * Two types of UI are provided here.
82 *
83 * The first is for "usual Settings", appearing as any other Setup fragment.
84 *
85 * The second is for Setup Wizard, with a simplified interface that hides the action bar
86 * and menus.
87 */
88public class WifiSettings extends RestrictedSettingsFragment
89        implements DialogInterface.OnClickListener, Indexable  {
90
91    private static final String TAG = "WifiSettings";
92
93    private static final int REQUEST_ENABLE_WIFI_ASSISTANT = 1;
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
106    private static final String KEY_ASSISTANT_DISMISS_PLATFORM = "assistant_dismiss_platform";
107
108    public static final int WIFI_DIALOG_ID = 1;
109    /* package */ static final int WPS_PBC_DIALOG_ID = 2;
110    private static final int WPS_PIN_DIALOG_ID = 3;
111    private static final int WRITE_NFC_DIALOG_ID = 6;
112
113    // Combo scans can take 5-6s to complete - set to 10s.
114    private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
115
116    // Instance state keys
117    private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode";
118    private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
119
120    private static boolean savedNetworksExist;
121
122    private final IntentFilter mFilter;
123    private final BroadcastReceiver mReceiver;
124    private final Scanner mScanner;
125
126    /* package */ WifiManager mWifiManager;
127    private WifiManager.ActionListener mConnectListener;
128    private WifiManager.ActionListener mSaveListener;
129    private WifiManager.ActionListener mForgetListener;
130
131    private WifiEnabler mWifiEnabler;
132    // An access point being editted is stored here.
133    private AccessPoint mSelectedAccessPoint;
134
135    private DetailedState mLastState;
136    private WifiInfo mLastInfo;
137
138    private final AtomicBoolean mConnected = new AtomicBoolean(false);
139
140    private WifiDialog mDialog;
141    private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
142
143    private TextView mEmptyView;
144
145    // this boolean extra specifies whether to disable the Next button when not connected. Used by
146    // account creation outside of setup wizard.
147    private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
148
149    // should Next button only be enabled when we have a connection?
150    private boolean mEnableNextOnConnection;
151
152    // Save the dialog details
153    private boolean mDlgEdit;
154    private AccessPoint mDlgAccessPoint;
155    private Bundle mAccessPointSavedState;
156    private View mWifiAssistantCard;
157    private NetworkScorerAppData mWifiAssistantApp;
158
159    /** verbose logging flag. this flag is set thru developer debugging options
160     * and used so as to assist with in-the-field WiFi connectivity debugging  */
161    public static int mVerboseLogging = 0;
162
163    /* End of "used in Wifi Setup context" */
164
165    /** A restricted multimap for use in constructAccessPoints */
166    private static class Multimap<K,V> {
167        private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
168        /** retrieve a non-null list of values with key K */
169        List<V> getAll(K key) {
170            List<V> values = store.get(key);
171            return values != null ? values : Collections.<V>emptyList();
172        }
173
174        void put(K key, V val) {
175            List<V> curVals = store.get(key);
176            if (curVals == null) {
177                curVals = new ArrayList<V>(3);
178                store.put(key, curVals);
179            }
180            curVals.add(val);
181        }
182    }
183
184    private static class Scanner extends Handler {
185        private int mRetry = 0;
186        private WifiSettings mWifiSettings = null;
187
188        Scanner(WifiSettings wifiSettings) {
189            mWifiSettings = wifiSettings;
190        }
191
192        void resume() {
193            if (!hasMessages(0)) {
194                sendEmptyMessage(0);
195            }
196        }
197
198        void forceScan() {
199            removeMessages(0);
200            sendEmptyMessage(0);
201        }
202
203        void pause() {
204            mRetry = 0;
205            removeMessages(0);
206        }
207
208        @Override
209        public void handleMessage(Message message) {
210            if (mWifiSettings.mWifiManager.startScan()) {
211                mRetry = 0;
212            } else if (++mRetry >= 3) {
213                mRetry = 0;
214                Activity activity = mWifiSettings.getActivity();
215                if (activity != null) {
216                    Toast.makeText(activity, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
217                }
218                return;
219            }
220            sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
221        }
222    }
223
224    public WifiSettings() {
225        super(DISALLOW_CONFIG_WIFI);
226        mFilter = new IntentFilter();
227        mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
228        mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
229        mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
230        mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
231        mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
232        mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
233        mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
234        mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
235
236        mReceiver = new BroadcastReceiver() {
237            @Override
238            public void onReceive(Context context, Intent intent) {
239                handleEvent(intent);
240            }
241        };
242
243        mScanner = new Scanner(this);
244    }
245
246    @Override
247    public void onActivityCreated(Bundle savedInstanceState) {
248        super.onActivityCreated(savedInstanceState);
249
250        mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
251
252        mConnectListener = new WifiManager.ActionListener() {
253                                   @Override
254                                   public void onSuccess() {
255                                   }
256                                   @Override
257                                   public void onFailure(int reason) {
258                                       Activity activity = getActivity();
259                                       if (activity != null) {
260                                           Toast.makeText(activity,
261                                                R.string.wifi_failed_connect_message,
262                                                Toast.LENGTH_SHORT).show();
263                                       }
264                                   }
265                               };
266
267        mSaveListener = new WifiManager.ActionListener() {
268                                @Override
269                                public void onSuccess() {
270                                }
271                                @Override
272                                public void onFailure(int reason) {
273                                    Activity activity = getActivity();
274                                    if (activity != null) {
275                                        Toast.makeText(activity,
276                                            R.string.wifi_failed_save_message,
277                                            Toast.LENGTH_SHORT).show();
278                                    }
279                                }
280                            };
281
282        mForgetListener = new WifiManager.ActionListener() {
283                                   @Override
284                                   public void onSuccess() {
285                                   }
286                                   @Override
287                                   public void onFailure(int reason) {
288                                       Activity activity = getActivity();
289                                       if (activity != null) {
290                                           Toast.makeText(activity,
291                                               R.string.wifi_failed_forget_message,
292                                               Toast.LENGTH_SHORT).show();
293                                       }
294                                   }
295                               };
296
297        if (savedInstanceState != null) {
298            mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
299            if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
300                mAccessPointSavedState =
301                    savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
302            }
303        }
304
305        // if we're supposed to enable/disable the Next button based on our current connection
306        // state, start it off in the right state
307        Intent intent = getActivity().getIntent();
308        mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
309
310        if (mEnableNextOnConnection) {
311            if (hasNextButton()) {
312                final ConnectivityManager connectivity = (ConnectivityManager)
313                        getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
314                if (connectivity != null) {
315                    NetworkInfo info = connectivity.getNetworkInfo(
316                            ConnectivityManager.TYPE_WIFI);
317                    changeNextButtonState(info.isConnected());
318                }
319            }
320        }
321
322        addPreferencesFromResource(R.xml.wifi_settings);
323
324        prepareWifiAssistantCard();
325
326        mEmptyView = initEmptyView();
327        registerForContextMenu(getListView());
328        setHasOptionsMenu(true);
329    }
330
331    @Override
332    public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
333        if (requestCode == REQUEST_ENABLE_WIFI_ASSISTANT) {
334            if (resultCode == Activity.RESULT_OK) {
335                disableWifiAssistantCardUntilPlatformUpgrade();
336                getListView().removeHeaderView(mWifiAssistantCard);
337                mWifiAssistantApp = null;
338            }
339        } else {
340            super.onActivityResult(requestCode, resultCode, resultData);
341        }
342    }
343
344    @Override
345    public void onDestroyView() {
346        super.onDestroyView();
347
348        if (mWifiEnabler != null) {
349            mWifiEnabler.teardownSwitchBar();
350        }
351    }
352
353    @Override
354    public void onStart() {
355        super.onStart();
356
357        // On/off switch is hidden for Setup Wizard (returns null)
358        mWifiEnabler = createWifiEnabler();
359    }
360
361    /**
362     * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
363     */
364    /* package */ WifiEnabler createWifiEnabler() {
365        final SettingsActivity activity = (SettingsActivity) getActivity();
366        return new WifiEnabler(activity, activity.getSwitchBar());
367    }
368
369    @Override
370    public void onResume() {
371        final Activity activity = getActivity();
372        super.onResume();
373        if (mWifiEnabler != null) {
374            mWifiEnabler.resume(activity);
375        }
376
377        activity.registerReceiver(mReceiver, mFilter);
378        updateAccessPoints();
379    }
380
381    @Override
382    public void onPause() {
383        super.onPause();
384        if (mWifiEnabler != null) {
385            mWifiEnabler.pause();
386        }
387
388        getActivity().unregisterReceiver(mReceiver);
389        mScanner.pause();
390    }
391
392    @Override
393    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
394        // If the user is not allowed to configure wifi, do not show the menu.
395        if (isUiRestricted()) return;
396
397        addOptionsMenuItems(menu);
398        super.onCreateOptionsMenu(menu, inflater);
399    }
400
401    /**
402     * @param menu
403     */
404    void addOptionsMenuItems(Menu menu) {
405        final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
406        TypedArray ta = getActivity().getTheme().obtainStyledAttributes(
407                new int[] {R.attr.ic_menu_add, R.attr.ic_wps});
408        menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
409                .setIcon(ta.getDrawable(0))
410                .setEnabled(wifiIsEnabled)
411                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
412        if (savedNetworksExist) {
413            menu.add(Menu.NONE, MENU_ID_SAVED_NETWORK, 0, R.string.wifi_saved_access_points_label)
414                    .setIcon(ta.getDrawable(0))
415                    .setEnabled(wifiIsEnabled)
416                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
417        }
418        menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh)
419               .setEnabled(wifiIsEnabled)
420               .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
421        menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
422                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
423        ta.recycle();
424    }
425
426    @Override
427    public void onSaveInstanceState(Bundle outState) {
428        super.onSaveInstanceState(outState);
429
430        // If the dialog is showing, save its state.
431        if (mDialog != null && mDialog.isShowing()) {
432            outState.putBoolean(SAVE_DIALOG_EDIT_MODE, mDlgEdit);
433            if (mDlgAccessPoint != null) {
434                mAccessPointSavedState = new Bundle();
435                mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
436                outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
437            }
438        }
439    }
440
441    @Override
442    public boolean onOptionsItemSelected(MenuItem item) {
443        // If the user is not allowed to configure wifi, do not handle menu selections.
444        if (isUiRestricted()) return false;
445
446        switch (item.getItemId()) {
447            case MENU_ID_WPS_PBC:
448                showDialog(WPS_PBC_DIALOG_ID);
449                return true;
450                /*
451            case MENU_ID_P2P:
452                if (getActivity() instanceof SettingsActivity) {
453                    ((SettingsActivity) getActivity()).startPreferencePanel(
454                            WifiP2pSettings.class.getCanonicalName(),
455                            null,
456                            R.string.wifi_p2p_settings_title, null,
457                            this, 0);
458                } else {
459                    startFragment(this, WifiP2pSettings.class.getCanonicalName(),
460                            R.string.wifi_p2p_settings_title, -1, null);
461                }
462                return true;
463                */
464            case MENU_ID_WPS_PIN:
465                showDialog(WPS_PIN_DIALOG_ID);
466                return true;
467            case MENU_ID_SCAN:
468                if (mWifiManager.isWifiEnabled()) {
469                    mScanner.forceScan();
470                }
471                return true;
472            case MENU_ID_ADD_NETWORK:
473                if (mWifiManager.isWifiEnabled()) {
474                    onAddNetworkPressed();
475                }
476                return true;
477            case MENU_ID_SAVED_NETWORK:
478                if (getActivity() instanceof SettingsActivity) {
479                    ((SettingsActivity) getActivity()).startPreferencePanel(
480                            SavedAccessPointsWifiSettings.class.getCanonicalName(), null,
481                            R.string.wifi_saved_access_points_titlebar, null, this, 0);
482                } else {
483                    startFragment(this, SavedAccessPointsWifiSettings.class.getCanonicalName(),
484                            R.string.wifi_saved_access_points_titlebar,
485                            -1 /* Do not request a result */, null);
486                }
487                return true;
488            case MENU_ID_ADVANCED:
489                if (getActivity() instanceof SettingsActivity) {
490                    ((SettingsActivity) getActivity()).startPreferencePanel(
491                            AdvancedWifiSettings.class.getCanonicalName(), null,
492                            R.string.wifi_advanced_titlebar, null, this, 0);
493                } else {
494                    startFragment(this, AdvancedWifiSettings.class.getCanonicalName(),
495                            R.string.wifi_advanced_titlebar, -1 /* Do not request a results */,
496                            null);
497                }
498                return true;
499        }
500        return super.onOptionsItemSelected(item);
501    }
502
503    @Override
504    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
505        if (info instanceof AdapterContextMenuInfo) {
506            Preference preference = (Preference) getListView().getItemAtPosition(
507                    ((AdapterContextMenuInfo) info).position);
508
509            if (preference instanceof AccessPoint) {
510                mSelectedAccessPoint = (AccessPoint) preference;
511                menu.setHeaderTitle(mSelectedAccessPoint.ssid);
512                if (mSelectedAccessPoint.getLevel() != -1
513                        && mSelectedAccessPoint.getState() == null) {
514                    menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
515                }
516                if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
517                    if (ActivityManager.getCurrentUser() == UserHandle.USER_OWNER) {
518                        menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
519                    }
520                    menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
521
522                    if (mSelectedAccessPoint.security != AccessPoint.SECURITY_NONE) {
523                        // Only allow writing of NFC tags for password-protected networks.
524                        menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
525                    }
526                }
527            }
528        }
529    }
530
531    @Override
532    public boolean onContextItemSelected(MenuItem item) {
533        if (mSelectedAccessPoint == null) {
534            return super.onContextItemSelected(item);
535        }
536        switch (item.getItemId()) {
537            case MENU_ID_CONNECT: {
538                if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
539                    connect(mSelectedAccessPoint.networkId);
540                } else if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE) {
541                    /** Bypass dialog for unsecured networks */
542                    mSelectedAccessPoint.generateOpenNetworkConfig();
543                    connect(mSelectedAccessPoint.getConfig());
544                } else {
545                    showDialog(mSelectedAccessPoint, true);
546                }
547                return true;
548            }
549            case MENU_ID_FORGET: {
550                mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener);
551                return true;
552            }
553            case MENU_ID_MODIFY: {
554                showDialog(mSelectedAccessPoint, true);
555                return true;
556            }
557            case MENU_ID_WRITE_NFC:
558                showDialog(WRITE_NFC_DIALOG_ID);
559                return true;
560
561        }
562        return super.onContextItemSelected(item);
563    }
564
565    @Override
566    public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
567        if (preference instanceof AccessPoint) {
568            mSelectedAccessPoint = (AccessPoint) preference;
569            /** Bypass dialog for unsecured, unsaved networks */
570            if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE &&
571                    mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
572                mSelectedAccessPoint.generateOpenNetworkConfig();
573                if (!savedNetworksExist) {
574                    savedNetworksExist = true;
575                    getActivity().invalidateOptionsMenu();
576                }
577                connect(mSelectedAccessPoint.getConfig());
578            } else {
579                showDialog(mSelectedAccessPoint, false);
580            }
581        } else {
582            return super.onPreferenceTreeClick(screen, preference);
583        }
584        return true;
585    }
586
587    private void showDialog(AccessPoint accessPoint, boolean edit) {
588        if (mDialog != null) {
589            removeDialog(WIFI_DIALOG_ID);
590            mDialog = null;
591        }
592
593        // Save the access point and edit mode
594        mDlgAccessPoint = accessPoint;
595        mDlgEdit = edit;
596
597        showDialog(WIFI_DIALOG_ID);
598    }
599
600    @Override
601    public Dialog onCreateDialog(int dialogId) {
602        switch (dialogId) {
603            case WIFI_DIALOG_ID:
604                AccessPoint ap = mDlgAccessPoint; // For manual launch
605                if (ap == null) { // For re-launch from saved state
606                    if (mAccessPointSavedState != null) {
607                        ap = new AccessPoint(getActivity(), mAccessPointSavedState);
608                        // For repeated orientation changes
609                        mDlgAccessPoint = ap;
610                        // Reset the saved access point data
611                        mAccessPointSavedState = null;
612                    }
613                }
614                // If it's null, fine, it's for Add Network
615                mSelectedAccessPoint = ap;
616                mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit);
617                return mDialog;
618            case WPS_PBC_DIALOG_ID:
619                return new WpsDialog(getActivity(), WpsInfo.PBC);
620            case WPS_PIN_DIALOG_ID:
621                return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
622            case WRITE_NFC_DIALOG_ID:
623                if (mSelectedAccessPoint != null) {
624                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
625                            getActivity(), mSelectedAccessPoint, mWifiManager);
626                    return mWifiToNfcDialog;
627                }
628
629        }
630        return super.onCreateDialog(dialogId);
631    }
632
633    /**
634     * Shows the latest access points available with supplemental information like
635     * the strength of network and the security for it.
636     */
637    private void updateAccessPoints() {
638        // Safeguard from some delayed event handling
639        if (getActivity() == null) return;
640
641        if (isUiRestricted()) {
642            addMessagePreference(R.string.wifi_empty_list_user_restricted);
643            return;
644        }
645        final int wifiState = mWifiManager.getWifiState();
646
647        //when we update the screen, check if verbose logging has been turned on or off
648        mVerboseLogging = mWifiManager.getVerboseLoggingLevel();
649
650        switch (wifiState) {
651            case WifiManager.WIFI_STATE_ENABLED:
652                // AccessPoints are automatically sorted with TreeSet.
653                final Collection<AccessPoint> accessPoints =
654                        constructAccessPoints(getActivity(), mWifiManager, mLastInfo, mLastState);
655                getPreferenceScreen().removeAll();
656                if (accessPoints.size() == 0) {
657                    addMessagePreference(R.string.wifi_empty_list_wifi_on);
658                }
659
660                getListView().removeHeaderView(mWifiAssistantCard);
661                if (mWifiAssistantApp != null) {
662                    getListView().addHeaderView(mWifiAssistantCard);
663                }
664
665                for (AccessPoint accessPoint : accessPoints) {
666                    // Ignore access points that are out of range.
667                    if (accessPoint.getLevel() != -1) {
668                        getPreferenceScreen().addPreference(accessPoint);
669                    }
670                }
671                break;
672
673            case WifiManager.WIFI_STATE_ENABLING:
674                getPreferenceScreen().removeAll();
675                break;
676
677            case WifiManager.WIFI_STATE_DISABLING:
678                addMessagePreference(R.string.wifi_stopping);
679                break;
680
681            case WifiManager.WIFI_STATE_DISABLED:
682                setOffMessage();
683                break;
684        }
685    }
686
687    /**
688     * Returns the Network Scorer for the Wifi Assistant App.
689     */
690    public static NetworkScorerAppData getWifiAssistantApp(Context context) {
691        Collection<NetworkScorerAppData> scorers =
692                NetworkScorerAppManager.getAllValidScorers(context);
693
694        if (scorers.isEmpty()) {
695            return null;
696        }
697
698        // TODO: b/13780935 - Implement proper scorer selection. Rather than pick the first
699        // scorer on the system, we should allow the user to select one.
700        return scorers.iterator().next();
701    }
702
703    private void prepareWifiAssistantCard() {
704        if (getActivity() instanceof WifiPickerActivity) {
705            return;
706        }
707
708        if (NetworkScorerAppManager.getActiveScorer(getActivity()) != null) {
709            // A scorer is already enabled; don't show the card.
710            return;
711        }
712
713        Collection<NetworkScorerAppData> scorers =
714                NetworkScorerAppManager.getAllValidScorers(getActivity());
715        if (scorers.isEmpty()) {
716            // No scorers are available to enable; don't show the card.
717            return;
718        }
719
720        SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences();
721        int lastDismissPlatform = sharedPreferences.getInt(KEY_ASSISTANT_DISMISS_PLATFORM, 0);
722
723        if (Build.VERSION.SDK_INT <= lastDismissPlatform) {
724            // User has dismissed the Wi-Fi assistant card on this SDK release. Suppress the card
725            // until the next major platform upgrade.
726            return;
727        }
728
729        // TODO: b/13780935 - Implement proper scorer selection. Rather than pick the first
730        // scorer on the system, we should allow the user to select one.
731        mWifiAssistantApp = scorers.iterator().next();
732
733        if (mWifiAssistantCard == null) {
734            mWifiAssistantCard = LayoutInflater.from(getActivity())
735                    .inflate(R.layout.wifi_assistant_card, getListView(), false);
736            Button setup = (Button) mWifiAssistantCard.findViewById(R.id.setup);
737            Button noThanks = (Button) mWifiAssistantCard.findViewById(R.id.no_thanks_button);
738            TextView assistantText =
739                (TextView) mWifiAssistantCard.findViewById(R.id.wifi_assistant_text);
740            assistantText.setText(getResources().getString(
741                    R.string.wifi_assistant_title_message, mWifiAssistantApp.mScorerName));
742
743            if (setup != null && noThanks != null) {
744                setup.setOnClickListener(new OnClickListener() {
745                    @Override
746                    public void onClick(View v) {
747                        Intent intent = new Intent();
748                        if (mWifiAssistantApp.mConfigurationActivityClassName != null) {
749                            // App has a custom configuration activity; launch that.
750                            // This custom activity will be responsible for launching the system
751                            // dialog.
752                            intent.setClassName(mWifiAssistantApp.mPackageName,
753                                    mWifiAssistantApp.mConfigurationActivityClassName);
754                        } else {
755                            // Fall back on the system dialog.
756                            intent.setAction(NetworkScoreManager.ACTION_CHANGE_ACTIVE);
757                            intent.putExtra(NetworkScoreManager.EXTRA_PACKAGE_NAME,
758                                    mWifiAssistantApp.mPackageName);
759                        }
760                        startActivityForResult(intent, REQUEST_ENABLE_WIFI_ASSISTANT);
761                    }
762                });
763
764                noThanks.setOnClickListener(new OnClickListener() {
765                    @Override
766                    public void onClick(View v) {
767                        disableWifiAssistantCardUntilPlatformUpgrade();
768                        getListView().removeHeaderView(mWifiAssistantCard);
769                        mWifiAssistantApp = null;
770                    }
771                });
772            }
773        }
774    }
775
776    private void disableWifiAssistantCardUntilPlatformUpgrade() {
777        SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences();
778        SharedPreferences.Editor editor = sharedPreferences.edit();
779        editor.putInt(KEY_ASSISTANT_DISMISS_PLATFORM, Build.VERSION.SDK_INT);
780        editor.apply();
781    }
782
783    protected TextView initEmptyView() {
784        TextView emptyView = (TextView) getActivity().findViewById(android.R.id.empty);
785        getListView().setEmptyView(emptyView);
786        return emptyView;
787    }
788
789    private void setOffMessage() {
790        if (mEmptyView != null) {
791            mEmptyView.setText(R.string.wifi_empty_list_wifi_off);
792            if (android.provider.Settings.Global.getInt(getActivity().getContentResolver(),
793                    android.provider.Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1) {
794                mEmptyView.append("\n\n");
795                int resId;
796                if (android.provider.Settings.Secure.isLocationProviderEnabled(
797                        getActivity().getContentResolver(), LocationManager.NETWORK_PROVIDER)) {
798                    resId = R.string.wifi_scan_notify_text_location_on;
799                } else {
800                    resId = R.string.wifi_scan_notify_text_location_off;
801                }
802                CharSequence charSeq = getText(resId);
803                mEmptyView.append(charSeq);
804            }
805        }
806        getPreferenceScreen().removeAll();
807    }
808
809    private void addMessagePreference(int messageId) {
810        if (mEmptyView != null) mEmptyView.setText(messageId);
811        getPreferenceScreen().removeAll();
812    }
813
814    /** Returns sorted list of access points */
815    private static List<AccessPoint> constructAccessPoints(Context context,
816            WifiManager wifiManager, WifiInfo lastInfo, DetailedState lastState) {
817        ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
818        /** Lookup table to more quickly update AccessPoints by only considering objects with the
819         * correct SSID.  Maps SSID -> List of AccessPoints with the given SSID.  */
820        Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
821
822        final List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks();
823        if (configs != null) {
824            // Update "Saved Networks" menu option.
825            if (savedNetworksExist != (configs.size() > 0)) {
826                savedNetworksExist = !savedNetworksExist;
827                if (context instanceof Activity) {
828                    ((Activity) context).invalidateOptionsMenu();
829                }
830            }
831            for (WifiConfiguration config : configs) {
832                if (config.selfAdded && config.numAssociation == 0) {
833                    continue;
834                }
835                AccessPoint accessPoint = new AccessPoint(context, config);
836                if (lastInfo != null && lastState != null) {
837                    accessPoint.update(lastInfo, lastState);
838                }
839                accessPoints.add(accessPoint);
840                apMap.put(accessPoint.ssid, accessPoint);
841            }
842        }
843
844        final List<ScanResult> results = wifiManager.getScanResults();
845        if (results != null) {
846            for (ScanResult result : results) {
847                // Ignore hidden and ad-hoc networks.
848                if (result.SSID == null || result.SSID.length() == 0 ||
849                        result.capabilities.contains("[IBSS]")) {
850                    continue;
851                }
852
853                boolean found = false;
854                for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
855                    if (accessPoint.update(result))
856                        found = true;
857                }
858                if (!found) {
859                    AccessPoint accessPoint = new AccessPoint(context, result);
860                    accessPoints.add(accessPoint);
861                    apMap.put(accessPoint.ssid, accessPoint);
862                }
863            }
864        }
865
866        // Pre-sort accessPoints to speed preference insertion
867        Collections.sort(accessPoints);
868        return accessPoints;
869    }
870
871    private void handleEvent(Intent intent) {
872        String action = intent.getAction();
873        if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
874            updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
875                    WifiManager.WIFI_STATE_UNKNOWN));
876        } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
877                WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
878                WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
879                updateAccessPoints();
880        } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
881            NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
882                    WifiManager.EXTRA_NETWORK_INFO);
883            mConnected.set(info.isConnected());
884            changeNextButtonState(info.isConnected());
885            updateAccessPoints();
886            updateConnectionState(info.getDetailedState());
887        } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
888            updateConnectionState(null);
889        }
890    }
891
892    private void updateConnectionState(DetailedState state) {
893        /* sticky broadcasts can call this when wifi is disabled */
894        if (!mWifiManager.isWifiEnabled()) {
895            mScanner.pause();
896            return;
897        }
898
899        if (state == DetailedState.OBTAINING_IPADDR) {
900            mScanner.pause();
901        } else {
902            mScanner.resume();
903        }
904
905        mLastInfo = mWifiManager.getConnectionInfo();
906        if (state != null) {
907            mLastState = state;
908        }
909
910        for (int i = getPreferenceScreen().getPreferenceCount() - 1; i >= 0; --i) {
911            // Maybe there's a WifiConfigPreference
912            Preference preference = getPreferenceScreen().getPreference(i);
913            if (preference instanceof AccessPoint) {
914                final AccessPoint accessPoint = (AccessPoint) preference;
915                accessPoint.update(mLastInfo, mLastState);
916            }
917        }
918    }
919
920    private void updateWifiState(int state) {
921        Activity activity = getActivity();
922        if (activity != null) {
923            activity.invalidateOptionsMenu();
924        }
925
926        switch (state) {
927            case WifiManager.WIFI_STATE_ENABLED:
928                mScanner.resume();
929                return; // not break, to avoid the call to pause() below
930
931            case WifiManager.WIFI_STATE_ENABLING:
932                addMessagePreference(R.string.wifi_starting);
933                break;
934
935            case WifiManager.WIFI_STATE_DISABLED:
936                setOffMessage();
937                break;
938        }
939
940        mLastInfo = null;
941        mLastState = null;
942        mScanner.pause();
943    }
944
945    /**
946     * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
947     * Wifi setup screens, not in usual wifi settings screen.
948     *
949     * @param enabled true when the device is connected to a wifi network.
950     */
951    private void changeNextButtonState(boolean enabled) {
952        if (mEnableNextOnConnection && hasNextButton()) {
953            getNextButton().setEnabled(enabled);
954        }
955    }
956
957    @Override
958    public void onClick(DialogInterface dialogInterface, int button) {
959        if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
960            forget();
961        } else if (button == WifiDialog.BUTTON_SUBMIT) {
962            if (mDialog != null) {
963                submit(mDialog.getController());
964            }
965        }
966    }
967
968    /* package */ void submit(WifiConfigController configController) {
969
970        final WifiConfiguration config = configController.getConfig();
971
972        if (config == null) {
973            if (mSelectedAccessPoint != null
974                    && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
975                connect(mSelectedAccessPoint.networkId);
976            }
977        } else if (config.networkId != INVALID_NETWORK_ID) {
978            if (mSelectedAccessPoint != null) {
979                mWifiManager.save(config, mSaveListener);
980            }
981        } else {
982            if (configController.isEdit()) {
983                mWifiManager.save(config, mSaveListener);
984            } else {
985                connect(config);
986            }
987        }
988
989        if (mWifiManager.isWifiEnabled()) {
990            mScanner.resume();
991        }
992        updateAccessPoints();
993    }
994
995    /* package */ void forget() {
996        if (mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
997            // Should not happen, but a monkey seems to trigger it
998            Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
999            return;
1000        }
1001
1002        mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener);
1003
1004        if (mWifiManager.isWifiEnabled()) {
1005            mScanner.resume();
1006        }
1007        updateAccessPoints();
1008
1009        // We need to rename/replace "Next" button in wifi setup context.
1010        changeNextButtonState(false);
1011    }
1012
1013    protected void connect(final WifiConfiguration config) {
1014        mWifiManager.connect(config, mConnectListener);
1015    }
1016
1017    protected void connect(final int networkId) {
1018        mWifiManager.connect(networkId, mConnectListener);
1019    }
1020
1021    /**
1022     * Refreshes acccess points and ask Wifi module to scan networks again.
1023     */
1024    /* package */ void refreshAccessPoints() {
1025        if (mWifiManager.isWifiEnabled()) {
1026            mScanner.resume();
1027        }
1028
1029        getPreferenceScreen().removeAll();
1030    }
1031
1032    /**
1033     * Called when "add network" button is pressed.
1034     */
1035    /* package */ void onAddNetworkPressed() {
1036        // No exact access point is selected.
1037        mSelectedAccessPoint = null;
1038        showDialog(null, true);
1039    }
1040
1041    /* package */ int getAccessPointsCount() {
1042        final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
1043        if (wifiIsEnabled) {
1044            return getPreferenceScreen().getPreferenceCount();
1045        } else {
1046            return 0;
1047        }
1048    }
1049
1050    /**
1051     * Requests wifi module to pause wifi scan. May be ignored when the module is disabled.
1052     */
1053    /* package */ void pauseWifiScan() {
1054        if (mWifiManager.isWifiEnabled()) {
1055            mScanner.pause();
1056        }
1057    }
1058
1059    /**
1060     * Requests wifi module to resume wifi scan. May be ignored when the module is disabled.
1061     */
1062    /* package */ void resumeWifiScan() {
1063        if (mWifiManager.isWifiEnabled()) {
1064            mScanner.resume();
1065        }
1066    }
1067
1068    @Override
1069    protected int getHelpResource() {
1070        return R.string.help_url_wifi;
1071    }
1072
1073    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
1074        new BaseSearchIndexProvider() {
1075            @Override
1076            public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
1077                final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
1078                final Resources res = context.getResources();
1079
1080                // Add fragment title
1081                SearchIndexableRaw data = new SearchIndexableRaw(context);
1082                data.title = res.getString(R.string.wifi_settings);
1083                data.screenTitle = res.getString(R.string.wifi_settings);
1084                data.keywords = res.getString(R.string.keywords_wifi);
1085                result.add(data);
1086
1087                // Add available Wi-Fi access points
1088                WifiManager wifiManager =
1089                        (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
1090                final Collection<AccessPoint> accessPoints =
1091                        constructAccessPoints(context, wifiManager, null, null);
1092                for (AccessPoint accessPoint : accessPoints) {
1093                    // We are indexing only the saved Wi-Fi networks.
1094                    if (accessPoint.getConfig() == null) continue;
1095                    data = new SearchIndexableRaw(context);
1096                    data.title = accessPoint.getTitle().toString();
1097                    data.screenTitle = res.getString(R.string.wifi_settings);
1098                    data.enabled = enabled;
1099                    result.add(data);
1100                }
1101
1102                return result;
1103            }
1104        };
1105}
1106