1 /*
2  * Copyright (C) 2019 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.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
20 import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
21 
22 import android.app.Activity;
23 import android.app.Dialog;
24 import android.app.settings.SettingsEnums;
25 import android.content.ActivityNotFoundException;
26 import android.content.ContentResolver;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.Intent;
30 import android.net.ConnectivityManager;
31 import android.net.NetworkScoreManager;
32 import android.net.NetworkTemplate;
33 import android.net.wifi.WifiConfiguration;
34 import android.net.wifi.WifiManager;
35 import android.os.Bundle;
36 import android.os.Handler;
37 import android.os.HandlerThread;
38 import android.os.Looper;
39 import android.os.PowerManager;
40 import android.os.Process;
41 import android.os.SimpleClock;
42 import android.os.SystemClock;
43 import android.provider.Settings;
44 import android.text.TextUtils;
45 import android.util.FeatureFlagUtils;
46 import android.util.Log;
47 import android.view.ContextMenu;
48 import android.view.ContextMenu.ContextMenuInfo;
49 import android.view.Menu;
50 import android.view.MenuItem;
51 import android.view.View;
52 import android.widget.Toast;
53 
54 import androidx.annotation.VisibleForTesting;
55 import androidx.preference.Preference;
56 import androidx.preference.PreferenceCategory;
57 import androidx.preference.PreferenceScreen;
58 import androidx.recyclerview.widget.RecyclerView;
59 
60 import com.android.settings.LinkifyUtils;
61 import com.android.settings.R;
62 import com.android.settings.RestrictedSettingsFragment;
63 import com.android.settings.SettingsActivity;
64 import com.android.settings.core.FeatureFlags;
65 import com.android.settings.core.SubSettingLauncher;
66 import com.android.settings.datausage.DataUsagePreference;
67 import com.android.settings.datausage.DataUsageUtils;
68 import com.android.settings.location.ScanningSettings;
69 import com.android.settings.search.BaseSearchIndexProvider;
70 import com.android.settings.widget.SwitchBarController;
71 import com.android.settings.wifi.details2.WifiNetworkDetailsFragment2;
72 import com.android.settings.wifi.dpp.WifiDppUtils;
73 import com.android.settingslib.HelpUtils;
74 import com.android.settingslib.RestrictedLockUtils;
75 import com.android.settingslib.RestrictedLockUtilsInternal;
76 import com.android.settingslib.search.Indexable;
77 import com.android.settingslib.search.SearchIndexable;
78 import com.android.settingslib.wifi.LongPressWifiEntryPreference;
79 import com.android.settingslib.wifi.WifiSavedConfigUtils;
80 import com.android.wifitrackerlib.WifiEntry;
81 import com.android.wifitrackerlib.WifiEntry.ConnectCallback;
82 import com.android.wifitrackerlib.WifiPickerTracker;
83 
84 import java.time.Clock;
85 import java.time.ZoneOffset;
86 import java.util.List;
87 import java.util.Optional;
88 
89 /**
90  * UI for Wi-Fi settings screen
91  */
92 @SearchIndexable
93 public class WifiSettings2 extends RestrictedSettingsFragment
94         implements Indexable, WifiPickerTracker.WifiPickerTrackerCallback,
95         WifiDialog2.WifiDialog2Listener, DialogInterface.OnDismissListener {
96 
97     private static final String TAG = "WifiSettings2";
98 
99     // IDs of context menu
100     static final int MENU_ID_CONNECT = Menu.FIRST + 1;
101     @VisibleForTesting
102     static final int MENU_ID_DISCONNECT = Menu.FIRST + 2;
103     @VisibleForTesting
104     static final int MENU_ID_FORGET = Menu.FIRST + 3;
105     static final int MENU_ID_MODIFY = Menu.FIRST + 4;
106 
107     // Max age of tracked WifiEntries
108     private static final long MAX_SCAN_AGE_MILLIS = 15_000;
109     // Interval between initiating WifiPickerTracker scans
110     private static final long SCAN_INTERVAL_MILLIS = 10_000;
111 
112     @VisibleForTesting
113     static final int ADD_NETWORK_REQUEST = 2;
114     static final int CONFIG_NETWORK_REQUEST = 3;
115     static final int MANAGE_SUBSCRIPTION = 4;
116 
117     private static final String PREF_KEY_EMPTY_WIFI_LIST = "wifi_empty_list";
118     // TODO(b/70983952): Rename these to use WifiEntry instead of AccessPoint.
119     private static final String PREF_KEY_CONNECTED_ACCESS_POINTS = "connected_access_point";
120     private static final String PREF_KEY_ACCESS_POINTS = "access_points";
121     private static final String PREF_KEY_CONFIGURE_WIFI_SETTINGS = "configure_wifi_settings";
122     private static final String PREF_KEY_SAVED_NETWORKS = "saved_networks";
123     private static final String PREF_KEY_STATUS_MESSAGE = "wifi_status_message";
124     @VisibleForTesting
125     static final String PREF_KEY_DATA_USAGE = "wifi_data_usage";
126 
127     private static final int REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER = 0;
128 
129     public static final int WIFI_DIALOG_ID = 1;
130 
131     // Instance state keys
132     private static final String SAVE_DIALOG_MODE = "dialog_mode";
133     private static final String SAVE_DIALOG_WIFIENTRY_KEY = "wifi_ap_key";
134 
135     // Cache at onCreateContextMenu and use at onContextItemSelected. Don't use it in other methods.
136     private WifiEntry mSelectedWifiEntry;
137 
138     // Save the dialog details
139     private int mDialogMode;
140     private String mDialogWifiEntryKey;
141     private WifiEntry mDialogWifiEntry;
142 
143     // This boolean extra specifies whether to enable the Next button when connected. Used by
144     // account creation outside of setup wizard.
145     private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
146 
147     // Enable the Next button when a Wi-Fi network is connected.
148     private boolean mEnableNextOnConnection;
149 
150     // This string extra specifies a network to open the connect dialog on, so the user can enter
151     // network credentials.  This is used by quick settings for secured networks, among other
152     // things.
153     private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
154     private String mOpenSsid;
155 
isVerboseLoggingEnabled()156     private static boolean isVerboseLoggingEnabled() {
157         return WifiPickerTracker.isVerboseLoggingEnabled();
158     }
159 
160     private final Runnable mUpdateWifiEntryPreferencesRunnable = () -> {
161         updateWifiEntryPreferences();
162     };
163     private final Runnable mHideProgressBarRunnable = () -> {
164         setProgressBarVisible(false);
165     };
166 
167     protected WifiManager mWifiManager;
168     private WifiManager.ActionListener mConnectListener;
169     private WifiManager.ActionListener mSaveListener;
170     private WifiManager.ActionListener mForgetListener;
171 
172     /**
173      * The state of {@link #isUiRestricted()} at {@link #onCreate(Bundle)}}. This is neccesary to
174      * ensure that behavior is consistent if {@link #isUiRestricted()} changes. It could be changed
175      * by the Test DPC tool in AFW mode.
176      */
177     private boolean mIsRestricted;
178 
179     private WifiEnabler mWifiEnabler;
180 
181     // Worker thread used for WifiPickerTracker work
182     private HandlerThread mWorkerThread;
183 
184     @VisibleForTesting
185     WifiPickerTracker mWifiPickerTracker;
186 
187     private WifiDialog2 mDialog;
188 
189     private View mProgressHeader;
190 
191     private PreferenceCategory mConnectedWifiEntryPreferenceCategory;
192     private PreferenceCategory mWifiEntryPreferenceCategory;
193     @VisibleForTesting
194     AddWifiNetworkPreference mAddWifiNetworkPreference;
195     @VisibleForTesting
196     Preference mConfigureWifiSettingsPreference;
197     @VisibleForTesting
198     Preference mSavedNetworksPreference;
199     @VisibleForTesting
200     DataUsagePreference mDataUsagePreference;
201     private LinkablePreference mStatusMessagePreference;
202 
203     /**
204      * Tracks whether the user initiated a connection via clicking in order to autoscroll to the
205      * network once connected.
206      */
207     private boolean mClickedConnect;
208 
WifiSettings2()209     public WifiSettings2() {
210         super(DISALLOW_CONFIG_WIFI);
211     }
212 
213     @Override
onViewCreated(View view, Bundle savedInstanceState)214     public void onViewCreated(View view, Bundle savedInstanceState) {
215         super.onViewCreated(view, savedInstanceState);
216         final Activity activity = getActivity();
217         if (activity != null) {
218             mProgressHeader = setPinnedHeaderView(R.layout.progress_header)
219                     .findViewById(R.id.progress_bar_animation);
220             setProgressBarVisible(false);
221         }
222         ((SettingsActivity) activity).getSwitchBar().setSwitchBarText(
223                 R.string.wifi_settings_master_switch_title,
224                 R.string.wifi_settings_master_switch_title);
225     }
226 
227     @Override
onCreate(Bundle icicle)228     public void onCreate(Bundle icicle) {
229         super.onCreate(icicle);
230 
231         // TODO(b/37429702): Add animations and preference comparator back after initial screen is
232         // loaded (ODR).
233         setAnimationAllowed(false);
234 
235         addPreferences();
236 
237         mIsRestricted = isUiRestricted();
238     }
239 
addPreferences()240     private void addPreferences() {
241         addPreferencesFromResource(R.xml.wifi_settings2);
242 
243         mConnectedWifiEntryPreferenceCategory = findPreference(PREF_KEY_CONNECTED_ACCESS_POINTS);
244         mWifiEntryPreferenceCategory = findPreference(PREF_KEY_ACCESS_POINTS);
245         mConfigureWifiSettingsPreference = findPreference(PREF_KEY_CONFIGURE_WIFI_SETTINGS);
246         mSavedNetworksPreference = findPreference(PREF_KEY_SAVED_NETWORKS);
247         mAddWifiNetworkPreference = new AddWifiNetworkPreference(getPrefContext());
248         mStatusMessagePreference = findPreference(PREF_KEY_STATUS_MESSAGE);
249         mDataUsagePreference = findPreference(PREF_KEY_DATA_USAGE);
250         mDataUsagePreference.setVisible(DataUsageUtils.hasWifiRadio(getContext()));
251         mDataUsagePreference.setTemplate(NetworkTemplate.buildTemplateWifiWildcard(),
252                 0 /*subId*/,
253                 null /*service*/);
254     }
255 
256     @Override
onActivityCreated(Bundle savedInstanceState)257     public void onActivityCreated(Bundle savedInstanceState) {
258         super.onActivityCreated(savedInstanceState);
259 
260         final Context context = getContext();
261         mWorkerThread = new HandlerThread(TAG +
262                 "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
263                 Process.THREAD_PRIORITY_BACKGROUND);
264         mWorkerThread.start();
265         final Clock elapsedRealtimeClock = new SimpleClock(ZoneOffset.UTC) {
266             @Override
267             public long millis() {
268                 return SystemClock.elapsedRealtime();
269             }
270         };
271         mWifiPickerTracker = new WifiPickerTracker(getSettingsLifecycle(), context,
272                 context.getSystemService(WifiManager.class),
273                 context.getSystemService(ConnectivityManager.class),
274                 context.getSystemService(NetworkScoreManager.class),
275                 new Handler(Looper.getMainLooper()),
276                 mWorkerThread.getThreadHandler(),
277                 elapsedRealtimeClock,
278                 MAX_SCAN_AGE_MILLIS,
279                 SCAN_INTERVAL_MILLIS,
280                 this);
281 
282         final Activity activity = getActivity();
283 
284         if (activity != null) {
285             mWifiManager = getActivity().getSystemService(WifiManager.class);
286         }
287 
288         mConnectListener = new WifiConnectListener(getActivity());
289 
290         mSaveListener = new WifiManager.ActionListener() {
291             @Override
292             public void onSuccess() {
293             }
294 
295             @Override
296             public void onFailure(int reason) {
297                 Activity activity = getActivity();
298                 if (activity != null) {
299                     Toast.makeText(activity,
300                             R.string.wifi_failed_save_message,
301                             Toast.LENGTH_SHORT).show();
302                 }
303             }
304         };
305 
306         mForgetListener = new WifiManager.ActionListener() {
307             @Override
308             public void onSuccess() {
309             }
310 
311             @Override
312             public void onFailure(int reason) {
313                 Activity activity = getActivity();
314                 if (activity != null) {
315                     Toast.makeText(activity,
316                             R.string.wifi_failed_forget_message,
317                             Toast.LENGTH_SHORT).show();
318                 }
319             }
320         };
321         registerForContextMenu(getListView());
322         setHasOptionsMenu(true);
323 
324         if (savedInstanceState != null) {
325             mDialogMode = savedInstanceState.getInt(SAVE_DIALOG_MODE);
326             mDialogWifiEntryKey = savedInstanceState.getString(SAVE_DIALOG_WIFIENTRY_KEY);
327         }
328 
329         // If we're supposed to enable/disable the Next button based on our current connection
330         // state, start it off in the right state.
331         final Intent intent = getActivity().getIntent();
332         mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
333 
334         if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
335             mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
336         }
337     }
338 
339     @Override
onDestroyView()340     public void onDestroyView() {
341         if (mWifiEnabler != null) {
342             mWifiEnabler.teardownSwitchController();
343         }
344         mWorkerThread.quit();
345 
346         super.onDestroyView();
347     }
348 
349     @Override
onStart()350     public void onStart() {
351         super.onStart();
352 
353         mWifiEnabler = createWifiEnabler();
354 
355         if (mIsRestricted) {
356             restrictUi();
357         }
358     }
359 
restrictUi()360     private void restrictUi() {
361         if (!isUiRestrictedByOnlyAdmin()) {
362             getEmptyTextView().setText(R.string.wifi_empty_list_user_restricted);
363         }
364         getPreferenceScreen().removeAll();
365     }
366 
367     /**
368      * @return new WifiEnabler
369      */
createWifiEnabler()370     private WifiEnabler createWifiEnabler() {
371         final SettingsActivity activity = (SettingsActivity) getActivity();
372         return new WifiEnabler(activity, new SwitchBarController(activity.getSwitchBar()),
373                 mMetricsFeatureProvider);
374     }
375 
376     @Override
onResume()377     public void onResume() {
378         final Activity activity = getActivity();
379         super.onResume();
380 
381         // Because RestrictedSettingsFragment's onResume potentially requests authorization,
382         // which changes the restriction state, recalculate it.
383         final boolean alreadyImmutablyRestricted = mIsRestricted;
384         mIsRestricted = isUiRestricted();
385         if (!alreadyImmutablyRestricted && mIsRestricted) {
386             restrictUi();
387         }
388 
389         if (mWifiEnabler != null) {
390             mWifiEnabler.resume(activity);
391         }
392 
393         changeNextButtonState(mWifiPickerTracker.getConnectedWifiEntry() != null);
394     }
395 
396     @Override
onPause()397     public void onPause() {
398         super.onPause();
399         if (mWifiEnabler != null) {
400             mWifiEnabler.pause();
401         }
402     }
403 
404     @Override
onStop()405     public void onStop() {
406         getView().removeCallbacks(mUpdateWifiEntryPreferencesRunnable);
407         getView().removeCallbacks(mHideProgressBarRunnable);
408         super.onStop();
409     }
410 
411     @Override
onActivityResult(int requestCode, int resultCode, Intent data)412     public void onActivityResult(int requestCode, int resultCode, Intent data) {
413         super.onActivityResult(requestCode, resultCode, data);
414 
415         if (requestCode == ADD_NETWORK_REQUEST) {
416             handleAddNetworkRequest(resultCode, data);
417             return;
418         } else if (requestCode == REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER) {
419             if (resultCode == Activity.RESULT_OK) {
420                 if (mDialog != null) {
421                     mDialog.dismiss();
422                 }
423             }
424             return;
425         } else if (requestCode == CONFIG_NETWORK_REQUEST) {
426             if (resultCode == Activity.RESULT_OK) {
427                 final WifiConfiguration wifiConfiguration = data.getParcelableExtra(
428                         ConfigureWifiEntryFragment.NETWORK_CONFIG_KEY);
429                 if (wifiConfiguration != null) {
430                     mWifiManager.connect(wifiConfiguration,
431                             new WifiConnectActionListener());
432                 }
433             }
434             return;
435         } else if (requestCode == MANAGE_SUBSCRIPTION) {
436             //Do nothing
437             return;
438         }
439 
440         final boolean formerlyRestricted = mIsRestricted;
441         mIsRestricted = isUiRestricted();
442         if (formerlyRestricted && !mIsRestricted
443                 && getPreferenceScreen().getPreferenceCount() == 0) {
444             // De-restrict the ui
445             addPreferences();
446         }
447     }
448 
449     @Override
onCreateAdapter(PreferenceScreen preferenceScreen)450     protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
451         final RecyclerView.Adapter adapter = super.onCreateAdapter(preferenceScreen);
452         adapter.setHasStableIds(true);
453         return adapter;
454     }
455 
456     @Override
getMetricsCategory()457     public int getMetricsCategory() {
458         return SettingsEnums.WIFI;
459     }
460 
461     @Override
onSaveInstanceState(Bundle outState)462     public void onSaveInstanceState(Bundle outState) {
463         super.onSaveInstanceState(outState);
464         // If dialog has been shown, save its state.
465         if (mDialog != null) {
466             outState.putInt(SAVE_DIALOG_MODE, mDialogMode);
467             outState.putString(SAVE_DIALOG_WIFIENTRY_KEY, mDialogWifiEntryKey);
468         }
469     }
470 
471     @Override
onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info)472     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
473         Preference preference = (Preference) view.getTag();
474         if (!(preference instanceof LongPressWifiEntryPreference)) {
475             // Do nothing.
476             return;
477         }
478 
479         // Cache the WifiEntry for onContextItemSelected. Don't use it in other methods.
480         mSelectedWifiEntry = ((LongPressWifiEntryPreference) preference).getWifiEntry();
481 
482         menu.setHeaderTitle(mSelectedWifiEntry.getTitle());
483         if (mSelectedWifiEntry.canConnect()) {
484             menu.add(Menu.NONE, MENU_ID_CONNECT, 0 /* order */, R.string.wifi_connect);
485         }
486 
487         if (mSelectedWifiEntry.canDisconnect()) {
488             menu.add(Menu.NONE, MENU_ID_DISCONNECT, 0 /* order */,
489                     R.string.wifi_disconnect_button_text);
490         }
491 
492         // "forget" for normal saved network. And "disconnect" for ephemeral network because it
493         // could only be disconnected and be put in blacklists so it won't be used again.
494         if (canForgetNetwork()) {
495             menu.add(Menu.NONE, MENU_ID_FORGET, 0 /* order */, R.string.forget);
496         }
497 
498         WifiConfiguration config = mSelectedWifiEntry.getWifiConfiguration();
499         // Some configs are ineditable
500         if (WifiUtils.isNetworkLockedDown(getActivity(), config)) {
501             return;
502         }
503 
504         if (mSelectedWifiEntry.isSaved() && mSelectedWifiEntry.getConnectedState()
505                 != WifiEntry.CONNECTED_STATE_CONNECTED) {
506             menu.add(Menu.NONE, MENU_ID_MODIFY, 0 /* order */, R.string.wifi_modify);
507         }
508     }
509 
canForgetNetwork()510     private boolean canForgetNetwork() {
511         return mSelectedWifiEntry.canForget() && !WifiUtils.isNetworkLockedDown(getActivity(),
512                 mSelectedWifiEntry.getWifiConfiguration());
513     }
514 
515     @Override
onContextItemSelected(MenuItem item)516     public boolean onContextItemSelected(MenuItem item) {
517         switch (item.getItemId()) {
518             case MENU_ID_CONNECT:
519                 connect(mSelectedWifiEntry, true /* editIfNoConfig */, false /* fullScreenEdit */);
520                 return true;
521             case MENU_ID_DISCONNECT:
522                 mSelectedWifiEntry.disconnect(null /* callback */);
523                 return true;
524             case MENU_ID_FORGET:
525                 forget(mSelectedWifiEntry);
526                 return true;
527             case MENU_ID_MODIFY:
528                 showDialog(mSelectedWifiEntry, WifiConfigUiBase2.MODE_MODIFY);
529                 return true;
530             default:
531                 return super.onContextItemSelected(item);
532         }
533     }
534 
535     @Override
onPreferenceTreeClick(Preference preference)536     public boolean onPreferenceTreeClick(Preference preference) {
537         // If the preference has a fragment set, open that
538         if (preference.getFragment() != null) {
539             preference.setOnPreferenceClickListener(null);
540             return super.onPreferenceTreeClick(preference);
541         }
542 
543         if (preference instanceof LongPressWifiEntryPreference) {
544             final WifiEntry selectedEntry =
545                     ((LongPressWifiEntryPreference) preference).getWifiEntry();
546 
547             if (selectedEntry.shouldEditBeforeConnect()) {
548                 launchConfigNewNetworkFragment(selectedEntry);
549                 return true;
550             }
551 
552             connect(selectedEntry, true /* editIfNoConfig */, true /* fullScreenEdit */);
553         } else if (preference == mAddWifiNetworkPreference) {
554             onAddNetworkPressed();
555         } else {
556             return super.onPreferenceTreeClick(preference);
557         }
558         return true;
559     }
560 
showDialog(WifiEntry wifiEntry, int dialogMode)561     private void showDialog(WifiEntry wifiEntry, int dialogMode) {
562         if (WifiUtils.isNetworkLockedDown(getActivity(), wifiEntry.getWifiConfiguration())
563                 && wifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) {
564             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(),
565                     RestrictedLockUtilsInternal.getDeviceOwner(getActivity()));
566             return;
567         }
568 
569         if (mDialog != null) {
570             removeDialog(WIFI_DIALOG_ID);
571             mDialog = null;
572         }
573 
574         // Save the access point and edit mode
575         mDialogWifiEntry = wifiEntry;
576         mDialogWifiEntryKey = wifiEntry.getKey();
577         mDialogMode = dialogMode;
578 
579         showDialog(WIFI_DIALOG_ID);
580     }
581 
582     @Override
onCreateDialog(int dialogId)583     public Dialog onCreateDialog(int dialogId) {
584         switch (dialogId) {
585             case WIFI_DIALOG_ID:
586                 // modify network
587                 mDialog = WifiDialog2
588                         .createModal(getActivity(), this, mDialogWifiEntry, mDialogMode);
589                 return mDialog;
590             default:
591                 return super.onCreateDialog(dialogId);
592         }
593     }
594 
595     @Override
onDialogShowing()596     public void onDialogShowing() {
597         super.onDialogShowing();
598         setOnDismissListener(this);
599     }
600 
601     @Override
onDismiss(DialogInterface dialog)602     public void onDismiss(DialogInterface dialog) {
603         // We don't keep any dialog object when dialog was dismissed.
604         mDialog = null;
605         mDialogWifiEntry = null;
606         mDialogWifiEntryKey = null;
607     }
608 
609     @Override
getDialogMetricsCategory(int dialogId)610     public int getDialogMetricsCategory(int dialogId) {
611         switch (dialogId) {
612             case WIFI_DIALOG_ID:
613                 return SettingsEnums.DIALOG_WIFI_AP_EDIT;
614             default:
615                 return 0;
616         }
617     }
618 
619     /** Called when the state of Wifi has changed. */
620     @Override
onWifiStateChanged()621     public void onWifiStateChanged() {
622         if (mIsRestricted) {
623             return;
624         }
625         final int wifiState = mWifiPickerTracker.getWifiState();
626 
627         if (isVerboseLoggingEnabled()) {
628             Log.i(TAG, "onWifiStateChanged called with wifi state: " + wifiState);
629         }
630 
631         switch (wifiState) {
632             case WifiManager.WIFI_STATE_ENABLED:
633                 updateWifiEntryPreferences();
634                 break;
635 
636             case WifiManager.WIFI_STATE_ENABLING:
637                 removeConnectedWifiEntryPreference();
638                 removeWifiEntryPreference();
639                 addMessagePreference(R.string.wifi_starting);
640                 setProgressBarVisible(true);
641                 break;
642 
643             case WifiManager.WIFI_STATE_DISABLING:
644                 removeConnectedWifiEntryPreference();
645                 removeWifiEntryPreference();
646                 addMessagePreference(R.string.wifi_stopping);
647                 break;
648 
649             case WifiManager.WIFI_STATE_DISABLED:
650                 setOffMessage();
651                 setAdditionalSettingsSummaries();
652                 setProgressBarVisible(false);
653                 mClickedConnect = false;
654                 break;
655         }
656     }
657 
658     @Override
onWifiEntriesChanged()659     public void onWifiEntriesChanged() {
660         updateWifiEntryPreferencesDelayed();
661         changeNextButtonState(mWifiPickerTracker.getConnectedWifiEntry() != null);
662 
663         // Edit the Wi-Fi network of specified SSID.
664         if (mOpenSsid != null) {
665             Optional<WifiEntry> matchedWifiEntry = mWifiPickerTracker.getWifiEntries().stream()
666                     .filter(wifiEntry -> TextUtils.equals(mOpenSsid, wifiEntry.getSsid()))
667                     .filter(wifiEntry -> wifiEntry.getSecurity() != WifiEntry.SECURITY_NONE
668                             && wifiEntry.getSecurity() != WifiEntry.SECURITY_OWE)
669                     .filter(wifiEntry -> !wifiEntry.isSaved()
670                             || isDisabledByWrongPassword(wifiEntry))
671                     .findFirst();
672             if (matchedWifiEntry.isPresent()) {
673                 mOpenSsid = null;
674                 launchConfigNewNetworkFragment(matchedWifiEntry.get());
675             }
676         }
677     }
678 
679     @Override
onNumSavedNetworksChanged()680     public void onNumSavedNetworksChanged() {
681         if (isFinishingOrDestroyed()) {
682             return;
683         }
684         setAdditionalSettingsSummaries();
685     }
686 
687     @Override
onNumSavedSubscriptionsChanged()688     public void onNumSavedSubscriptionsChanged() {
689         if (isFinishingOrDestroyed()) {
690             return;
691         }
692         setAdditionalSettingsSummaries();
693     }
694 
695     /**
696      * Updates WifiEntries from {@link WifiPickerTracker#getWifiEntries()}. Adds a delay to have
697      * progress bar displayed before starting to modify entries.
698      */
updateWifiEntryPreferencesDelayed()699     private void updateWifiEntryPreferencesDelayed() {
700         // Safeguard from some delayed event handling
701         if (getActivity() != null && !mIsRestricted &&
702                 mWifiPickerTracker.getWifiState() == WifiManager.WIFI_STATE_ENABLED) {
703             final View view = getView();
704             final Handler handler = view.getHandler();
705             if (handler != null && handler.hasCallbacks(mUpdateWifiEntryPreferencesRunnable)) {
706                 return;
707             }
708             setProgressBarVisible(true);
709             view.postDelayed(mUpdateWifiEntryPreferencesRunnable, 300);
710         }
711     }
712 
updateWifiEntryPreferences()713     private void updateWifiEntryPreferences() {
714         // in case state has changed
715         if (mWifiPickerTracker.getWifiState() != WifiManager.WIFI_STATE_ENABLED) {
716             return;
717         }
718 
719         boolean hasAvailableWifiEntries = false;
720         mStatusMessagePreference.setVisible(false);
721         mWifiEntryPreferenceCategory.setVisible(true);
722 
723         final WifiEntry connectedEntry = mWifiPickerTracker.getConnectedWifiEntry();
724         mConnectedWifiEntryPreferenceCategory.setVisible(connectedEntry != null);
725         if (connectedEntry != null) {
726             final LongPressWifiEntryPreference connectedPref =
727                     mConnectedWifiEntryPreferenceCategory.findPreference(connectedEntry.getKey());
728             if (connectedPref == null || connectedPref.getWifiEntry() != connectedEntry) {
729                 mConnectedWifiEntryPreferenceCategory.removeAll();
730                 final ConnectedWifiEntryPreference pref =
731                         new ConnectedWifiEntryPreference(getPrefContext(), connectedEntry, this);
732                 pref.setKey(connectedEntry.getKey());
733                 pref.refresh();
734                 mConnectedWifiEntryPreferenceCategory.addPreference(pref);
735                 pref.setOnPreferenceClickListener(preference -> {
736                     if (connectedEntry.canSignIn()) {
737                         connectedEntry.signIn(null /* callback */);
738                     } else {
739                         launchNetworkDetailsFragment(pref);
740                     }
741                     return true;
742                 });
743                 pref.setOnGearClickListener(preference -> {
744                     launchNetworkDetailsFragment(pref);
745                 });
746 
747                 if (mClickedConnect) {
748                     mClickedConnect = false;
749                     scrollToPreference(mConnectedWifiEntryPreferenceCategory);
750                 }
751             }
752         } else {
753             mConnectedWifiEntryPreferenceCategory.removeAll();
754         }
755 
756         int index = 0;
757         cacheRemoveAllPrefs(mWifiEntryPreferenceCategory);
758         List<WifiEntry> wifiEntries = mWifiPickerTracker.getWifiEntries();
759         for (WifiEntry wifiEntry : wifiEntries) {
760             hasAvailableWifiEntries = true;
761 
762             String key = wifiEntry.getKey();
763             LongPressWifiEntryPreference pref =
764                     (LongPressWifiEntryPreference) getCachedPreference(key);
765             if (pref != null) {
766                 if (pref.getWifiEntry() == wifiEntry) {
767                     pref.setOrder(index++);
768                     continue;
769                 } else {
770                     // Create a new preference if the underlying WifiEntry object has changed
771                     removePreference(key);
772                 }
773             }
774 
775             pref = createLongPressWifiEntryPreference(wifiEntry);
776             pref.setKey(wifiEntry.getKey());
777             pref.setOrder(index++);
778             pref.refresh();
779 
780             if (wifiEntry.getHelpUriString() != null) {
781                 pref.setOnButtonClickListener(preference -> {
782                     openSubscriptionHelpPage(wifiEntry);
783                 });
784             }
785             mWifiEntryPreferenceCategory.addPreference(pref);
786         }
787         removeCachedPrefs(mWifiEntryPreferenceCategory);
788 
789         if (!hasAvailableWifiEntries) {
790             setProgressBarVisible(true);
791             Preference pref = new Preference(getPrefContext());
792             pref.setSelectable(false);
793             pref.setSummary(R.string.wifi_empty_list_wifi_on);
794             pref.setOrder(index++);
795             pref.setKey(PREF_KEY_EMPTY_WIFI_LIST);
796             mWifiEntryPreferenceCategory.addPreference(pref);
797         } else {
798             // Continuing showing progress bar for an additional delay to overlap with animation
799             getView().postDelayed(mHideProgressBarRunnable, 1700 /* delay millis */);
800         }
801 
802         mAddWifiNetworkPreference.setOrder(index++);
803         mWifiEntryPreferenceCategory.addPreference(mAddWifiNetworkPreference);
804         setAdditionalSettingsSummaries();
805     }
806 
launchNetworkDetailsFragment(LongPressWifiEntryPreference pref)807     private void launchNetworkDetailsFragment(LongPressWifiEntryPreference pref) {
808         final WifiEntry wifiEntry = pref.getWifiEntry();
809         final Context context = getContext();
810         final CharSequence title =
811                 FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER)
812                         ? wifiEntry.getTitle()
813                         : context.getText(R.string.pref_title_network_details);
814 
815         final Bundle bundle = new Bundle();
816         bundle.putString(WifiNetworkDetailsFragment2.KEY_CHOSEN_WIFIENTRY_KEY, wifiEntry.getKey());
817 
818         new SubSettingLauncher(context)
819                 .setTitleText(title)
820                 .setDestination(WifiNetworkDetailsFragment2.class.getName())
821                 .setArguments(bundle)
822                 .setSourceMetricsCategory(getMetricsCategory())
823                 .launch();
824     }
825 
826     @VisibleForTesting
createLongPressWifiEntryPreference(WifiEntry wifiEntry)827     LongPressWifiEntryPreference createLongPressWifiEntryPreference(WifiEntry wifiEntry) {
828         return new LongPressWifiEntryPreference(getPrefContext(), wifiEntry, this);
829     }
830 
launchAddNetworkFragment()831     private void launchAddNetworkFragment() {
832         new SubSettingLauncher(getContext())
833                 .setTitleRes(R.string.wifi_add_network)
834                 .setDestination(AddNetworkFragment.class.getName())
835                 .setSourceMetricsCategory(getMetricsCategory())
836                 .setResultListener(this, ADD_NETWORK_REQUEST)
837                 .launch();
838     }
839 
840     /** Removes all preferences and hide the {@link #mConnectedWifiEntryPreferenceCategory}. */
removeConnectedWifiEntryPreference()841     private void removeConnectedWifiEntryPreference() {
842         mConnectedWifiEntryPreferenceCategory.removeAll();
843         mConnectedWifiEntryPreferenceCategory.setVisible(false);
844     }
845 
removeWifiEntryPreference()846     private void removeWifiEntryPreference() {
847         mWifiEntryPreferenceCategory.removeAll();
848         mWifiEntryPreferenceCategory.setVisible(false);
849     }
850 
851     @VisibleForTesting
setAdditionalSettingsSummaries()852     void setAdditionalSettingsSummaries() {
853         mConfigureWifiSettingsPreference.setSummary(getString(
854                 isWifiWakeupEnabled()
855                         ? R.string.wifi_configure_settings_preference_summary_wakeup_on
856                         : R.string.wifi_configure_settings_preference_summary_wakeup_off));
857 
858         final int numSavedNetworks = mWifiPickerTracker.getNumSavedNetworks();
859         final int numSavedSubscriptions = mWifiPickerTracker.getNumSavedSubscriptions();
860         if (numSavedNetworks + numSavedSubscriptions > 0) {
861             mSavedNetworksPreference.setVisible(true);
862             mSavedNetworksPreference.setSummary(
863                     getSavedNetworkSettingsSummaryText(numSavedNetworks, numSavedSubscriptions));
864         } else {
865             mSavedNetworksPreference.setVisible(false);
866         }
867     }
868 
getSavedNetworkSettingsSummaryText( int numSavedNetworks, int numSavedSubscriptions)869     private String getSavedNetworkSettingsSummaryText(
870             int numSavedNetworks, int numSavedSubscriptions) {
871         if (numSavedSubscriptions == 0) {
872             return getResources().getQuantityString(R.plurals.wifi_saved_access_points_summary,
873                     numSavedNetworks, numSavedNetworks);
874         } else if (numSavedNetworks == 0) {
875             return getResources().getQuantityString(
876                     R.plurals.wifi_saved_passpoint_access_points_summary,
877                     numSavedSubscriptions, numSavedSubscriptions);
878         } else {
879             final int numTotalEntries = numSavedNetworks + numSavedSubscriptions;
880             return getResources().getQuantityString(R.plurals.wifi_saved_all_access_points_summary,
881                     numTotalEntries, numTotalEntries);
882         }
883     }
884 
isWifiWakeupEnabled()885     private boolean isWifiWakeupEnabled() {
886         final Context context = getContext();
887         final PowerManager powerManager = context.getSystemService(PowerManager.class);
888         final ContentResolver contentResolver = context.getContentResolver();
889         return mWifiManager.isAutoWakeupEnabled()
890                 && mWifiManager.isScanAlwaysAvailable()
891                 && Settings.Global.getInt(contentResolver,
892                 Settings.Global.AIRPLANE_MODE_ON, 0) == 0
893                 && !powerManager.isPowerSaveMode();
894     }
895 
setOffMessage()896     private void setOffMessage() {
897         final CharSequence title = getText(R.string.wifi_empty_list_wifi_off);
898         // Don't use WifiManager.isScanAlwaysAvailable() to check the Wi-Fi scanning mode. Instead,
899         // read the system settings directly. Because when the device is in Airplane mode, even if
900         // Wi-Fi scanning mode is on, WifiManager.isScanAlwaysAvailable() still returns "off".
901         // TODO(b/149421497): Fix this?
902         final boolean wifiScanningMode = mWifiManager.isScanAlwaysAvailable();
903         final CharSequence description = wifiScanningMode ? getText(R.string.wifi_scan_notify_text)
904                 : getText(R.string.wifi_scan_notify_text_scanning_off);
905         final LinkifyUtils.OnClickListener clickListener =
906                 () -> new SubSettingLauncher(getContext())
907                         .setDestination(ScanningSettings.class.getName())
908                         .setTitleRes(R.string.location_scanning_screen_title)
909                         .setSourceMetricsCategory(getMetricsCategory())
910                         .launch();
911         mStatusMessagePreference.setText(title, description, clickListener);
912         removeConnectedWifiEntryPreference();
913         removeWifiEntryPreference();
914         mStatusMessagePreference.setVisible(true);
915     }
916 
addMessagePreference(int messageId)917     private void addMessagePreference(int messageId) {
918         mStatusMessagePreference.setTitle(messageId);
919         mStatusMessagePreference.setVisible(true);
920 
921     }
922 
setProgressBarVisible(boolean visible)923     protected void setProgressBarVisible(boolean visible) {
924         if (mProgressHeader != null) {
925             mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
926         }
927     }
928 
929     @VisibleForTesting
handleAddNetworkRequest(int result, Intent data)930     void handleAddNetworkRequest(int result, Intent data) {
931         if (result == Activity.RESULT_OK) {
932             handleAddNetworkSubmitEvent(data);
933         }
934     }
935 
handleAddNetworkSubmitEvent(Intent data)936     private void handleAddNetworkSubmitEvent(Intent data) {
937         final WifiConfiguration wifiConfiguration = data.getParcelableExtra(
938                 AddNetworkFragment.WIFI_CONFIG_KEY);
939         if (wifiConfiguration != null) {
940             mWifiManager.save(wifiConfiguration, mSaveListener);
941         }
942     }
943 
944     /**
945      * Called when "add network" button is pressed.
946      */
onAddNetworkPressed()947     private void onAddNetworkPressed() {
948         launchAddNetworkFragment();
949     }
950 
951     @Override
getHelpResource()952     public int getHelpResource() {
953         return R.string.help_url_wifi;
954     }
955 
956     /**
957      * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
958      * Wi-Fi setup screens, not in usual wifi settings screen.
959      *
960      * @param enabled true when the device is connected to a wifi network.
961      */
962     @VisibleForTesting
changeNextButtonState(boolean enabled)963     void changeNextButtonState(boolean enabled) {
964         if (mEnableNextOnConnection && hasNextButton()) {
965             getNextButton().setEnabled(enabled);
966         }
967     }
968 
969     @Override
onForget(WifiDialog2 dialog)970     public void onForget(WifiDialog2 dialog) {
971         forget(dialog.getWifiEntry());
972     }
973 
974     @Override
onSubmit(WifiDialog2 dialog)975     public void onSubmit(WifiDialog2 dialog) {
976         final int dialogMode = dialog.getMode();
977         final WifiConfiguration config = dialog.getController().getConfig();
978         final WifiEntry wifiEntry = dialog.getWifiEntry();
979 
980         if (dialogMode == WifiConfigUiBase2.MODE_MODIFY) {
981             if (config == null) {
982                 Toast.makeText(getContext(), R.string.wifi_failed_save_message,
983                         Toast.LENGTH_SHORT).show();
984             } else {
985                 mWifiManager.save(config, mSaveListener);
986             }
987         } else if (dialogMode == WifiConfigUiBase2.MODE_CONNECT
988                 || (dialogMode == WifiConfigUiBase2.MODE_VIEW && wifiEntry.canConnect())) {
989             if (config == null) {
990                 connect(wifiEntry, false /* editIfNoConfig */,
991                         false /* fullScreenEdit*/);
992             } else {
993                 mWifiManager.connect(config, new WifiConnectActionListener());
994             }
995         }
996     }
997 
998     @Override
onScan(WifiDialog2 dialog, String ssid)999     public void onScan(WifiDialog2 dialog, String ssid) {
1000         // Launch QR code scanner to join a network.
1001         startActivityForResult(WifiDppUtils.getEnrolleeQrCodeScannerIntent(ssid),
1002                 REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER);
1003     }
1004 
forget(WifiEntry wifiEntry)1005     private void forget(WifiEntry wifiEntry) {
1006         mMetricsFeatureProvider.action(getActivity(), SettingsEnums.ACTION_WIFI_FORGET);
1007         wifiEntry.forget(null /* callback */);
1008     }
1009 
1010     @VisibleForTesting
connect(WifiEntry wifiEntry, boolean editIfNoConfig, boolean fullScreenEdit)1011     void connect(WifiEntry wifiEntry, boolean editIfNoConfig, boolean fullScreenEdit) {
1012         mMetricsFeatureProvider.action(getActivity(), SettingsEnums.ACTION_WIFI_CONNECT,
1013                 wifiEntry.isSaved());
1014 
1015         // If it's an unsaved secure WifiEntry, it will callback
1016         // ConnectCallback#onConnectResult with ConnectCallback#CONNECT_STATUS_FAILURE_NO_CONFIG
1017         wifiEntry.connect(new WifiEntryConnectCallback(wifiEntry, editIfNoConfig,
1018                 fullScreenEdit));
1019     }
1020 
1021     private class WifiConnectActionListener implements WifiManager.ActionListener {
1022         @Override
onSuccess()1023         public void onSuccess() {
1024             mClickedConnect = true;
1025         }
1026 
1027         @Override
onFailure(int reason)1028         public void onFailure(int reason) {
1029             if (isFinishingOrDestroyed()) {
1030                 return;
1031             }
1032             Toast.makeText(getContext(), R.string.wifi_failed_connect_message, Toast.LENGTH_SHORT)
1033                     .show();
1034         }
1035     };
1036 
1037     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
1038             new BaseSearchIndexProvider(R.xml.wifi_settings2) {
1039                 @Override
1040                 public List<String> getNonIndexableKeys(Context context) {
1041                     final List<String> keys = super.getNonIndexableKeys(context);
1042 
1043                     final WifiManager wifiManager = context.getSystemService(WifiManager.class);
1044                     if (WifiSavedConfigUtils.getAllConfigsCount(context, wifiManager) == 0) {
1045                         keys.add(PREF_KEY_SAVED_NETWORKS);
1046                     }
1047 
1048                     if (!DataUsageUtils.hasWifiRadio(context)) {
1049                         keys.add(PREF_KEY_DATA_USAGE);
1050                     }
1051                     return keys;
1052                 }
1053             };
1054 
1055     private class WifiEntryConnectCallback implements ConnectCallback {
1056         final WifiEntry mConnectWifiEntry;
1057         final boolean mEditIfNoConfig;
1058         final boolean mFullScreenEdit;
1059 
WifiEntryConnectCallback(WifiEntry connectWifiEntry, boolean editIfNoConfig, boolean fullScreenEdit)1060         WifiEntryConnectCallback(WifiEntry connectWifiEntry, boolean editIfNoConfig,
1061                 boolean fullScreenEdit) {
1062             mConnectWifiEntry = connectWifiEntry;
1063             mEditIfNoConfig = editIfNoConfig;
1064             mFullScreenEdit = fullScreenEdit;
1065         }
1066 
1067         @Override
onConnectResult(@onnectStatus int status)1068         public void onConnectResult(@ConnectStatus int status) {
1069             if (isFinishingOrDestroyed()) {
1070                 return;
1071             }
1072 
1073             if (status == ConnectCallback.CONNECT_STATUS_SUCCESS) {
1074                 mClickedConnect = true;
1075             } else if (status == ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) {
1076                 if (mEditIfNoConfig) {
1077                     // Edit an unsaved secure Wi-Fi network.
1078                     if (mFullScreenEdit) {
1079                         launchConfigNewNetworkFragment(mConnectWifiEntry);
1080                     } else {
1081                         showDialog(mConnectWifiEntry, WifiConfigUiBase2.MODE_CONNECT);
1082                     }
1083                 }
1084             } else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) {
1085                 Toast.makeText(getContext(), R.string.wifi_failed_connect_message,
1086                         Toast.LENGTH_SHORT).show();
1087             }
1088         }
1089     }
1090 
launchConfigNewNetworkFragment(WifiEntry wifiEntry)1091     private void launchConfigNewNetworkFragment(WifiEntry wifiEntry) {
1092         final Bundle bundle = new Bundle();
1093         bundle.putString(WifiNetworkDetailsFragment2.KEY_CHOSEN_WIFIENTRY_KEY,
1094                 wifiEntry.getKey());
1095         new SubSettingLauncher(getContext())
1096                 .setTitleText(wifiEntry.getTitle())
1097                 .setDestination(ConfigureWifiEntryFragment.class.getName())
1098                 .setArguments(bundle)
1099                 .setSourceMetricsCategory(getMetricsCategory())
1100                 .setResultListener(WifiSettings2.this, CONFIG_NETWORK_REQUEST)
1101                 .launch();
1102     }
1103 
1104     /** Helper method to return whether a WifiEntry is disabled due to a wrong password */
isDisabledByWrongPassword(WifiEntry wifiEntry)1105     private static boolean isDisabledByWrongPassword(WifiEntry wifiEntry) {
1106         WifiConfiguration config = wifiEntry.getWifiConfiguration();
1107         if (config == null) {
1108             return false;
1109         }
1110         WifiConfiguration.NetworkSelectionStatus networkStatus =
1111                 config.getNetworkSelectionStatus();
1112         if (networkStatus == null
1113                 || networkStatus.getNetworkSelectionStatus() == NETWORK_SELECTION_ENABLED) {
1114             return false;
1115         }
1116         int reason = networkStatus.getNetworkSelectionDisableReason();
1117         return WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD == reason;
1118     }
1119 
1120     @VisibleForTesting
openSubscriptionHelpPage(WifiEntry wifiEntry)1121     void openSubscriptionHelpPage(WifiEntry wifiEntry) {
1122         final Intent intent = getHelpIntent(getContext(), wifiEntry.getHelpUriString());
1123         if (intent != null) {
1124             try {
1125                 startActivityForResult(intent, MANAGE_SUBSCRIPTION);
1126             } catch (ActivityNotFoundException e) {
1127                 Log.e(TAG, "Activity was not found for intent, " + intent.toString());
1128             }
1129         }
1130     }
1131 
1132     @VisibleForTesting
getHelpIntent(Context context, String helpUrlString)1133     Intent getHelpIntent(Context context, String helpUrlString) {
1134         return HelpUtils.getHelpIntent(context, helpUrlString, context.getClass().getName());
1135     }
1136 }
1137