/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.wifi;

import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
import static android.os.UserManager.DISALLOW_CONFIG_WIFI;

import android.app.Activity;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.NetworkTemplate;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.PowerManager;
import android.os.Process;
import android.os.SimpleClock;
import android.os.SystemClock;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.FeatureFlagUtils;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;

import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;

import com.android.settings.LinkifyUtils;
import com.android.settings.R;
import com.android.settings.RestrictedSettingsFragment;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.core.FeatureFlags;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.datausage.DataUsagePreference;
import com.android.settings.datausage.DataUsageUtils;
import com.android.settings.location.WifiScanningFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.MainSwitchBarController;
import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
import com.android.settings.wifi.dpp.WifiDppUtils;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.search.Indexable;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.wifi.WifiSavedConfigUtils;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiEntry.ConnectCallback;
import com.android.wifitrackerlib.WifiPickerTracker;

import java.time.Clock;
import java.time.ZoneOffset;
import java.util.List;
import java.util.Optional;

/**
 * UI for Wi-Fi settings screen
 *
 * @deprecated This file will be deprecated at Android S, please merge your WifiSettings
 * in change in {@link NetworkProviderSettings}.
 */
@Deprecated
@SearchIndexable
public class WifiSettings extends RestrictedSettingsFragment
        implements Indexable, WifiPickerTracker.WifiPickerTrackerCallback,
        WifiDialog2.WifiDialog2Listener, DialogInterface.OnDismissListener {

    private static final String TAG = "WifiSettings";

    // Set the Provider Model is always enabled
    @VisibleForTesting
    static Boolean IS_ENABLED_PROVIDER_MODEL = true;

    // IDs of context menu
    static final int MENU_ID_CONNECT = Menu.FIRST + 1;
    @VisibleForTesting
    static final int MENU_ID_DISCONNECT = Menu.FIRST + 2;
    @VisibleForTesting
    static final int MENU_ID_FORGET = Menu.FIRST + 3;
    static final int MENU_ID_MODIFY = Menu.FIRST + 4;
    static final int MENU_ID_SHARE = Menu.FIRST + 5;

    // Max age of tracked WifiEntries
    private static final long MAX_SCAN_AGE_MILLIS = 15_000;
    // Interval between initiating WifiPickerTracker scans
    private static final long SCAN_INTERVAL_MILLIS = 10_000;

    @VisibleForTesting
    static final int ADD_NETWORK_REQUEST = 2;
    static final int CONFIG_NETWORK_REQUEST = 3;
    static final int MANAGE_SUBSCRIPTION = 4;

    private static final String PREF_KEY_EMPTY_WIFI_LIST = "wifi_empty_list";
    // TODO(b/70983952): Rename these to use WifiEntry instead of AccessPoint.
    private static final String PREF_KEY_CONNECTED_ACCESS_POINTS = "connected_access_point";
    private static final String PREF_KEY_ACCESS_POINTS = "access_points";
    private static final String PREF_KEY_CONFIGURE_WIFI_SETTINGS = "configure_wifi_settings";
    private static final String PREF_KEY_SAVED_NETWORKS = "saved_networks";
    private static final String PREF_KEY_STATUS_MESSAGE = "wifi_status_message";
    @VisibleForTesting
    static final String PREF_KEY_DATA_USAGE = "wifi_data_usage";

    private static final int REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER = 0;

    public static final int WIFI_DIALOG_ID = 1;

    // Instance state keys
    private static final String SAVE_DIALOG_MODE = "dialog_mode";
    private static final String SAVE_DIALOG_WIFIENTRY_KEY = "wifi_ap_key";

    // Cache at onCreateContextMenu and use at onContextItemSelected. Don't use it in other methods.
    private WifiEntry mSelectedWifiEntry;

    // Save the dialog details
    private int mDialogMode;
    private String mDialogWifiEntryKey;
    private WifiEntry mDialogWifiEntry;

    // This boolean extra specifies whether to enable the Next button when connected. Used by
    // account creation outside of setup wizard.
    private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";

    // Enable the Next button when a Wi-Fi network is connected.
    private boolean mEnableNextOnConnection;

    // This string extra specifies a network to open the connect dialog on, so the user can enter
    // network credentials.  This is used by quick settings for secured networks, among other
    // things.
    private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
    private String mOpenSsid;

    private static boolean isVerboseLoggingEnabled() {
        return WifiPickerTracker.isVerboseLoggingEnabled();
    }

    private boolean mIsWifiEntryListStale = true;
    private final Runnable mUpdateWifiEntryPreferencesRunnable = () -> {
        updateWifiEntryPreferences();
    };
    private final Runnable mHideProgressBarRunnable = () -> {
        setProgressBarVisible(false);
    };

    protected WifiManager mWifiManager;
    private WifiManager.ActionListener mConnectListener;
    private WifiManager.ActionListener mSaveListener;
    private WifiManager.ActionListener mForgetListener;

    /**
     * The state of {@link #isUiRestricted()} at {@link #onCreate(Bundle)}}. This is neccesary to
     * ensure that behavior is consistent if {@link #isUiRestricted()} changes. It could be changed
     * by the Test DPC tool in AFW mode.
     */
    private boolean mIsRestricted;

    private WifiEnabler mWifiEnabler;

    // Worker thread used for WifiPickerTracker work
    private HandlerThread mWorkerThread;
    private Handler mMainHandler;
    private Handler mWorkerHandler;

    @VisibleForTesting
    WifiPickerTracker mWifiPickerTracker;

    private WifiDialog2 mDialog;

    private View mProgressHeader;

    private PreferenceCategory mConnectedWifiEntryPreferenceCategory;
    private PreferenceCategory mWifiEntryPreferenceCategory;
    @VisibleForTesting
    AddWifiNetworkPreference mAddWifiNetworkPreference;
    @VisibleForTesting
    Preference mConfigureWifiSettingsPreference;
    @VisibleForTesting
    Preference mSavedNetworksPreference;
    @VisibleForTesting
    DataUsagePreference mDataUsagePreference;
    private LinkablePreference mStatusMessagePreference;

    /**
     * Tracks whether the user initiated a connection via clicking in order to autoscroll to the
     * network once connected.
     */
    private boolean mClickedConnect;

    public WifiSettings() {
        super(DISALLOW_CONFIG_WIFI);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        final Activity activity = getActivity();
        if (activity != null) {
            mProgressHeader = setPinnedHeaderView(R.layout.progress_header)
                    .findViewById(R.id.progress_bar_animation);
            setProgressBarVisible(false);
        }
        ((SettingsActivity) activity).getSwitchBar().setTitle(
                getContext().getString(R.string.wifi_settings_primary_switch_title));
    }

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        if (IS_ENABLED_PROVIDER_MODEL) {
            final Intent intent = new Intent("android.settings.NETWORK_PROVIDER_SETTINGS");
            // Add FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_CLEAR_TASK to avoid multiple
            // instances issue. (e.g. b/191956700)
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
            final Bundle extras = getActivity().getIntent().getExtras();
            if (extras != null) {
                intent.putExtras(extras);
            }
            getContext().startActivity(intent);
            finish();
            return;
        }

        // TODO(b/37429702): Add animations and preference comparator back after initial screen is
        // loaded (ODR).
        setAnimationAllowed(false);

        addPreferences();

        mIsRestricted = isUiRestricted();
    }

    private void addPreferences() {
        addPreferencesFromResource(R.xml.wifi_settings);

        mConnectedWifiEntryPreferenceCategory = findPreference(PREF_KEY_CONNECTED_ACCESS_POINTS);
        mWifiEntryPreferenceCategory = findPreference(PREF_KEY_ACCESS_POINTS);
        mConfigureWifiSettingsPreference = findPreference(PREF_KEY_CONFIGURE_WIFI_SETTINGS);
        mSavedNetworksPreference = findPreference(PREF_KEY_SAVED_NETWORKS);
        mAddWifiNetworkPreference = new AddWifiNetworkPreference(getPrefContext());
        mStatusMessagePreference = findPreference(PREF_KEY_STATUS_MESSAGE);
        mDataUsagePreference = findPreference(PREF_KEY_DATA_USAGE);
        mDataUsagePreference.setVisible(DataUsageUtils.hasWifiRadio(getContext()));
        mDataUsagePreference.setTemplate(new NetworkTemplate.Builder(
                NetworkTemplate.MATCH_WIFI).build(), 0 /*subId*/, null /*service*/);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        final Context context = getContext();
        mWorkerThread = new HandlerThread(TAG
                + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
                Process.THREAD_PRIORITY_BACKGROUND);
        mWorkerThread.start();
        final Clock elapsedRealtimeClock = new SimpleClock(ZoneOffset.UTC) {
            @Override
            public long millis() {
                return SystemClock.elapsedRealtime();
            }
        };

        mMainHandler = new Handler(Looper.getMainLooper());
        mWorkerHandler = mWorkerThread.getThreadHandler();
        mWifiPickerTracker = FeatureFactory.getFactory(context)
                .getWifiTrackerLibProvider()
                .createWifiPickerTracker(getSettingsLifecycle(), context,
                        mMainHandler, mWorkerHandler,
                        elapsedRealtimeClock,
                        MAX_SCAN_AGE_MILLIS,
                        SCAN_INTERVAL_MILLIS,
                        this);

        final Activity activity = getActivity();

        if (activity != null) {
            mWifiManager = getActivity().getSystemService(WifiManager.class);
        }

        mConnectListener = new WifiConnectListener(getActivity());

        mSaveListener = new WifiManager.ActionListener() {
            @Override
            public void onSuccess() {
            }

            @Override
            public void onFailure(int reason) {
                Activity activity = getActivity();
                if (activity != null) {
                    Toast.makeText(activity,
                            R.string.wifi_failed_save_message,
                            Toast.LENGTH_SHORT).show();
                }
            }
        };

        mForgetListener = new WifiManager.ActionListener() {
            @Override
            public void onSuccess() {
            }

            @Override
            public void onFailure(int reason) {
                Activity activity = getActivity();
                if (activity != null) {
                    Toast.makeText(activity,
                            R.string.wifi_failed_forget_message,
                            Toast.LENGTH_SHORT).show();
                }
            }
        };
        registerForContextMenu(getListView());
        setHasOptionsMenu(true);

        if (savedInstanceState != null) {
            mDialogMode = savedInstanceState.getInt(SAVE_DIALOG_MODE);
            mDialogWifiEntryKey = savedInstanceState.getString(SAVE_DIALOG_WIFIENTRY_KEY);
        }

        // If we're supposed to enable/disable the Next button based on our current connection
        // state, start it off in the right state.
        final Intent intent = getActivity().getIntent();
        mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);

        if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
            mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
        }

        // After rebooting the device, the Wi-Fi state will not be called back in the airplane
        // mode, need to call onWifiStateChanged() to update the initial state of the UI.
        onWifiStateChanged();
    }

    @Override
    public void onDestroyView() {
        if (mWifiEnabler != null) {
            mWifiEnabler.teardownSwitchController();
        }

        // remove all msg and callback in main handler and worker handler
        mMainHandler.removeCallbacksAndMessages(null);
        mWorkerHandler.removeCallbacksAndMessages(null);
        mWorkerThread.quit();

        super.onDestroyView();
    }

    @Override
    public void onStart() {
        super.onStart();

        mWifiEnabler = createWifiEnabler();

        if (mIsRestricted) {
            restrictUi();
        }
    }

    private void restrictUi() {
        if (!isUiRestrictedByOnlyAdmin()) {
            getEmptyTextView().setText(R.string.wifi_empty_list_user_restricted);
        }
        getPreferenceScreen().removeAll();
    }

    /**
     * @return new WifiEnabler
     */
    private WifiEnabler createWifiEnabler() {
        final SettingsActivity activity = (SettingsActivity) getActivity();
        return new WifiEnabler(activity, new MainSwitchBarController(activity.getSwitchBar()),
                mMetricsFeatureProvider);
    }

    @Override
    public void onResume() {
        final Activity activity = getActivity();
        super.onResume();

        // Because RestrictedSettingsFragment's onResume potentially requests authorization,
        // which changes the restriction state, recalculate it.
        final boolean alreadyImmutablyRestricted = mIsRestricted;
        mIsRestricted = isUiRestricted();
        if (!alreadyImmutablyRestricted && mIsRestricted) {
            restrictUi();
        }

        if (mWifiEnabler != null) {
            mWifiEnabler.resume(activity);
        }

        changeNextButtonState(mWifiPickerTracker.getConnectedWifiEntry() != null);
    }

    @Override
    public void onPause() {
        super.onPause();
        if (mWifiEnabler != null) {
            mWifiEnabler.pause();
        }
    }

    @Override
    public void onStop() {
        getView().removeCallbacks(mUpdateWifiEntryPreferencesRunnable);
        getView().removeCallbacks(mHideProgressBarRunnable);
        mIsWifiEntryListStale = true;
        super.onStop();
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == ADD_NETWORK_REQUEST) {
            handleAddNetworkRequest(resultCode, data);
            return;
        } else if (requestCode == REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER) {
            if (resultCode == Activity.RESULT_OK) {
                if (mDialog != null) {
                    mDialog.dismiss();
                }
            }
            return;
        } else if (requestCode == CONFIG_NETWORK_REQUEST) {
            if (resultCode == Activity.RESULT_OK) {
                final WifiConfiguration wifiConfiguration = data.getParcelableExtra(
                        ConfigureWifiEntryFragment.NETWORK_CONFIG_KEY);
                if (wifiConfiguration != null) {
                    mWifiManager.connect(wifiConfiguration,
                            new WifiConnectActionListener());
                }
            }
            return;
        } else if (requestCode == MANAGE_SUBSCRIPTION) {
            //Do nothing
            return;
        }

        final boolean formerlyRestricted = mIsRestricted;
        mIsRestricted = isUiRestricted();
        if (formerlyRestricted && !mIsRestricted
                && getPreferenceScreen().getPreferenceCount() == 0) {
            // De-restrict the ui
            addPreferences();
        }
    }

    @Override
    protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
        final RecyclerView.Adapter adapter = super.onCreateAdapter(preferenceScreen);
        adapter.setHasStableIds(true);
        return adapter;
    }

    @Override
    public int getMetricsCategory() {
        return SettingsEnums.WIFI;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // If dialog has been shown, save its state.
        if (mDialog != null) {
            outState.putInt(SAVE_DIALOG_MODE, mDialogMode);
            outState.putString(SAVE_DIALOG_WIFIENTRY_KEY, mDialogWifiEntryKey);
        }
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
        Preference preference = (Preference) view.getTag();
        if (!(preference instanceof LongPressWifiEntryPreference)) {
            // Do nothing.
            return;
        }

        // Cache the WifiEntry for onContextItemSelected. Don't use it in other methods.
        mSelectedWifiEntry = ((LongPressWifiEntryPreference) preference).getWifiEntry();

        menu.setHeaderTitle(mSelectedWifiEntry.getTitle());
        if (mSelectedWifiEntry.canConnect()) {
            menu.add(Menu.NONE, MENU_ID_CONNECT, 0 /* order */, R.string.wifi_connect);
        }

        if (mSelectedWifiEntry.canDisconnect()) {
            menu.add(Menu.NONE, MENU_ID_SHARE, 0 /* order */, R.string.share);
            menu.add(Menu.NONE, MENU_ID_DISCONNECT, 1 /* order */,
                    R.string.wifi_disconnect_button_text);
        }

        // "forget" for normal saved network. And "disconnect" for ephemeral network because it
        // could only be disconnected and be put in blocklists so it won't be used again.
        if (canForgetNetwork()) {
            menu.add(Menu.NONE, MENU_ID_FORGET, 0 /* order */, R.string.forget);
        }

        WifiConfiguration config = mSelectedWifiEntry.getWifiConfiguration();
        // Some configs are ineditable
        if (WifiUtils.isNetworkLockedDown(getActivity(), config)) {
            return;
        }

        if (mSelectedWifiEntry.isSaved() && mSelectedWifiEntry.getConnectedState()
                != WifiEntry.CONNECTED_STATE_CONNECTED) {
            menu.add(Menu.NONE, MENU_ID_MODIFY, 0 /* order */, R.string.wifi_modify);
        }
    }

    private boolean canForgetNetwork() {
        return mSelectedWifiEntry.canForget() && !WifiUtils.isNetworkLockedDown(getActivity(),
                mSelectedWifiEntry.getWifiConfiguration());
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case MENU_ID_CONNECT:
                connect(mSelectedWifiEntry, true /* editIfNoConfig */, false /* fullScreenEdit */);
                return true;
            case MENU_ID_DISCONNECT:
                mSelectedWifiEntry.disconnect(null /* callback */);
                return true;
            case MENU_ID_FORGET:
                forget(mSelectedWifiEntry);
                return true;
            case MENU_ID_SHARE:
                WifiDppUtils.showLockScreen(getContext(),
                        () -> launchWifiDppConfiguratorActivity(mSelectedWifiEntry));
                return true;
            case MENU_ID_MODIFY:
                showDialog(mSelectedWifiEntry, WifiConfigUiBase2.MODE_MODIFY);
                return true;
            default:
                return super.onContextItemSelected(item);
        }
    }

    @Override
    public boolean onPreferenceTreeClick(Preference preference) {
        // If the preference has a fragment set, open that
        if (preference.getFragment() != null) {
            preference.setOnPreferenceClickListener(null);
            return super.onPreferenceTreeClick(preference);
        }

        if (preference instanceof LongPressWifiEntryPreference) {
            final WifiEntry selectedEntry =
                    ((LongPressWifiEntryPreference) preference).getWifiEntry();

            if (selectedEntry.shouldEditBeforeConnect()) {
                launchConfigNewNetworkFragment(selectedEntry);
                return true;
            }

            connect(selectedEntry, true /* editIfNoConfig */, true /* fullScreenEdit */);
        } else if (preference == mAddWifiNetworkPreference) {
            onAddNetworkPressed();
        } else {
            return super.onPreferenceTreeClick(preference);
        }
        return true;
    }

    private void showDialog(WifiEntry wifiEntry, int dialogMode) {
        if (WifiUtils.isNetworkLockedDown(getActivity(), wifiEntry.getWifiConfiguration())
                && wifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) {
            RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(),
                    RestrictedLockUtilsInternal.getDeviceOwner(getActivity()));
            return;
        }

        if (mDialog != null) {
            removeDialog(WIFI_DIALOG_ID);
            mDialog = null;
        }

        // Save the access point and edit mode
        mDialogWifiEntry = wifiEntry;
        mDialogWifiEntryKey = wifiEntry.getKey();
        mDialogMode = dialogMode;

        showDialog(WIFI_DIALOG_ID);
    }

    @Override
    public Dialog onCreateDialog(int dialogId) {
        switch (dialogId) {
            case WIFI_DIALOG_ID:
                // modify network
                mDialog = WifiDialog2
                        .createModal(getActivity(), this, mDialogWifiEntry, mDialogMode);
                return mDialog;
            default:
                return super.onCreateDialog(dialogId);
        }
    }

    @Override
    public void onDialogShowing() {
        super.onDialogShowing();
        setOnDismissListener(this);
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        // We don't keep any dialog object when dialog was dismissed.
        mDialog = null;
        mDialogWifiEntry = null;
        mDialogWifiEntryKey = null;
    }

    @Override
    public int getDialogMetricsCategory(int dialogId) {
        switch (dialogId) {
            case WIFI_DIALOG_ID:
                return SettingsEnums.DIALOG_WIFI_AP_EDIT;
            default:
                return 0;
        }
    }

    /** Called when the state of Wifi has changed. */
    @Override
    public void onWifiStateChanged() {
        if (mIsRestricted || isFinishingOrDestroyed()) {
            return;
        }
        final int wifiState = mWifiPickerTracker.getWifiState();

        if (isVerboseLoggingEnabled()) {
            Log.i(TAG, "onWifiStateChanged called with wifi state: " + wifiState);
        }

        switch (wifiState) {
            case WifiManager.WIFI_STATE_ENABLED:
                updateWifiEntryPreferences();
                break;

            case WifiManager.WIFI_STATE_ENABLING:
                removeConnectedWifiEntryPreference();
                removeWifiEntryPreference();
                addMessagePreference(R.string.wifi_starting);
                setProgressBarVisible(true);
                break;

            case WifiManager.WIFI_STATE_DISABLING:
                removeConnectedWifiEntryPreference();
                removeWifiEntryPreference();
                addMessagePreference(R.string.wifi_stopping);
                break;

            case WifiManager.WIFI_STATE_DISABLED:
                setOffMessage();
                setAdditionalSettingsSummaries();
                setProgressBarVisible(false);
                mClickedConnect = false;
                break;
        }
    }

    @Override
    public void onWifiEntriesChanged() {
        if (isFinishingOrDestroyed()) {
            return;
        }

        if (mIsWifiEntryListStale) {
            mIsWifiEntryListStale = false;
            updateWifiEntryPreferences();
        } else {
            updateWifiEntryPreferencesDelayed();
        }
        changeNextButtonState(mWifiPickerTracker.getConnectedWifiEntry() != null);

        // Edit the Wi-Fi network of specified SSID.
        if (mOpenSsid != null) {
            Optional<WifiEntry> matchedWifiEntry = mWifiPickerTracker.getWifiEntries().stream()
                    .filter(wifiEntry -> TextUtils.equals(mOpenSsid, wifiEntry.getSsid()))
                    .filter(wifiEntry -> wifiEntry.getSecurity() != WifiEntry.SECURITY_NONE
                            && wifiEntry.getSecurity() != WifiEntry.SECURITY_OWE)
                    .filter(wifiEntry -> !wifiEntry.isSaved()
                            || isDisabledByWrongPassword(wifiEntry))
                    .findFirst();
            if (matchedWifiEntry.isPresent()) {
                mOpenSsid = null;
                launchConfigNewNetworkFragment(matchedWifiEntry.get());
            }
        }
    }

    @Override
    public void onNumSavedNetworksChanged() {
        if (isFinishingOrDestroyed()) {
            return;
        }
        setAdditionalSettingsSummaries();
    }

    @Override
    public void onNumSavedSubscriptionsChanged() {
        if (isFinishingOrDestroyed()) {
            return;
        }
        setAdditionalSettingsSummaries();
    }

    /**
     * Updates WifiEntries from {@link WifiPickerTracker#getWifiEntries()}. Adds a delay to have
     * progress bar displayed before starting to modify entries.
     */
    private void updateWifiEntryPreferencesDelayed() {
        // Safeguard from some delayed event handling
        if (getActivity() != null && !mIsRestricted
                && mWifiPickerTracker.getWifiState() == WifiManager.WIFI_STATE_ENABLED) {
            final View view = getView();
            final Handler handler = view.getHandler();
            if (handler != null && handler.hasCallbacks(mUpdateWifiEntryPreferencesRunnable)) {
                return;
            }
            setProgressBarVisible(true);
            view.postDelayed(mUpdateWifiEntryPreferencesRunnable, 300);
        }
    }

    private void updateWifiEntryPreferences() {
        // in case state has changed
        if (mWifiPickerTracker.getWifiState() != WifiManager.WIFI_STATE_ENABLED) {
            return;
        }

        boolean hasAvailableWifiEntries = false;
        mStatusMessagePreference.setVisible(false);
        mWifiEntryPreferenceCategory.setVisible(true);

        final WifiEntry connectedEntry = mWifiPickerTracker.getConnectedWifiEntry();
        mConnectedWifiEntryPreferenceCategory.setVisible(connectedEntry != null);
        if (connectedEntry != null) {
            final LongPressWifiEntryPreference connectedPref =
                    mConnectedWifiEntryPreferenceCategory.findPreference(connectedEntry.getKey());
            if (connectedPref == null || connectedPref.getWifiEntry() != connectedEntry) {
                mConnectedWifiEntryPreferenceCategory.removeAll();
                final ConnectedWifiEntryPreference pref =
                        new ConnectedWifiEntryPreference(getPrefContext(), connectedEntry, this);
                pref.setKey(connectedEntry.getKey());
                pref.refresh();
                mConnectedWifiEntryPreferenceCategory.addPreference(pref);
                pref.setOnPreferenceClickListener(preference -> {
                    if (connectedEntry.canSignIn()) {
                        connectedEntry.signIn(null /* callback */);
                    } else {
                        launchNetworkDetailsFragment(pref);
                    }
                    return true;
                });
                pref.setOnGearClickListener(preference -> {
                    launchNetworkDetailsFragment(pref);
                });

                if (mClickedConnect) {
                    mClickedConnect = false;
                    scrollToPreference(mConnectedWifiEntryPreferenceCategory);
                }
            }
        } else {
            mConnectedWifiEntryPreferenceCategory.removeAll();
        }

        int index = 0;
        cacheRemoveAllPrefs(mWifiEntryPreferenceCategory);
        List<WifiEntry> wifiEntries = mWifiPickerTracker.getWifiEntries();
        for (WifiEntry wifiEntry : wifiEntries) {
            hasAvailableWifiEntries = true;

            String key = wifiEntry.getKey();
            LongPressWifiEntryPreference pref =
                    (LongPressWifiEntryPreference) getCachedPreference(key);
            if (pref != null) {
                if (pref.getWifiEntry() == wifiEntry) {
                    pref.setOrder(index++);
                    continue;
                } else {
                    // Create a new preference if the underlying WifiEntry object has changed
                    removePreference(key);
                }
            }

            pref = createLongPressWifiEntryPreference(wifiEntry);
            pref.setKey(wifiEntry.getKey());
            pref.setOrder(index++);
            pref.refresh();

            if (wifiEntry.getHelpUriString() != null) {
                pref.setOnButtonClickListener(preference -> {
                    openSubscriptionHelpPage(wifiEntry);
                });
            }
            mWifiEntryPreferenceCategory.addPreference(pref);
        }
        removeCachedPrefs(mWifiEntryPreferenceCategory);

        if (!hasAvailableWifiEntries) {
            setProgressBarVisible(true);
            Preference pref = new Preference(getPrefContext());
            pref.setSelectable(false);
            pref.setSummary(R.string.wifi_empty_list_wifi_on);
            pref.setOrder(index++);
            pref.setKey(PREF_KEY_EMPTY_WIFI_LIST);
            mWifiEntryPreferenceCategory.addPreference(pref);
        } else {
            // Continuing showing progress bar for an additional delay to overlap with animation
            getView().postDelayed(mHideProgressBarRunnable, 1700 /* delay millis */);
        }

        mAddWifiNetworkPreference.setOrder(index++);
        mWifiEntryPreferenceCategory.addPreference(mAddWifiNetworkPreference);
        setAdditionalSettingsSummaries();
    }

    private void launchNetworkDetailsFragment(LongPressWifiEntryPreference pref) {
        final WifiEntry wifiEntry = pref.getWifiEntry();
        final Context context = getContext();
        final CharSequence title =
                FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER)
                        ? wifiEntry.getTitle()
                        : context.getText(R.string.pref_title_network_details);

        final Bundle bundle = new Bundle();
        bundle.putString(WifiNetworkDetailsFragment.KEY_CHOSEN_WIFIENTRY_KEY, wifiEntry.getKey());

        new SubSettingLauncher(context)
                .setTitleText(title)
                .setDestination(WifiNetworkDetailsFragment.class.getName())
                .setArguments(bundle)
                .setSourceMetricsCategory(getMetricsCategory())
                .launch();
    }

    @VisibleForTesting
    LongPressWifiEntryPreference createLongPressWifiEntryPreference(WifiEntry wifiEntry) {
        return new LongPressWifiEntryPreference(getPrefContext(), wifiEntry, this);
    }

    private void launchAddNetworkFragment() {
        new SubSettingLauncher(getContext())
                .setTitleRes(R.string.wifi_add_network)
                .setDestination(AddNetworkFragment.class.getName())
                .setSourceMetricsCategory(getMetricsCategory())
                .setResultListener(this, ADD_NETWORK_REQUEST)
                .launch();
    }

    /** Removes all preferences and hide the {@link #mConnectedWifiEntryPreferenceCategory}. */
    private void removeConnectedWifiEntryPreference() {
        mConnectedWifiEntryPreferenceCategory.removeAll();
        mConnectedWifiEntryPreferenceCategory.setVisible(false);
    }

    private void removeWifiEntryPreference() {
        mWifiEntryPreferenceCategory.removeAll();
        mWifiEntryPreferenceCategory.setVisible(false);
    }

    @VisibleForTesting
    void setAdditionalSettingsSummaries() {
        mConfigureWifiSettingsPreference.setSummary(getString(
                isWifiWakeupEnabled()
                        ? R.string.wifi_configure_settings_preference_summary_wakeup_on
                        : R.string.wifi_configure_settings_preference_summary_wakeup_off));

        final int numSavedNetworks = mWifiPickerTracker.getNumSavedNetworks();
        final int numSavedSubscriptions = mWifiPickerTracker.getNumSavedSubscriptions();
        if (numSavedNetworks + numSavedSubscriptions > 0) {
            mSavedNetworksPreference.setVisible(true);
            mSavedNetworksPreference.setSummary(
                    getSavedNetworkSettingsSummaryText(numSavedNetworks, numSavedSubscriptions));
        } else {
            mSavedNetworksPreference.setVisible(false);
        }
    }

    private String getSavedNetworkSettingsSummaryText(
            int numSavedNetworks, int numSavedSubscriptions) {
        if (getContext() == null) {
            return "";
        }
        if (numSavedSubscriptions == 0) {
            return StringUtil.getIcuPluralsString(getContext(), numSavedNetworks,
                    R.string.wifi_saved_access_points_summary);
        } else if (numSavedNetworks == 0) {
            return StringUtil.getIcuPluralsString(getContext(), numSavedSubscriptions,
                    R.string.wifi_saved_passpoint_access_points_summary);
        } else {
            final int numTotalEntries = numSavedNetworks + numSavedSubscriptions;
            return StringUtil.getIcuPluralsString(getContext(), numTotalEntries,
                    R.string.wifi_saved_all_access_points_summary);
        }
    }

    private boolean isWifiWakeupEnabled() {
        final Context context = getContext();
        final PowerManager powerManager = context.getSystemService(PowerManager.class);
        final ContentResolver contentResolver = context.getContentResolver();
        return mWifiManager.isAutoWakeupEnabled()
                && mWifiManager.isScanAlwaysAvailable()
                && Settings.Global.getInt(contentResolver,
                Settings.Global.AIRPLANE_MODE_ON, 0) == 0
                && !powerManager.isPowerSaveMode();
    }

    private void setOffMessage() {
        final CharSequence title = getText(R.string.wifi_empty_list_wifi_off);
        // Don't use WifiManager.isScanAlwaysAvailable() to check the Wi-Fi scanning mode. Instead,
        // read the system settings directly. Because when the device is in Airplane mode, even if
        // Wi-Fi scanning mode is on, WifiManager.isScanAlwaysAvailable() still returns "off".
        // TODO(b/149421497): Fix this?
        final boolean wifiScanningMode = mWifiManager.isScanAlwaysAvailable();
        final CharSequence description = wifiScanningMode ? getText(R.string.wifi_scan_notify_text)
                : getText(R.string.wifi_scan_notify_text_scanning_off);
        final LinkifyUtils.OnClickListener clickListener =
                () -> new SubSettingLauncher(getContext())
                        .setDestination(WifiScanningFragment.class.getName())
                        .setTitleRes(R.string.location_scanning_wifi_always_scanning_title)
                        .setSourceMetricsCategory(getMetricsCategory())
                        .launch();
        mStatusMessagePreference.setText(title, description, clickListener);
        removeConnectedWifiEntryPreference();
        removeWifiEntryPreference();
        mStatusMessagePreference.setVisible(true);
    }

    private void addMessagePreference(int messageId) {
        mStatusMessagePreference.setTitle(messageId);
        mStatusMessagePreference.setVisible(true);

    }

    protected void setProgressBarVisible(boolean visible) {
        if (mProgressHeader != null) {
            mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
        }
    }

    @VisibleForTesting
    void handleAddNetworkRequest(int result, Intent data) {
        if (result == Activity.RESULT_OK) {
            handleAddNetworkSubmitEvent(data);
        }
    }

    private void handleAddNetworkSubmitEvent(Intent data) {
        final WifiConfiguration wifiConfiguration = data.getParcelableExtra(
                AddNetworkFragment.WIFI_CONFIG_KEY);
        if (wifiConfiguration != null) {
            mWifiManager.save(wifiConfiguration, mSaveListener);
        }
    }

    /**
     * Called when "add network" button is pressed.
     */
    private void onAddNetworkPressed() {
        launchAddNetworkFragment();
    }

    @Override
    public int getHelpResource() {
        return R.string.help_url_wifi;
    }

    /**
     * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
     * Wi-Fi setup screens, not in usual wifi settings screen.
     *
     * @param enabled true when the device is connected to a wifi network.
     */
    @VisibleForTesting
    void changeNextButtonState(boolean enabled) {
        if (mEnableNextOnConnection && hasNextButton()) {
            getNextButton().setEnabled(enabled);
        }
    }

    @Override
    public void onForget(WifiDialog2 dialog) {
        forget(dialog.getWifiEntry());
    }

    @Override
    public void onSubmit(WifiDialog2 dialog) {
        final int dialogMode = dialog.getMode();
        final WifiConfiguration config = dialog.getController().getConfig();
        final WifiEntry wifiEntry = dialog.getWifiEntry();

        if (dialogMode == WifiConfigUiBase2.MODE_MODIFY) {
            if (config == null) {
                Toast.makeText(getContext(), R.string.wifi_failed_save_message,
                        Toast.LENGTH_SHORT).show();
            } else {
                mWifiManager.save(config, mSaveListener);
            }
        } else if (dialogMode == WifiConfigUiBase2.MODE_CONNECT
                || (dialogMode == WifiConfigUiBase2.MODE_VIEW && wifiEntry.canConnect())) {
            if (config == null) {
                connect(wifiEntry, false /* editIfNoConfig */,
                        false /* fullScreenEdit*/);
            } else {
                mWifiManager.connect(config, new WifiConnectActionListener());
            }
        }
    }

    @Override
    public void onScan(WifiDialog2 dialog, String ssid) {
        // Launch QR code scanner to join a network.
        startActivityForResult(
                WifiDppUtils.getEnrolleeQrCodeScannerIntent(dialog.getContext(), ssid),
                REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER);
    }

    private void forget(WifiEntry wifiEntry) {
        mMetricsFeatureProvider.action(getActivity(), SettingsEnums.ACTION_WIFI_FORGET);
        wifiEntry.forget(null /* callback */);
    }

    @VisibleForTesting
    void connect(WifiEntry wifiEntry, boolean editIfNoConfig, boolean fullScreenEdit) {
        mMetricsFeatureProvider.action(getActivity(), SettingsEnums.ACTION_WIFI_CONNECT,
                wifiEntry.isSaved());

        // If it's an unsaved secure WifiEntry, it will callback
        // ConnectCallback#onConnectResult with ConnectCallback#CONNECT_STATUS_FAILURE_NO_CONFIG
        wifiEntry.connect(new WifiEntryConnectCallback(wifiEntry, editIfNoConfig,
                fullScreenEdit));
    }

    private class WifiConnectActionListener implements WifiManager.ActionListener {
        @Override
        public void onSuccess() {
            mClickedConnect = true;
        }

        @Override
        public void onFailure(int reason) {
            if (isFinishingOrDestroyed()) {
                return;
            }
            Toast.makeText(getContext(), R.string.wifi_failed_connect_message, Toast.LENGTH_SHORT)
                    .show();
        }
    }

    public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
            new BaseSearchIndexProvider(R.xml.wifi_settings) {
                @Override
                protected boolean isPageSearchEnabled(Context context) {
                    return !IS_ENABLED_PROVIDER_MODEL;
                }

                @Override
                public List<String> getNonIndexableKeys(Context context) {
                    final List<String> keys = super.getNonIndexableKeys(context);

                    final WifiManager wifiManager = context.getSystemService(WifiManager.class);
                    if (WifiSavedConfigUtils.getAllConfigsCount(context, wifiManager) == 0) {
                        keys.add(PREF_KEY_SAVED_NETWORKS);
                    }

                    if (!DataUsageUtils.hasWifiRadio(context)) {
                        keys.add(PREF_KEY_DATA_USAGE);
                    }
                    return keys;
                }
            };

    private class WifiEntryConnectCallback implements ConnectCallback {
        final WifiEntry mConnectWifiEntry;
        final boolean mEditIfNoConfig;
        final boolean mFullScreenEdit;

        WifiEntryConnectCallback(WifiEntry connectWifiEntry, boolean editIfNoConfig,
                boolean fullScreenEdit) {
            mConnectWifiEntry = connectWifiEntry;
            mEditIfNoConfig = editIfNoConfig;
            mFullScreenEdit = fullScreenEdit;
        }

        @Override
        public void onConnectResult(@ConnectStatus int status) {
            if (isFinishingOrDestroyed()) {
                return;
            }

            if (status == ConnectCallback.CONNECT_STATUS_SUCCESS) {
                mClickedConnect = true;
            } else if (status == ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) {
                if (mEditIfNoConfig) {
                    // Edit an unsaved secure Wi-Fi network.
                    if (mFullScreenEdit) {
                        launchConfigNewNetworkFragment(mConnectWifiEntry);
                    } else {
                        showDialog(mConnectWifiEntry, WifiConfigUiBase2.MODE_CONNECT);
                    }
                }
            } else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) {
                Toast.makeText(getContext(), R.string.wifi_failed_connect_message,
                        Toast.LENGTH_SHORT).show();
            }
        }
    }

    private void launchConfigNewNetworkFragment(WifiEntry wifiEntry) {
        final Bundle bundle = new Bundle();
        bundle.putString(WifiNetworkDetailsFragment.KEY_CHOSEN_WIFIENTRY_KEY,
                wifiEntry.getKey());
        new SubSettingLauncher(getContext())
                .setTitleText(wifiEntry.getTitle())
                .setDestination(ConfigureWifiEntryFragment.class.getName())
                .setArguments(bundle)
                .setSourceMetricsCategory(getMetricsCategory())
                .setResultListener(WifiSettings.this, CONFIG_NETWORK_REQUEST)
                .launch();
    }

    private void launchWifiDppConfiguratorActivity(WifiEntry wifiEntry) {
        final Intent intent = WifiDppUtils.getConfiguratorQrCodeGeneratorIntentOrNull(getContext(),
                mWifiManager, wifiEntry);

        if (intent == null) {
            Log.e(TAG, "Launch Wi-Fi DPP QR code generator with a wrong Wi-Fi network!");
        } else {
            mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
                    SettingsEnums.ACTION_SETTINGS_SHARE_WIFI_QR_CODE,
                    SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR,
                    /* key */ null,
                    /* value */ Integer.MIN_VALUE);

            startActivity(intent);
        }
    }

    /** Helper method to return whether a WifiEntry is disabled due to a wrong password */
    private static boolean isDisabledByWrongPassword(WifiEntry wifiEntry) {
        WifiConfiguration config = wifiEntry.getWifiConfiguration();
        if (config == null) {
            return false;
        }
        WifiConfiguration.NetworkSelectionStatus networkStatus =
                config.getNetworkSelectionStatus();
        if (networkStatus == null
                || networkStatus.getNetworkSelectionStatus() == NETWORK_SELECTION_ENABLED) {
            return false;
        }
        int reason = networkStatus.getNetworkSelectionDisableReason();
        return WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD == reason;
    }

    @VisibleForTesting
    void openSubscriptionHelpPage(WifiEntry wifiEntry) {
        final Intent intent = getHelpIntent(getContext(), wifiEntry.getHelpUriString());
        if (intent != null) {
            try {
                startActivityForResult(intent, MANAGE_SUBSCRIPTION);
            } catch (ActivityNotFoundException e) {
                Log.e(TAG, "Activity was not found for intent, " + intent.toString());
            }
        }
    }

    @VisibleForTesting
    Intent getHelpIntent(Context context, String helpUrlString) {
        return HelpUtils.getHelpIntent(context, helpUrlString, context.getClass().getName());
    }
}
