• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.settings.wifi;
18 
19 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
20 import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
21 
22 import android.annotation.NonNull;
23 import android.app.Activity;
24 import android.app.Dialog;
25 import android.content.ContentResolver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.res.Resources;
29 import android.net.ConnectivityManager;
30 import android.net.Network;
31 import android.net.NetworkInfo;
32 import android.net.NetworkInfo.State;
33 import android.net.NetworkRequest;
34 import android.net.wifi.WifiConfiguration;
35 import android.net.wifi.WifiManager;
36 import android.nfc.NfcAdapter;
37 import android.os.Bundle;
38 import android.os.Handler;
39 import android.os.Looper;
40 import android.os.PowerManager;
41 import android.provider.Settings;
42 import android.support.annotation.VisibleForTesting;
43 import android.support.v7.preference.Preference;
44 import android.support.v7.preference.PreferenceCategory;
45 import android.util.Log;
46 import android.view.ContextMenu;
47 import android.view.ContextMenu.ContextMenuInfo;
48 import android.view.Menu;
49 import android.view.MenuItem;
50 import android.view.View;
51 import android.widget.Toast;
52 
53 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
54 import com.android.settings.LinkifyUtils;
55 import com.android.settings.R;
56 import com.android.settings.RestrictedSettingsFragment;
57 import com.android.settings.SettingsActivity;
58 import com.android.settings.core.SubSettingLauncher;
59 import com.android.settings.dashboard.SummaryLoader;
60 import com.android.settings.location.ScanningSettings;
61 import com.android.settings.search.BaseSearchIndexProvider;
62 import com.android.settings.search.Indexable;
63 import com.android.settings.search.SearchIndexableRaw;
64 import com.android.settings.widget.SummaryUpdater.OnSummaryChangeListener;
65 import com.android.settings.widget.SwitchBarController;
66 import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
67 import com.android.settingslib.RestrictedLockUtils;
68 import com.android.settingslib.wifi.AccessPoint;
69 import com.android.settingslib.wifi.AccessPoint.AccessPointListener;
70 import com.android.settingslib.wifi.AccessPointPreference;
71 import com.android.settingslib.wifi.WifiTracker;
72 import com.android.settingslib.wifi.WifiTrackerFactory;
73 
74 import java.util.ArrayList;
75 import java.util.List;
76 
77 /**
78  * Two types of UI are provided here.
79  *
80  * The first is for "usual Settings", appearing as any other Setup fragment.
81  *
82  * The second is for Setup Wizard, with a simplified interface that hides the action bar
83  * and menus.
84  */
85 public class WifiSettings extends RestrictedSettingsFragment
86         implements Indexable, WifiTracker.WifiListener, AccessPointListener,
87         WifiDialog.WifiDialogListener {
88 
89     private static final String TAG = "WifiSettings";
90 
91     private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
92     private static final int MENU_ID_FORGET = Menu.FIRST + 7;
93     private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
94     private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
95 
96     public static final int WIFI_DIALOG_ID = 1;
97     private static final int WRITE_NFC_DIALOG_ID = 6;
98 
99     // Instance state keys
100     private static final String SAVE_DIALOG_MODE = "dialog_mode";
101     private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
102     private static final String SAVED_WIFI_NFC_DIALOG_STATE = "wifi_nfc_dlg_state";
103 
104     private static final String PREF_KEY_EMPTY_WIFI_LIST = "wifi_empty_list";
105     private static final String PREF_KEY_CONNECTED_ACCESS_POINTS = "connected_access_point";
106     private static final String PREF_KEY_ACCESS_POINTS = "access_points";
107     private static final String PREF_KEY_ADDITIONAL_SETTINGS = "additional_settings";
108     private static final String PREF_KEY_CONFIGURE_WIFI_SETTINGS = "configure_settings";
109     private static final String PREF_KEY_SAVED_NETWORKS = "saved_networks";
110 
isVerboseLoggingEnabled()111     private static boolean isVerboseLoggingEnabled() {
112         return WifiTracker.sVerboseLogging || Log.isLoggable(TAG, Log.VERBOSE);
113     }
114 
115     private final Runnable mUpdateAccessPointsRunnable = () -> {
116         updateAccessPointPreferences();
117     };
118     private final Runnable mHideProgressBarRunnable = () -> {
119         setProgressBarVisible(false);
120     };
121 
122     protected WifiManager mWifiManager;
123     private ConnectivityManager mConnectivityManager;
124     private WifiManager.ActionListener mConnectListener;
125     private WifiManager.ActionListener mSaveListener;
126     private WifiManager.ActionListener mForgetListener;
127     private CaptivePortalNetworkCallback mCaptivePortalNetworkCallback;
128 
129     /**
130      * The state of {@link #isUiRestricted()} at {@link #onCreate(Bundle)}}. This is neccesary to
131      * ensure that behavior is consistent if {@link #isUiRestricted()} changes. It could be changed
132      * by the Test DPC tool in AFW mode.
133      */
134     private boolean mIsRestricted;
135 
136     private WifiEnabler mWifiEnabler;
137     // An access point being edited is stored here.
138     private AccessPoint mSelectedAccessPoint;
139 
140     private WifiDialog mDialog;
141     private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
142 
143     private View mProgressHeader;
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     // This string extra specifies a network to open the connect dialog on, so the user can enter
149     // network credentials.  This is used by quick settings for secured networks, among other
150     // things.
151     public static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
152 
153     // should Next button only be enabled when we have a connection?
154     private boolean mEnableNextOnConnection;
155 
156     // Save the dialog details
157     private int mDialogMode;
158     private AccessPoint mDlgAccessPoint;
159     private Bundle mAccessPointSavedState;
160     private Bundle mWifiNfcDialogSavedState;
161 
162     private WifiTracker mWifiTracker;
163     private String mOpenSsid;
164 
165     private AccessPointPreference.UserBadgeCache mUserBadgeCache;
166 
167     private PreferenceCategory mConnectedAccessPointPreferenceCategory;
168     private PreferenceCategory mAccessPointsPreferenceCategory;
169     private PreferenceCategory mAdditionalSettingsPreferenceCategory;
170     private Preference mAddPreference;
171     private Preference mConfigureWifiSettingsPreference;
172     private Preference mSavedNetworksPreference;
173     private LinkablePreference mStatusMessagePreference;
174 
175     // For Search
176     public static final String DATA_KEY_REFERENCE = "main_toggle_wifi";
177 
178     /**
179      * Tracks whether the user initiated a connection via clicking in order to autoscroll to the
180      * network once connected.
181      */
182     private boolean mClickedConnect;
183 
184     /* End of "used in Wifi Setup context" */
185 
WifiSettings()186     public WifiSettings() {
187         super(DISALLOW_CONFIG_WIFI);
188     }
189 
190     @Override
onViewCreated(View view, Bundle savedInstanceState)191     public void onViewCreated(View view, Bundle savedInstanceState) {
192         super.onViewCreated(view, savedInstanceState);
193         final Activity activity = getActivity();
194         if (activity != null) {
195             mProgressHeader = setPinnedHeaderView(R.layout.wifi_progress_header)
196                     .findViewById(R.id.progress_bar_animation);
197             setProgressBarVisible(false);
198         }
199         ((SettingsActivity) activity).getSwitchBar().setSwitchBarText(
200                 R.string.wifi_settings_master_switch_title,
201                 R.string.wifi_settings_master_switch_title);
202     }
203 
204     @Override
onCreate(Bundle icicle)205     public void onCreate(Bundle icicle) {
206         super.onCreate(icicle);
207 
208         // TODO(b/37429702): Add animations and preference comparator back after initial screen is
209         // loaded (ODR).
210         setAnimationAllowed(false);
211 
212         addPreferences();
213 
214         mIsRestricted = isUiRestricted();
215     }
216 
addPreferences()217     private void addPreferences() {
218         addPreferencesFromResource(R.xml.wifi_settings);
219 
220         mConnectedAccessPointPreferenceCategory =
221                 (PreferenceCategory) findPreference(PREF_KEY_CONNECTED_ACCESS_POINTS);
222         mAccessPointsPreferenceCategory =
223                 (PreferenceCategory) findPreference(PREF_KEY_ACCESS_POINTS);
224         mAdditionalSettingsPreferenceCategory =
225                 (PreferenceCategory) findPreference(PREF_KEY_ADDITIONAL_SETTINGS);
226         mConfigureWifiSettingsPreference = findPreference(PREF_KEY_CONFIGURE_WIFI_SETTINGS);
227         mSavedNetworksPreference = findPreference(PREF_KEY_SAVED_NETWORKS);
228 
229         Context prefContext = getPrefContext();
230         mAddPreference = new Preference(prefContext);
231         mAddPreference.setIcon(R.drawable.ic_menu_add_inset);
232         mAddPreference.setTitle(R.string.wifi_add_network);
233         mStatusMessagePreference = new LinkablePreference(prefContext);
234 
235         mUserBadgeCache = new AccessPointPreference.UserBadgeCache(getPackageManager());
236     }
237 
238     @Override
onActivityCreated(Bundle savedInstanceState)239     public void onActivityCreated(Bundle savedInstanceState) {
240         super.onActivityCreated(savedInstanceState);
241 
242         mWifiTracker = WifiTrackerFactory.create(
243                 getActivity(), this, getLifecycle(), true, true);
244         mWifiManager = mWifiTracker.getManager();
245 
246         final Activity activity = getActivity();
247         if (activity != null) {
248             mConnectivityManager = getActivity().getSystemService(ConnectivityManager.class);
249         }
250 
251         mConnectListener = new WifiManager.ActionListener() {
252                                    @Override
253                                    public void onSuccess() {
254                                    }
255                                    @Override
256                                    public void onFailure(int reason) {
257                                        Activity activity = getActivity();
258                                        if (activity != null) {
259                                            Toast.makeText(activity,
260                                                 R.string.wifi_failed_connect_message,
261                                                 Toast.LENGTH_SHORT).show();
262                                        }
263                                    }
264                                };
265 
266         mSaveListener = new WifiManager.ActionListener() {
267                                 @Override
268                                 public void onSuccess() {
269                                 }
270                                 @Override
271                                 public void onFailure(int reason) {
272                                     Activity activity = getActivity();
273                                     if (activity != null) {
274                                         Toast.makeText(activity,
275                                             R.string.wifi_failed_save_message,
276                                             Toast.LENGTH_SHORT).show();
277                                     }
278                                 }
279                             };
280 
281         mForgetListener = new WifiManager.ActionListener() {
282                                    @Override
283                                    public void onSuccess() {
284                                    }
285                                    @Override
286                                    public void onFailure(int reason) {
287                                        Activity activity = getActivity();
288                                        if (activity != null) {
289                                            Toast.makeText(activity,
290                                                R.string.wifi_failed_forget_message,
291                                                Toast.LENGTH_SHORT).show();
292                                        }
293                                    }
294                                };
295 
296         if (savedInstanceState != null) {
297             mDialogMode = savedInstanceState.getInt(SAVE_DIALOG_MODE);
298             if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
299                 mAccessPointSavedState =
300                     savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
301             }
302 
303             if (savedInstanceState.containsKey(SAVED_WIFI_NFC_DIALOG_STATE)) {
304                 mWifiNfcDialogSavedState =
305                     savedInstanceState.getBundle(SAVED_WIFI_NFC_DIALOG_STATE);
306             }
307         }
308 
309         // if we're supposed to enable/disable the Next button based on our current connection
310         // state, start it off in the right state
311         Intent intent = getActivity().getIntent();
312         mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
313 
314         if (mEnableNextOnConnection) {
315             if (hasNextButton()) {
316                 final ConnectivityManager connectivity = (ConnectivityManager)
317                         getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
318                 if (connectivity != null) {
319                     NetworkInfo info = connectivity.getNetworkInfo(
320                             ConnectivityManager.TYPE_WIFI);
321                     changeNextButtonState(info.isConnected());
322                 }
323             }
324         }
325 
326         registerForContextMenu(getListView());
327         setHasOptionsMenu(true);
328 
329         if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
330             mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
331         }
332     }
333 
334     @Override
onDestroyView()335     public void onDestroyView() {
336         super.onDestroyView();
337 
338         if (mWifiEnabler != null) {
339             mWifiEnabler.teardownSwitchController();
340         }
341     }
342 
343     @Override
onStart()344     public void onStart() {
345         super.onStart();
346 
347         // On/off switch is hidden for Setup Wizard (returns null)
348         mWifiEnabler = createWifiEnabler();
349 
350         if (mIsRestricted) {
351             restrictUi();
352             return;
353         }
354 
355         onWifiStateChanged(mWifiManager.getWifiState());
356     }
357 
restrictUi()358     private void restrictUi() {
359         if (!isUiRestrictedByOnlyAdmin()) {
360             getEmptyTextView().setText(R.string.wifi_empty_list_user_restricted);
361         }
362         getPreferenceScreen().removeAll();
363     }
364 
365     /**
366      * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
367      */
createWifiEnabler()368     private WifiEnabler createWifiEnabler() {
369         final SettingsActivity activity = (SettingsActivity) getActivity();
370         return new WifiEnabler(activity, new SwitchBarController(activity.getSwitchBar()),
371             mMetricsFeatureProvider);
372     }
373 
374     @Override
onResume()375     public void onResume() {
376         final Activity activity = getActivity();
377         super.onResume();
378 
379         // Because RestrictedSettingsFragment's onResume potentially requests authorization,
380         // which changes the restriction state, recalculate it.
381         final boolean alreadyImmutablyRestricted = mIsRestricted;
382         mIsRestricted = isUiRestricted();
383         if (!alreadyImmutablyRestricted && mIsRestricted) {
384             restrictUi();
385         }
386 
387         if (mWifiEnabler != null) {
388             mWifiEnabler.resume(activity);
389         }
390     }
391 
392     @Override
onPause()393     public void onPause() {
394         super.onPause();
395         if (mWifiEnabler != null) {
396             mWifiEnabler.pause();
397         }
398     }
399 
400     @Override
onStop()401     public void onStop() {
402         getView().removeCallbacks(mUpdateAccessPointsRunnable);
403         getView().removeCallbacks(mHideProgressBarRunnable);
404         unregisterCaptivePortalNetworkCallback();
405         super.onStop();
406     }
407 
408     @Override
onActivityResult(int requestCode, int resultCode, Intent data)409     public void onActivityResult(int requestCode, int resultCode, Intent data) {
410         super.onActivityResult(requestCode, resultCode, data);
411 
412         final boolean formerlyRestricted = mIsRestricted;
413         mIsRestricted = isUiRestricted();
414         if (formerlyRestricted && !mIsRestricted
415                 && getPreferenceScreen().getPreferenceCount() == 0) {
416             // De-restrict the ui
417             addPreferences();
418         }
419     }
420 
421     @Override
getMetricsCategory()422     public int getMetricsCategory() {
423         return MetricsEvent.WIFI;
424     }
425 
426     @Override
onSaveInstanceState(Bundle outState)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.putInt(SAVE_DIALOG_MODE, mDialogMode);
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         if (mWifiToNfcDialog != null && mWifiToNfcDialog.isShowing()) {
441             Bundle savedState = new Bundle();
442             mWifiToNfcDialog.saveState(savedState);
443             outState.putBundle(SAVED_WIFI_NFC_DIALOG_STATE, savedState);
444         }
445     }
446 
447     @Override
onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info)448     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
449             Preference preference = (Preference) view.getTag();
450 
451             if (preference instanceof LongPressAccessPointPreference) {
452                 mSelectedAccessPoint =
453                         ((LongPressAccessPointPreference) preference).getAccessPoint();
454                 menu.setHeaderTitle(mSelectedAccessPoint.getSsid());
455                 if (mSelectedAccessPoint.isConnectable()) {
456                     menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
457                 }
458 
459                 WifiConfiguration config = mSelectedAccessPoint.getConfig();
460                 // Some configs are ineditable
461                 if (WifiUtils.isNetworkLockedDown(getActivity(), config)) {
462                     return;
463                 }
464 
465                 if (mSelectedAccessPoint.isSaved() || mSelectedAccessPoint.isEphemeral()) {
466                     // Allow forgetting a network if either the network is saved or ephemerally
467                     // connected. (In the latter case, "forget" blacklists the network so it won't
468                     // be used again, ephemerally).
469                     menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
470                 }
471                 if (mSelectedAccessPoint.isSaved()) {
472                     menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
473                     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
474                     if (nfcAdapter != null && nfcAdapter.isEnabled() &&
475                             mSelectedAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
476                         // Only allow writing of NFC tags for password-protected networks.
477                         menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
478                     }
479                 }
480             }
481     }
482 
483     @Override
onContextItemSelected(MenuItem item)484     public boolean onContextItemSelected(MenuItem item) {
485         if (mSelectedAccessPoint == null) {
486             return super.onContextItemSelected(item);
487         }
488         switch (item.getItemId()) {
489             case MENU_ID_CONNECT: {
490                 boolean isSavedNetwork = mSelectedAccessPoint.isSaved();
491                 if (isSavedNetwork) {
492                     connect(mSelectedAccessPoint.getConfig(), isSavedNetwork);
493                 } else if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) {
494                     /** Bypass dialog for unsecured networks */
495                     mSelectedAccessPoint.generateOpenNetworkConfig();
496                     connect(mSelectedAccessPoint.getConfig(), isSavedNetwork);
497                 } else {
498                     showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
499                 }
500                 return true;
501             }
502             case MENU_ID_FORGET: {
503                 forget();
504                 return true;
505             }
506             case MENU_ID_MODIFY: {
507                 showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_MODIFY);
508                 return true;
509             }
510             case MENU_ID_WRITE_NFC:
511                 showDialog(WRITE_NFC_DIALOG_ID);
512                 return true;
513 
514         }
515         return super.onContextItemSelected(item);
516     }
517 
518     @Override
onPreferenceTreeClick(Preference preference)519     public boolean onPreferenceTreeClick(Preference preference) {
520         // If the preference has a fragment set, open that
521         if (preference.getFragment() != null) {
522             preference.setOnPreferenceClickListener(null);
523             return super.onPreferenceTreeClick(preference);
524         }
525 
526         if (preference instanceof LongPressAccessPointPreference) {
527             mSelectedAccessPoint = ((LongPressAccessPointPreference) preference).getAccessPoint();
528             if (mSelectedAccessPoint == null) {
529                 return false;
530             }
531             if (mSelectedAccessPoint.isActive()) {
532                 return super.onPreferenceTreeClick(preference);
533             }
534             /**
535              * Bypass dialog and connect to unsecured networks, or previously connected saved
536              * networks, or Passpoint provided networks.
537              */
538             WifiConfiguration config = mSelectedAccessPoint.getConfig();
539             if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) {
540                 mSelectedAccessPoint.generateOpenNetworkConfig();
541                 connect(mSelectedAccessPoint.getConfig(), mSelectedAccessPoint.isSaved());
542             } else if (mSelectedAccessPoint.isSaved() && config != null
543                     && config.getNetworkSelectionStatus() != null
544                     && config.getNetworkSelectionStatus().getHasEverConnected()) {
545                 connect(config, true /* isSavedNetwork */);
546             } else if (mSelectedAccessPoint.isPasspoint()) {
547                 // Access point provided by an installed Passpoint provider, connect using
548                 // the associated config.
549                 connect(config, true /* isSavedNetwork */);
550             } else {
551                 showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
552             }
553         } else if (preference == mAddPreference) {
554             onAddNetworkPressed();
555         } else {
556             return super.onPreferenceTreeClick(preference);
557         }
558         return true;
559     }
560 
showDialog(AccessPoint accessPoint, int dialogMode)561     private void showDialog(AccessPoint accessPoint, int dialogMode) {
562         if (accessPoint != null) {
563             WifiConfiguration config = accessPoint.getConfig();
564             if (WifiUtils.isNetworkLockedDown(getActivity(), config) && accessPoint.isActive()) {
565                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(),
566                         RestrictedLockUtils.getDeviceOwner(getActivity()));
567                 return;
568             }
569         }
570 
571         if (mDialog != null) {
572             removeDialog(WIFI_DIALOG_ID);
573             mDialog = null;
574         }
575 
576         // Save the access point and edit mode
577         mDlgAccessPoint = accessPoint;
578         mDialogMode = dialogMode;
579 
580         showDialog(WIFI_DIALOG_ID);
581     }
582 
583     @Override
onCreateDialog(int dialogId)584     public Dialog onCreateDialog(int dialogId) {
585         switch (dialogId) {
586             case WIFI_DIALOG_ID:
587                 if (mDlgAccessPoint == null && mAccessPointSavedState == null) {
588                     // add new network
589                     mDialog = WifiDialog
590                             .createFullscreen(getActivity(), this, mDlgAccessPoint, mDialogMode);
591                 } else {
592                     // modify network
593                     if (mDlgAccessPoint == null) {
594                         // restore AP from save state
595                         mDlgAccessPoint = new AccessPoint(getActivity(), mAccessPointSavedState);
596                         // Reset the saved access point data
597                         mAccessPointSavedState = null;
598                     }
599                     mDialog = WifiDialog
600                             .createModal(getActivity(), this, mDlgAccessPoint, mDialogMode);
601                 }
602 
603                 mSelectedAccessPoint = mDlgAccessPoint;
604                 return mDialog;
605             case WRITE_NFC_DIALOG_ID:
606                 if (mSelectedAccessPoint != null) {
607                     mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
608                             getActivity(),
609                             mSelectedAccessPoint.getSecurity());
610                 } else if (mWifiNfcDialogSavedState != null) {
611                     mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(getActivity(),
612                             mWifiNfcDialogSavedState);
613                 }
614 
615                 return mWifiToNfcDialog;
616         }
617         return super.onCreateDialog(dialogId);
618     }
619 
620     @Override
getDialogMetricsCategory(int dialogId)621     public int getDialogMetricsCategory(int dialogId) {
622         switch (dialogId) {
623             case WIFI_DIALOG_ID:
624                 return MetricsEvent.DIALOG_WIFI_AP_EDIT;
625             case WRITE_NFC_DIALOG_ID:
626                 return MetricsEvent.DIALOG_WIFI_WRITE_NFC;
627             default:
628                 return 0;
629         }
630     }
631 
632     /**
633      * Called to indicate the list of AccessPoints has been updated and
634      * getAccessPoints should be called to get the latest information.
635      */
636     @Override
onAccessPointsChanged()637     public void onAccessPointsChanged() {
638         Log.d(TAG, "onAccessPointsChanged (WifiTracker) callback initiated");
639         updateAccessPointsDelayed();
640     }
641 
642     /**
643      * Updates access points from {@link WifiManager#getScanResults()}. Adds a delay to have
644      * progress bar displayed before starting to modify APs.
645      */
updateAccessPointsDelayed()646     private void updateAccessPointsDelayed() {
647         // Safeguard from some delayed event handling
648         if (getActivity() != null && !mIsRestricted && mWifiManager.isWifiEnabled()) {
649             final View view = getView();
650             final Handler handler = view.getHandler();
651             if (handler != null && handler.hasCallbacks(mUpdateAccessPointsRunnable)) {
652                 return;
653             }
654             setProgressBarVisible(true);
655             view.postDelayed(mUpdateAccessPointsRunnable, 300 /* delay milliseconds */);
656         }
657     }
658 
659     /** Called when the state of Wifi has changed. */
660     @Override
onWifiStateChanged(int state)661     public void onWifiStateChanged(int state) {
662         if (mIsRestricted) {
663             return;
664         }
665 
666         final int wifiState = mWifiManager.getWifiState();
667         switch (wifiState) {
668             case WifiManager.WIFI_STATE_ENABLED:
669                 updateAccessPointPreferences();
670                 break;
671 
672             case WifiManager.WIFI_STATE_ENABLING:
673                 removeConnectedAccessPointPreference();
674                 mAccessPointsPreferenceCategory.removeAll();
675                 addMessagePreference(R.string.wifi_starting);
676                 setProgressBarVisible(true);
677                 break;
678 
679             case WifiManager.WIFI_STATE_DISABLING:
680                 removeConnectedAccessPointPreference();
681                 mAccessPointsPreferenceCategory.removeAll();
682                 addMessagePreference(R.string.wifi_stopping);
683                 break;
684 
685             case WifiManager.WIFI_STATE_DISABLED:
686                 setOffMessage();
687                 setAdditionalSettingsSummaries();
688                 setProgressBarVisible(false);
689                 break;
690         }
691     }
692 
693     /**
694      * Called when the connection state of wifi has changed.
695      */
696     @Override
onConnectedChanged()697     public void onConnectedChanged() {
698         changeNextButtonState(mWifiTracker.isConnected());
699     }
700 
701     /** Helper method to return whether an AccessPoint is disabled due to a wrong password */
isDisabledByWrongPassword(AccessPoint accessPoint)702     private static boolean isDisabledByWrongPassword(AccessPoint accessPoint) {
703         WifiConfiguration config = accessPoint.getConfig();
704         if (config == null) {
705             return false;
706         }
707         WifiConfiguration.NetworkSelectionStatus networkStatus =
708                 config.getNetworkSelectionStatus();
709         if (networkStatus == null || networkStatus.isNetworkEnabled()) {
710             return false;
711         }
712         int reason = networkStatus.getNetworkSelectionDisableReason();
713         return WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD == reason;
714     }
715 
updateAccessPointPreferences()716     private void updateAccessPointPreferences() {
717         // in case state has changed
718         if (!mWifiManager.isWifiEnabled()) {
719             return;
720         }
721         // AccessPoints are sorted by the WifiTracker
722         final List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
723         if (isVerboseLoggingEnabled()) {
724             Log.i(TAG, "updateAccessPoints called for: " + accessPoints);
725         }
726 
727         boolean hasAvailableAccessPoints = false;
728         mAccessPointsPreferenceCategory.removePreference(mStatusMessagePreference);
729         cacheRemoveAllPrefs(mAccessPointsPreferenceCategory);
730 
731         int index =
732                 configureConnectedAccessPointPreferenceCategory(accessPoints) ? 1 : 0;
733         int numAccessPoints = accessPoints.size();
734         for (; index < numAccessPoints; index++) {
735             AccessPoint accessPoint = accessPoints.get(index);
736             // Ignore access points that are out of range.
737             if (accessPoint.isReachable()) {
738                 String key = accessPoint.getKey();
739                 hasAvailableAccessPoints = true;
740                 LongPressAccessPointPreference pref =
741                         (LongPressAccessPointPreference) getCachedPreference(key);
742                 if (pref != null) {
743                     pref.setOrder(index);
744                     continue;
745                 }
746                 LongPressAccessPointPreference preference =
747                         createLongPressAccessPointPreference(accessPoint);
748                 preference.setKey(key);
749                 preference.setOrder(index);
750                 if (mOpenSsid != null && mOpenSsid.equals(accessPoint.getSsidStr())
751                         && accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
752                     if (!accessPoint.isSaved() || isDisabledByWrongPassword(accessPoint)) {
753                         onPreferenceTreeClick(preference);
754                         mOpenSsid = null;
755                     }
756                 }
757                 mAccessPointsPreferenceCategory.addPreference(preference);
758                 accessPoint.setListener(WifiSettings.this);
759                 preference.refresh();
760             }
761         }
762         removeCachedPrefs(mAccessPointsPreferenceCategory);
763         mAddPreference.setOrder(index);
764         mAccessPointsPreferenceCategory.addPreference(mAddPreference);
765         setAdditionalSettingsSummaries();
766 
767         if (!hasAvailableAccessPoints) {
768             setProgressBarVisible(true);
769             Preference pref = new Preference(getPrefContext());
770             pref.setSelectable(false);
771             pref.setSummary(R.string.wifi_empty_list_wifi_on);
772             pref.setOrder(index++);
773             pref.setKey(PREF_KEY_EMPTY_WIFI_LIST);
774             mAccessPointsPreferenceCategory.addPreference(pref);
775         } else {
776             // Continuing showing progress bar for an additional delay to overlap with animation
777             getView().postDelayed(mHideProgressBarRunnable, 1700 /* delay millis */);
778         }
779     }
780 
781     @NonNull
createLongPressAccessPointPreference( AccessPoint accessPoint)782     private LongPressAccessPointPreference createLongPressAccessPointPreference(
783             AccessPoint accessPoint) {
784         return new LongPressAccessPointPreference(accessPoint, getPrefContext(), mUserBadgeCache,
785                 false /* forSavedNetworks */, R.drawable.ic_wifi_signal_0, this);
786     }
787 
788     @NonNull
createConnectedAccessPointPreference( AccessPoint accessPoint)789     private ConnectedAccessPointPreference createConnectedAccessPointPreference(
790             AccessPoint accessPoint) {
791         return new ConnectedAccessPointPreference(accessPoint, getPrefContext(), mUserBadgeCache,
792                 R.drawable.ic_wifi_signal_0, false /* forSavedNetworks */);
793     }
794 
795     /**
796      * Configure the ConnectedAccessPointPreferenceCategory and return true if the Category was
797      * shown.
798      */
configureConnectedAccessPointPreferenceCategory( List<AccessPoint> accessPoints)799     private boolean configureConnectedAccessPointPreferenceCategory(
800             List<AccessPoint> accessPoints) {
801         if (accessPoints.size() == 0) {
802             removeConnectedAccessPointPreference();
803             return false;
804         }
805 
806         AccessPoint connectedAp = accessPoints.get(0);
807         if (!connectedAp.isActive()) {
808             removeConnectedAccessPointPreference();
809             return false;
810         }
811 
812         // Is the preference category empty?
813         if (mConnectedAccessPointPreferenceCategory.getPreferenceCount() == 0) {
814             addConnectedAccessPointPreference(connectedAp);
815             return true;
816         }
817 
818         // Is the previous currently connected SSID different from the new one?
819         ConnectedAccessPointPreference preference =
820                 (ConnectedAccessPointPreference)
821                         (mConnectedAccessPointPreferenceCategory.getPreference(0));
822         // The AccessPoints need to be the same reference to ensure that updates are reflected
823         // in the UI.
824         if (preference.getAccessPoint() != connectedAp) {
825             removeConnectedAccessPointPreference();
826             addConnectedAccessPointPreference(connectedAp);
827             return true;
828         }
829 
830         // Else same AP is connected, simply refresh the connected access point preference
831         // (first and only access point in this category).
832         preference.refresh();
833         // Update any potential changes to the connected network and ensure that the callback is
834         // registered after an onStop lifecycle event.
835         registerCaptivePortalNetworkCallback(getCurrentWifiNetwork(), preference);
836         return true;
837     }
838 
839     /**
840      * Creates a Preference for the given {@link AccessPoint} and adds it to the
841      * {@link #mConnectedAccessPointPreferenceCategory}.
842      */
addConnectedAccessPointPreference(AccessPoint connectedAp)843     private void addConnectedAccessPointPreference(AccessPoint connectedAp) {
844         final ConnectedAccessPointPreference pref =
845                 createConnectedAccessPointPreference(connectedAp);
846         registerCaptivePortalNetworkCallback(getCurrentWifiNetwork(), pref);
847 
848         // Launch details page or captive portal on click.
849         pref.setOnPreferenceClickListener(
850                 preference -> {
851                     pref.getAccessPoint().saveWifiState(pref.getExtras());
852                     if (mCaptivePortalNetworkCallback != null
853                             && mCaptivePortalNetworkCallback.isCaptivePortal()) {
854                         mConnectivityManager.startCaptivePortalApp(
855                                 mCaptivePortalNetworkCallback.getNetwork());
856                     } else {
857                         launchNetworkDetailsFragment(pref);
858                     }
859                     return true;
860                 });
861 
862         pref.setOnGearClickListener(
863                 preference -> {
864                     pref.getAccessPoint().saveWifiState(pref.getExtras());
865                     launchNetworkDetailsFragment(pref);
866                 });
867 
868         pref.refresh();
869 
870         mConnectedAccessPointPreferenceCategory.addPreference(pref);
871         mConnectedAccessPointPreferenceCategory.setVisible(true);
872         if (mClickedConnect) {
873             mClickedConnect = false;
874             scrollToPreference(mConnectedAccessPointPreferenceCategory);
875         }
876     }
877 
registerCaptivePortalNetworkCallback( Network wifiNetwork, ConnectedAccessPointPreference pref)878     private void registerCaptivePortalNetworkCallback(
879             Network wifiNetwork, ConnectedAccessPointPreference pref) {
880         if (wifiNetwork == null || pref == null) {
881             Log.w(TAG, "Network or Preference were null when registering callback.");
882             return;
883         }
884 
885         if (mCaptivePortalNetworkCallback != null
886                 && mCaptivePortalNetworkCallback.isSameNetworkAndPreference(wifiNetwork, pref)) {
887             return;
888         }
889 
890         unregisterCaptivePortalNetworkCallback();
891 
892         mCaptivePortalNetworkCallback = new CaptivePortalNetworkCallback(wifiNetwork, pref);
893         mConnectivityManager.registerNetworkCallback(
894                 new NetworkRequest.Builder()
895                         .clearCapabilities()
896                         .addTransportType(TRANSPORT_WIFI)
897                         .build(),
898                 mCaptivePortalNetworkCallback,
899                 new Handler(Looper.getMainLooper()));
900     }
901 
unregisterCaptivePortalNetworkCallback()902     private void unregisterCaptivePortalNetworkCallback() {
903         if (mCaptivePortalNetworkCallback != null) {
904             try {
905                 mConnectivityManager.unregisterNetworkCallback(mCaptivePortalNetworkCallback);
906             } catch (RuntimeException e) {
907                 Log.e(TAG, "Unregistering CaptivePortalNetworkCallback failed.", e);
908             }
909             mCaptivePortalNetworkCallback = null;
910         }
911     }
912 
launchNetworkDetailsFragment(ConnectedAccessPointPreference pref)913     private void launchNetworkDetailsFragment(ConnectedAccessPointPreference pref) {
914         new SubSettingLauncher(getContext())
915                 .setTitle(R.string.pref_title_network_details)
916                 .setDestination(WifiNetworkDetailsFragment.class.getName())
917                 .setArguments(pref.getExtras())
918                 .setSourceMetricsCategory(getMetricsCategory())
919                 .launch();
920     }
921 
getCurrentWifiNetwork()922     private Network getCurrentWifiNetwork() {
923         return mWifiManager != null ? mWifiManager.getCurrentNetwork() : null;
924     }
925 
926     /** Removes all preferences and hide the {@link #mConnectedAccessPointPreferenceCategory}. */
removeConnectedAccessPointPreference()927     private void removeConnectedAccessPointPreference() {
928         mConnectedAccessPointPreferenceCategory.removeAll();
929         mConnectedAccessPointPreferenceCategory.setVisible(false);
930         unregisterCaptivePortalNetworkCallback();
931     }
932 
setAdditionalSettingsSummaries()933     private void setAdditionalSettingsSummaries() {
934         mAdditionalSettingsPreferenceCategory.addPreference(mConfigureWifiSettingsPreference);
935         mConfigureWifiSettingsPreference.setSummary(getString(
936                 isWifiWakeupEnabled()
937                         ? R.string.wifi_configure_settings_preference_summary_wakeup_on
938                         : R.string.wifi_configure_settings_preference_summary_wakeup_off));
939         int numSavedNetworks = mWifiTracker.getNumSavedNetworks();
940         if (numSavedNetworks > 0) {
941             mAdditionalSettingsPreferenceCategory.addPreference(mSavedNetworksPreference);
942             mSavedNetworksPreference.setSummary(
943                     getResources().getQuantityString(R.plurals.wifi_saved_access_points_summary,
944                             numSavedNetworks, numSavedNetworks));
945         } else {
946             mAdditionalSettingsPreferenceCategory.removePreference(mSavedNetworksPreference);
947         }
948     }
949 
isWifiWakeupEnabled()950     private boolean isWifiWakeupEnabled() {
951         PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
952         ContentResolver contentResolver = getContentResolver();
953         return Settings.Global.getInt(contentResolver,
954                         Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1
955                 && Settings.Global.getInt(contentResolver,
956                         Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1
957                 && Settings.Global.getInt(contentResolver,
958                         Settings.Global.AIRPLANE_MODE_ON, 0) == 0
959                 && !powerManager.isPowerSaveMode();
960     }
961 
setOffMessage()962     private void setOffMessage() {
963         final CharSequence title = getText(R.string.wifi_empty_list_wifi_off);
964         // Don't use WifiManager.isScanAlwaysAvailable() to check the Wi-Fi scanning mode. Instead,
965         // read the system settings directly. Because when the device is in Airplane mode, even if
966         // Wi-Fi scanning mode is on, WifiManager.isScanAlwaysAvailable() still returns "off".
967         final boolean wifiScanningMode = Settings.Global.getInt(getActivity().getContentResolver(),
968                 Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1;
969         final CharSequence description = wifiScanningMode ? getText(R.string.wifi_scan_notify_text)
970                 : getText(R.string.wifi_scan_notify_text_scanning_off);
971         final LinkifyUtils.OnClickListener clickListener =
972                 () -> new SubSettingLauncher(getContext())
973                         .setDestination(ScanningSettings.class.getName())
974                         .setTitle(R.string.location_scanning_screen_title)
975                         .setSourceMetricsCategory(getMetricsCategory())
976                         .launch();
977         mStatusMessagePreference.setText(title, description, clickListener);
978         removeConnectedAccessPointPreference();
979         mAccessPointsPreferenceCategory.removeAll();
980         mAccessPointsPreferenceCategory.addPreference(mStatusMessagePreference);
981     }
982 
addMessagePreference(int messageId)983     private void addMessagePreference(int messageId) {
984         mStatusMessagePreference.setTitle(messageId);
985         removeConnectedAccessPointPreference();
986         mAccessPointsPreferenceCategory.removeAll();
987         mAccessPointsPreferenceCategory.addPreference(mStatusMessagePreference);
988     }
989 
setProgressBarVisible(boolean visible)990     protected void setProgressBarVisible(boolean visible) {
991         if (mProgressHeader != null) {
992             mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
993         }
994     }
995 
996     /**
997      * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
998      * Wifi setup screens, not in usual wifi settings screen.
999      *
1000      * @param enabled true when the device is connected to a wifi network.
1001      */
changeNextButtonState(boolean enabled)1002     private void changeNextButtonState(boolean enabled) {
1003         if (mEnableNextOnConnection && hasNextButton()) {
1004             getNextButton().setEnabled(enabled);
1005         }
1006     }
1007 
1008     @Override
onForget(WifiDialog dialog)1009     public void onForget(WifiDialog dialog) {
1010         forget();
1011     }
1012 
1013     @Override
onSubmit(WifiDialog dialog)1014     public void onSubmit(WifiDialog dialog) {
1015         if (mDialog != null) {
1016             submit(mDialog.getController());
1017         }
1018     }
1019 
submit(WifiConfigController configController)1020     /* package */ void submit(WifiConfigController configController) {
1021 
1022         final WifiConfiguration config = configController.getConfig();
1023 
1024         if (config == null) {
1025             if (mSelectedAccessPoint != null
1026                     && mSelectedAccessPoint.isSaved()) {
1027                 connect(mSelectedAccessPoint.getConfig(), true /* isSavedNetwork */);
1028             }
1029         } else if (configController.getMode() == WifiConfigUiBase.MODE_MODIFY) {
1030             mWifiManager.save(config, mSaveListener);
1031         } else {
1032             mWifiManager.save(config, mSaveListener);
1033             if (mSelectedAccessPoint != null) { // Not an "Add network"
1034                 connect(config, false /* isSavedNetwork */);
1035             }
1036         }
1037 
1038         mWifiTracker.resumeScanning();
1039     }
1040 
forget()1041     /* package */ void forget() {
1042         mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_FORGET);
1043         if (!mSelectedAccessPoint.isSaved()) {
1044             if (mSelectedAccessPoint.getNetworkInfo() != null &&
1045                     mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
1046                 // Network is active but has no network ID - must be ephemeral.
1047                 mWifiManager.disableEphemeralNetwork(
1048                         AccessPoint.convertToQuotedString(mSelectedAccessPoint.getSsidStr()));
1049             } else {
1050                 // Should not happen, but a monkey seems to trigger it
1051                 Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
1052                 return;
1053             }
1054         } else if (mSelectedAccessPoint.getConfig().isPasspoint()) {
1055             mWifiManager.removePasspointConfiguration(mSelectedAccessPoint.getConfig().FQDN);
1056         } else {
1057             mWifiManager.forget(mSelectedAccessPoint.getConfig().networkId, mForgetListener);
1058         }
1059 
1060         mWifiTracker.resumeScanning();
1061 
1062         // We need to rename/replace "Next" button in wifi setup context.
1063         changeNextButtonState(false);
1064     }
1065 
connect(final WifiConfiguration config, boolean isSavedNetwork)1066     protected void connect(final WifiConfiguration config, boolean isSavedNetwork) {
1067         // Log subtype if configuration is a saved network.
1068         mMetricsFeatureProvider.action(getVisibilityLogger(), MetricsEvent.ACTION_WIFI_CONNECT,
1069                 isSavedNetwork);
1070         mWifiManager.connect(config, mConnectListener);
1071         mClickedConnect = true;
1072     }
1073 
connect(final int networkId, boolean isSavedNetwork)1074     protected void connect(final int networkId, boolean isSavedNetwork) {
1075         // Log subtype if configuration is a saved network.
1076         mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT,
1077                 isSavedNetwork);
1078         mWifiManager.connect(networkId, mConnectListener);
1079     }
1080 
1081     /**
1082      * Called when "add network" button is pressed.
1083      */
onAddNetworkPressed()1084     /* package */ void onAddNetworkPressed() {
1085         mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_ADD_NETWORK);
1086         // No exact access point is selected.
1087         mSelectedAccessPoint = null;
1088         showDialog(null, WifiConfigUiBase.MODE_CONNECT);
1089     }
1090 
1091     @Override
getHelpResource()1092     public int getHelpResource() {
1093         return R.string.help_url_wifi;
1094     }
1095 
1096     @Override
onAccessPointChanged(final AccessPoint accessPoint)1097     public void onAccessPointChanged(final AccessPoint accessPoint) {
1098         Log.d(TAG, "onAccessPointChanged (singular) callback initiated");
1099         View view = getView();
1100         if (view != null) {
1101             view.post(new Runnable() {
1102                 @Override
1103                 public void run() {
1104                     Object tag = accessPoint.getTag();
1105                     if (tag != null) {
1106                         ((AccessPointPreference) tag).refresh();
1107                     }
1108                 }
1109             });
1110         }
1111     }
1112 
1113     @Override
onLevelChanged(AccessPoint accessPoint)1114     public void onLevelChanged(AccessPoint accessPoint) {
1115         ((AccessPointPreference) accessPoint.getTag()).onLevelChanged();
1116     }
1117 
1118     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
1119         new BaseSearchIndexProvider() {
1120             @Override
1121             public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
1122                 final List<SearchIndexableRaw> result = new ArrayList<>();
1123                 final Resources res = context.getResources();
1124 
1125                 // Add fragment title if we are showing this fragment
1126                 if (res.getBoolean(R.bool.config_show_wifi_settings)) {
1127                     SearchIndexableRaw data = new SearchIndexableRaw(context);
1128                     data.title = res.getString(R.string.wifi_settings);
1129                     data.screenTitle = res.getString(R.string.wifi_settings);
1130                     data.keywords = res.getString(R.string.keywords_wifi);
1131                     data.key = DATA_KEY_REFERENCE;
1132                     result.add(data);
1133                 }
1134 
1135                 return result;
1136             }
1137         };
1138 
1139     private static class SummaryProvider
1140             implements SummaryLoader.SummaryProvider, OnSummaryChangeListener {
1141 
1142         private final Context mContext;
1143         private final SummaryLoader mSummaryLoader;
1144 
1145         @VisibleForTesting
1146         WifiSummaryUpdater mSummaryHelper;
1147 
SummaryProvider(Context context, SummaryLoader summaryLoader)1148         public SummaryProvider(Context context, SummaryLoader summaryLoader) {
1149             mContext = context;
1150             mSummaryLoader = summaryLoader;
1151             mSummaryHelper = new WifiSummaryUpdater(mContext, this);
1152         }
1153 
1154 
1155         @Override
setListening(boolean listening)1156         public void setListening(boolean listening) {
1157             mSummaryHelper.register(listening);
1158         }
1159 
1160         @Override
onSummaryChanged(String summary)1161         public void onSummaryChanged(String summary) {
1162             mSummaryLoader.setSummary(this, summary);
1163         }
1164     }
1165 
1166     public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
1167             = new SummaryLoader.SummaryProviderFactory() {
1168         @Override
1169         public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
1170                                                                    SummaryLoader summaryLoader) {
1171             return new SummaryProvider(activity, summaryLoader);
1172         }
1173     };
1174 }
1175