Merge "[Wi-Fi] Create WifiNetworkDetailsFragment related version 2 files for WifiTracker2 development"
diff --git a/res/xml/wifi_display_saved_access_points2.xml b/res/xml/wifi_display_saved_access_points2.xml
new file mode 100644
index 0000000..02c732a
--- /dev/null
+++ b/res/xml/wifi_display_saved_access_points2.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:key="saved_access_points"
+    android:title="@string/wifi_saved_access_points_label">
+
+    <PreferenceCategory
+        android:key="subscribed_access_points_category"
+        android:title="@string/wifi_subscribed_access_points_tab"
+        settings:controller="com.android.settings.wifi.savedaccesspoints2.SubscribedAccessPointsPreferenceController2"/>
+
+    <PreferenceCategory
+        android:key="saved_access_points_category"
+        android:title="@string/wifi_saved_access_points_tab"
+        settings:controller="com.android.settings.wifi.savedaccesspoints2.SavedAccessPointsPreferenceController2"/>
+
+</PreferenceScreen>
diff --git a/res/xml/wifi_settings2.xml b/res/xml/wifi_settings2.xml
new file mode 100644
index 0000000..8cd3857
--- /dev/null
+++ b/res/xml/wifi_settings2.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<PreferenceScreen
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:settings="http://schemas.android.com/apk/res-auto"
+        android:title="@string/wifi_settings"
+        settings:keywords="@string/keywords_wifi">
+
+    <com.android.settings.wifi.LinkablePreference
+        android:key="wifi_status_message"/>
+
+    <PreferenceCategory
+        android:key="connected_access_point"
+        android:layout="@layout/preference_category_no_label"/>
+
+    <PreferenceCategory
+        android:key="access_points"
+        android:layout="@layout/preference_category_no_label"/>
+
+    <Preference
+        android:key="configure_settings"
+        android:title="@string/wifi_configure_settings_preference_title"
+        settings:allowDividerAbove="true"
+        android:fragment="com.android.settings.wifi.ConfigureWifiSettings"/>
+
+    <Preference
+        android:key="saved_networks"
+        android:title="@string/wifi_saved_access_points_label"
+        android:fragment="com.android.settings.wifi.savedaccesspoints2.SavedAccessPointsWifiSettings2"/>
+
+    <com.android.settings.datausage.DataUsagePreference
+        android:key="wifi_data_usage"
+        android:title="@string/wifi_data_usage"/>
+</PreferenceScreen>
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index 0934ba9..98c6b87 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -146,6 +146,7 @@
 import com.android.settings.wifi.calling.WifiCallingSettings;
 import com.android.settings.wifi.p2p.WifiP2pSettings;
 import com.android.settings.wifi.savedaccesspoints.SavedAccessPointsWifiSettings;
+import com.android.settings.wifi.savedaccesspoints2.SavedAccessPointsWifiSettings2;
 import com.android.settings.wifi.tether.WifiTetherSettings;
 
 public class SettingsGateway {
@@ -161,6 +162,7 @@
             WifiSettings2.class.getName(),
             ConfigureWifiSettings.class.getName(),
             SavedAccessPointsWifiSettings.class.getName(),
+            SavedAccessPointsWifiSettings2.class.getName(),
             TetherSettings.class.getName(),
             WifiP2pSettings.class.getName(),
             WifiTetherSettings.class.getName(),
diff --git a/src/com/android/settings/panel/WifiPanel.java b/src/com/android/settings/panel/WifiPanel.java
index 36ee117..0efa804 100644
--- a/src/com/android/settings/panel/WifiPanel.java
+++ b/src/com/android/settings/panel/WifiPanel.java
@@ -20,12 +20,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
+import android.util.FeatureFlagUtils;
 
 import com.android.settings.R;
 import com.android.settings.SubSettings;
 import com.android.settings.slices.CustomSliceRegistry;
 import com.android.settings.slices.SliceBuilderUtils;
 import com.android.settings.wifi.WifiSettings;
+import com.android.settings.wifi.WifiSettings2;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -61,11 +63,20 @@
     public Intent getSeeMoreIntent() {
         final String screenTitle =
                 mContext.getText(R.string.wifi_settings).toString();
-        final Intent intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext,
-                WifiSettings.class.getName(),
-                null /* key */,
-                screenTitle,
-                SettingsEnums.WIFI);
+        Intent intent;
+        if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) {
+            intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext,
+                    WifiSettings2.class.getName(),
+                    null /* key */,
+                    screenTitle,
+                    SettingsEnums.WIFI);
+        } else {
+            intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext,
+                    WifiSettings.class.getName(),
+                    null /* key */,
+                    screenTitle,
+                    SettingsEnums.WIFI);
+        }
         intent.setClassName(mContext.getPackageName(), SubSettings.class.getName());
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         return intent;
diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java
index 0ba2a94..a3435e6 100644
--- a/src/com/android/settings/wifi/WifiConfigController.java
+++ b/src/com/android/settings/wifi/WifiConfigController.java
@@ -44,6 +44,7 @@
 import android.text.SpannableString;
 import android.text.TextUtils;
 import android.text.TextWatcher;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
@@ -70,6 +71,7 @@
 import com.android.settings.ProxySelector;
 import com.android.settings.R;
 import com.android.settings.wifi.details.WifiPrivacyPreferenceController;
+import com.android.settings.wifi.details2.WifiPrivacyPreferenceController2;
 import com.android.settings.wifi.dpp.WifiDppUtils;
 import com.android.settingslib.Utils;
 import com.android.settingslib.utils.ThreadUtils;
@@ -289,9 +291,14 @@
                         ? HIDDEN_NETWORK
                         : NOT_HIDDEN_NETWORK);
 
-                final int prefMacValue =
-                        WifiPrivacyPreferenceController.translateMacRandomizedValueToPrefValue(
-                                config.macRandomizationSetting);
+                int prefMacValue;
+                if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) {
+                    prefMacValue = WifiPrivacyPreferenceController2
+                            .translateMacRandomizedValueToPrefValue(config.macRandomizationSetting);
+                } else {
+                    prefMacValue = WifiPrivacyPreferenceController
+                            .translateMacRandomizedValueToPrefValue(config.macRandomizationSetting);
+                }
                 mPrivacySettingsSpinner.setSelection(prefMacValue);
 
                 if (config.getIpAssignment() == IpAssignment.STATIC) {
@@ -843,9 +850,14 @@
         }
 
         if (mPrivacySettingsSpinner != null) {
-            final int macValue =
-                    WifiPrivacyPreferenceController.translatePrefValueToMacRandomizedValue(
-                            mPrivacySettingsSpinner.getSelectedItemPosition());
+            int macValue;
+            if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) {
+                macValue = WifiPrivacyPreferenceController2.translatePrefValueToMacRandomizedValue(
+                        mPrivacySettingsSpinner.getSelectedItemPosition());
+            } else {
+                macValue = WifiPrivacyPreferenceController.translatePrefValueToMacRandomizedValue(
+                        mPrivacySettingsSpinner.getSelectedItemPosition());
+            }
             config.macRandomizationSetting = macValue;
         }
 
diff --git a/src/com/android/settings/wifi/WifiConnectionPreferenceController.java b/src/com/android/settings/wifi/WifiConnectionPreferenceController.java
index 36c4455..2c6feac 100644
--- a/src/com/android/settings/wifi/WifiConnectionPreferenceController.java
+++ b/src/com/android/settings/wifi/WifiConnectionPreferenceController.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.os.Bundle;
+import android.util.FeatureFlagUtils;
 
 import androidx.preference.PreferenceGroup;
 import androidx.preference.PreferenceScreen;
@@ -25,6 +26,7 @@
 import com.android.settings.R;
 import com.android.settings.core.SubSettingLauncher;
 import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
+import com.android.settings.wifi.details2.WifiNetworkDetailsFragment2;
 import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.wifi.AccessPoint;
@@ -128,17 +130,31 @@
             mPreference.refresh();
             mPreference.setOrder(order);
 
-            mPreference.setOnPreferenceClickListener(pref -> {
-                Bundle args = new Bundle();
-                mPreference.getAccessPoint().saveWifiState(args);
-                new SubSettingLauncher(mPrefContext)
-                        .setTitleRes(R.string.pref_title_network_details)
-                        .setDestination(WifiNetworkDetailsFragment.class.getName())
-                        .setArguments(args)
-                        .setSourceMetricsCategory(mMetricsCategory)
-                        .launch();
-                return true;
-            });
+            if (FeatureFlagUtils.isEnabled(mPrefContext, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) {
+                mPreference.setOnPreferenceClickListener(pref -> {
+                    Bundle args = new Bundle();
+                    mPreference.getAccessPoint().saveWifiState(args);
+                    new SubSettingLauncher(mPrefContext)
+                            .setTitleRes(R.string.pref_title_network_details)
+                            .setDestination(WifiNetworkDetailsFragment2.class.getName())
+                            .setArguments(args)
+                            .setSourceMetricsCategory(mMetricsCategory)
+                            .launch();
+                    return true;
+                });
+            } else {
+                mPreference.setOnPreferenceClickListener(pref -> {
+                    Bundle args = new Bundle();
+                    mPreference.getAccessPoint().saveWifiState(args);
+                    new SubSettingLauncher(mPrefContext)
+                            .setTitleRes(R.string.pref_title_network_details)
+                            .setDestination(WifiNetworkDetailsFragment.class.getName())
+                            .setArguments(args)
+                            .setSourceMetricsCategory(mMetricsCategory)
+                            .launch();
+                    return true;
+                });
+            }
             mPreferenceGroup.addPreference(mPreference);
         }
     }
diff --git a/src/com/android/settings/wifi/WifiPickerActivity.java b/src/com/android/settings/wifi/WifiPickerActivity.java
index a590a0f..adfc7ec 100644
--- a/src/com/android/settings/wifi/WifiPickerActivity.java
+++ b/src/com/android/settings/wifi/WifiPickerActivity.java
@@ -16,6 +16,7 @@
 package com.android.settings.wifi;
 
 import android.content.Intent;
+import android.util.FeatureFlagUtils;
 
 import androidx.preference.PreferenceFragmentCompat;
 
@@ -24,6 +25,7 @@
 import com.android.settings.SettingsActivity;
 import com.android.settings.wifi.p2p.WifiP2pSettings;
 import com.android.settings.wifi.savedaccesspoints.SavedAccessPointsWifiSettings;
+import com.android.settings.wifi.savedaccesspoints2.SavedAccessPointsWifiSettings2;
 
 public class WifiPickerActivity extends SettingsActivity implements ButtonBarHandler {
 
@@ -39,9 +41,18 @@
 
     @Override
     protected boolean isValidFragment(String fragmentName) {
+        boolean isSavedAccessPointsWifiSettings;
+        if (FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) {
+            isSavedAccessPointsWifiSettings =
+                    SavedAccessPointsWifiSettings2.class.getName().equals(fragmentName);
+        } else {
+            isSavedAccessPointsWifiSettings =
+                    SavedAccessPointsWifiSettings.class.getName().equals(fragmentName);
+        }
+
         if (WifiSettings.class.getName().equals(fragmentName)
                 || WifiP2pSettings.class.getName().equals(fragmentName)
-                || SavedAccessPointsWifiSettings.class.getName().equals(fragmentName)) {
+                || isSavedAccessPointsWifiSettings) {
             return true;
         }
         return false;
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index 28b668f..a5b380e 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -68,6 +68,7 @@
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.widget.SwitchBarController;
 import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
+import com.android.settings.wifi.details2.WifiNetworkDetailsFragment2;
 import com.android.settings.wifi.dpp.WifiDppUtils;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedLockUtilsInternal;
@@ -954,12 +955,21 @@
                         ? accessPoint.getTitle()
                         : context.getText(R.string.pref_title_network_details);
 
-        new SubSettingLauncher(getContext())
-                .setTitleText(title)
-                .setDestination(WifiNetworkDetailsFragment.class.getName())
-                .setArguments(pref.getExtras())
-                .setSourceMetricsCategory(getMetricsCategory())
-                .launch();
+        if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) {
+            new SubSettingLauncher(getContext())
+                    .setTitleText(title)
+                    .setDestination(WifiNetworkDetailsFragment2.class.getName())
+                    .setArguments(pref.getExtras())
+                    .setSourceMetricsCategory(getMetricsCategory())
+                    .launch();
+        } else {
+            new SubSettingLauncher(getContext())
+                    .setTitleText(title)
+                    .setDestination(WifiNetworkDetailsFragment.class.getName())
+                    .setArguments(pref.getExtras())
+                    .setSourceMetricsCategory(getMetricsCategory())
+                    .launch();
+        }
     }
 
     private Network getCurrentWifiNetwork() {
diff --git a/src/com/android/settings/wifi/WifiSettings2.java b/src/com/android/settings/wifi/WifiSettings2.java
index 2d26cc4..f2cd1cf 100644
--- a/src/com/android/settings/wifi/WifiSettings2.java
+++ b/src/com/android/settings/wifi/WifiSettings2.java
@@ -190,7 +190,7 @@
     }
 
     private void addPreferences() {
-        addPreferencesFromResource(R.xml.wifi_settings);
+        addPreferencesFromResource(R.xml.wifi_settings2);
 
         mConnectedWifiEntryPreferenceCategory = findPreference(PREF_KEY_CONNECTED_ACCESS_POINTS);
         mWifiEntryPreferenceCategory = findPreference(PREF_KEY_ACCESS_POINTS);
diff --git a/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java b/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java
new file mode 100644
index 0000000..de831b7
--- /dev/null
+++ b/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 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.details2;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.WifiManager;
+import android.util.Log;
+
+import androidx.preference.Preference;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.wifi.dpp.WifiDppUtils;
+import com.android.settingslib.wifi.AccessPoint;
+
+/**
+ * {@link BasePreferenceController} that launches Wi-Fi Easy Connect configurator flow
+ */
+public class AddDevicePreferenceController2 extends BasePreferenceController {
+
+    private static final String TAG = "AddDevicePreferenceController2";
+
+    private static final String KEY_ADD_DEVICE = "add_device_to_network";
+
+    private AccessPoint mAccessPoint;
+    private WifiManager mWifiManager;
+
+    public AddDevicePreferenceController2(Context context) {
+        super(context, KEY_ADD_DEVICE);
+
+        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+    }
+
+    /**
+     * Initiate with an {@link AccessPoint}.
+     */
+    public AddDevicePreferenceController2 init(AccessPoint accessPoint) {
+        mAccessPoint = accessPoint;
+
+        return this;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        if (WifiDppUtils.isSupportConfiguratorQrCodeScanner(mContext, mAccessPoint)) {
+            return AVAILABLE;
+        } else {
+            return CONDITIONALLY_UNAVAILABLE;
+        }
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        if (KEY_ADD_DEVICE.equals(preference.getKey())) {
+            WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorQrCodeScanner());
+            return true; /* click is handled */
+        }
+
+        return false; /* click is not handled */
+    }
+
+    private void launchWifiDppConfiguratorQrCodeScanner() {
+        final Intent intent = WifiDppUtils.getConfiguratorQrCodeScannerIntentOrNull(mContext,
+                mWifiManager, mAccessPoint);
+
+        if (intent == null) {
+            Log.e(TAG, "Launch Wi-Fi QR code scanner with a wrong Wi-Fi network!");
+        } else {
+            mContext.startActivity(intent);
+        }
+    }
+}
diff --git a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
new file mode 100644
index 0000000..1d6e457
--- /dev/null
+++ b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
@@ -0,0 +1,1207 @@
+/*
+ * Copyright (C) 2019 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.details2;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.settings.SettingsEnums;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.VectorDrawable;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.net.NetworkUtils;
+import android.net.RouteInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.CountDownTimer;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
+import android.util.Log;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.core.text.BidiFormatter;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.core.FeatureFlags;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.datausage.WifiDataUsageSummaryPreferenceController;
+import com.android.settings.widget.EntityHeaderController;
+import com.android.settings.wifi.WifiDialog;
+import com.android.settings.wifi.WifiDialog.WifiDialogListener;
+import com.android.settings.wifi.WifiUtils;
+import com.android.settings.wifi.dpp.WifiDppUtils;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+import com.android.settingslib.widget.ActionButtonsPreference;
+import com.android.settingslib.widget.LayoutPreference;
+import com.android.settingslib.wifi.AccessPoint;
+import com.android.settingslib.wifi.WifiTracker;
+import com.android.settingslib.wifi.WifiTrackerFactory;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.time.Duration;
+import java.util.StringJoiner;
+import java.util.stream.Collectors;
+
+/**
+ * Controller for logic pertaining to displaying Wifi information for the
+ * {@link WifiNetworkDetailsFragment}.
+ */
+public class WifiDetailPreferenceController2 extends AbstractPreferenceController
+        implements PreferenceControllerMixin, WifiDialogListener, LifecycleObserver, OnPause,
+        OnResume {
+
+    private static final String TAG = "WifiDetailsPrefCtrl2";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    @VisibleForTesting
+    static final String KEY_HEADER = "connection_header";
+    @VisibleForTesting
+    static final String KEY_DATA_USAGE_HEADER = "status_header";
+    @VisibleForTesting
+    static final String KEY_BUTTONS_PREF = "buttons";
+    @VisibleForTesting
+    static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength";
+    @VisibleForTesting
+    static final String KEY_TX_LINK_SPEED = "tx_link_speed";
+    @VisibleForTesting
+    static final String KEY_RX_LINK_SPEED = "rx_link_speed";
+    @VisibleForTesting
+    static final String KEY_FREQUENCY_PREF = "frequency";
+    @VisibleForTesting
+    static final String KEY_SECURITY_PREF = "security";
+    @VisibleForTesting
+    static final String KEY_SSID_PREF = "ssid";
+    @VisibleForTesting
+    static final String KEY_MAC_ADDRESS_PREF = "mac_address";
+    @VisibleForTesting
+    static final String KEY_IP_ADDRESS_PREF = "ip_address";
+    @VisibleForTesting
+    static final String KEY_GATEWAY_PREF = "gateway";
+    @VisibleForTesting
+    static final String KEY_SUBNET_MASK_PREF = "subnet_mask";
+    @VisibleForTesting
+    static final String KEY_DNS_PREF = "dns";
+    @VisibleForTesting
+    static final String KEY_IPV6_CATEGORY = "ipv6_category";
+    @VisibleForTesting
+    static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses";
+
+    private static final int STATE_NONE = 1;
+    private static final int STATE_ENABLE_WIFI = 2;
+    private static final int STATE_ENABLE_WIFI_FAILED = 3;
+    private static final int STATE_CONNECTING = 4;
+    private static final int STATE_CONNECTED = 5;
+    private static final int STATE_FAILED = 6;
+    private static final int STATE_NOT_IN_RANGE = 7;
+    private static final int STATE_DISCONNECTED = 8;
+    private static final long TIMEOUT = Duration.ofSeconds(10).toMillis();
+
+    // Be static to avoid too much object not be reset.
+    @VisibleForTesting
+    static CountDownTimer sTimer;
+
+    private AccessPoint mAccessPoint;
+    private final ConnectivityManager mConnectivityManager;
+    private final PreferenceFragmentCompat mFragment;
+    private final Handler mHandler;
+    private LinkProperties mLinkProperties;
+    private Network mNetwork;
+    private NetworkInfo mNetworkInfo;
+    private NetworkCapabilities mNetworkCapabilities;
+    private int mRssiSignalLevel = -1;
+    private String[] mSignalStr;
+    private WifiConfiguration mWifiConfig;
+    private WifiInfo mWifiInfo;
+    private final WifiManager mWifiManager;
+    private final WifiTracker mWifiTracker;
+    private final MetricsFeatureProvider mMetricsFeatureProvider;
+    private boolean mIsOutOfRange;
+    private boolean mIsEphemeral;
+    private boolean mConnected;
+    private int mConnectingState;
+    private WifiManager.ActionListener mConnectListener;
+
+    // UI elements - in order of appearance
+    private ActionButtonsPreference mButtonsPref;
+    private EntityHeaderController mEntityHeaderController;
+    private Preference mSignalStrengthPref;
+    private Preference mTxLinkSpeedPref;
+    private Preference mRxLinkSpeedPref;
+    private Preference mFrequencyPref;
+    private Preference mSecurityPref;
+    private Preference mSsidPref;
+    private Preference mMacAddressPref;
+    private Preference mIpAddressPref;
+    private Preference mGatewayPref;
+    private Preference mSubnetPref;
+    private Preference mDnsPref;
+    private PreferenceCategory mIpv6Category;
+    private Preference mIpv6AddressPref;
+    private Lifecycle mLifecycle;
+    Preference mDataUsageSummaryPref;
+    WifiDataUsageSummaryPreferenceController mSummaryHeaderController;
+
+    private final IconInjector mIconInjector;
+    private final IntentFilter mFilter;
+
+    // Passpoint information - cache it in case of losing these information after
+    // updateAccessPointFromScannedList(). For R2, we should update these data from
+    // WifiManager#getPasspointConfigurations() after users manage the passpoint profile.
+    private boolean mIsExpired;
+    private boolean mIsPasspointConfigurationR1;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION:
+                    if (!intent.getBooleanExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED,
+                            false /* defaultValue */)) {
+                        // only one network changed
+                        WifiConfiguration wifiConfiguration = intent
+                                .getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION);
+                        if (mAccessPoint.matches(wifiConfiguration)) {
+                            mWifiConfig = wifiConfiguration;
+                        }
+                    }
+                    // fall through
+                case WifiManager.NETWORK_STATE_CHANGED_ACTION:
+                case WifiManager.RSSI_CHANGED_ACTION:
+                    refreshPage();
+                    break;
+            }
+        }
+    };
+
+    private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
+            .clearCapabilities().addTransportType(TRANSPORT_WIFI).build();
+
+    // Must be run on the UI thread since it directly manipulates UI state.
+    private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+        @Override
+        public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
+            if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) {
+                mLinkProperties = lp;
+                refreshIpLayerInfo();
+            }
+        }
+
+        private boolean hasCapabilityChanged(NetworkCapabilities nc, int cap) {
+            // If this is the first time we get NetworkCapabilities, report that something changed.
+            if (mNetworkCapabilities == null) return true;
+
+            // nc can never be null, see ConnectivityService#callCallbackForRequest.
+            return mNetworkCapabilities.hasCapability(cap) != nc.hasCapability(cap);
+        }
+
+        private boolean hasPrivateDnsStatusChanged(NetworkCapabilities nc) {
+            // If this is the first time that WifiDetailPreferenceController2 gets
+            // NetworkCapabilities, report that something has changed and assign nc to
+            // mNetworkCapabilities in onCapabilitiesChanged. Note that the NetworkCapabilities
+            // from onCapabilitiesChanged() will never be null, so calling
+            // mNetworkCapabilities.isPrivateDnsBroken() would be safe next time.
+            if (mNetworkCapabilities == null) {
+                return true;
+            }
+
+            return mNetworkCapabilities.isPrivateDnsBroken() != nc.isPrivateDnsBroken();
+        }
+
+        @Override
+        public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+            // If the network just validated or lost Internet access or detected partial internet
+            // connectivity or private dns was broken, refresh network state. Don't do this on
+            // every NetworkCapabilities change because refreshEntityHeader sends IPCs to the
+            // system server from the UI thread, which can cause jank.
+            if (network.equals(mNetwork) && !nc.equals(mNetworkCapabilities)) {
+                if (hasPrivateDnsStatusChanged(nc)
+                        || hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED)
+                        || hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL)
+                        || hasCapabilityChanged(nc, NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
+                    mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
+                    refreshEntityHeader();
+                }
+                mNetworkCapabilities = nc;
+                refreshButtons();
+                refreshIpLayerInfo();
+            }
+        }
+
+        @Override
+        public void onLost(Network network) {
+            // Ephemeral network not a saved network, leave detail page once disconnected
+            if (mIsEphemeral && network.equals(mNetwork)) {
+                exitActivity();
+            }
+        }
+    };
+
+    @VisibleForTesting
+    final WifiTracker.WifiListener mWifiListener = new WifiTracker.WifiListener() {
+        /** Called when the state of Wifi has changed. */
+        public void onWifiStateChanged(int state) {
+            Log.d(TAG, "onWifiStateChanged(" + state + ")");
+            if (mConnectingState == STATE_ENABLE_WIFI && state == WifiManager.WIFI_STATE_ENABLED) {
+                updateConnectingState(STATE_CONNECTING);
+            } else if (mConnectingState != STATE_NONE && state == WifiManager.WIFI_STATE_DISABLED) {
+                // update as disconnected once Wi-Fi disabled since may not received
+                // onConnectedChanged for this case.
+                updateConnectingState(STATE_DISCONNECTED);
+            }
+        }
+
+        /** Called when the connection state of wifi has changed. */
+        public void onConnectedChanged() {
+            refreshPage();
+        }
+
+        /**
+         * Called to indicate the list of AccessPoints has been updated and
+         * {@link WifiTracker#getAccessPoints()} should be called to get the updated list.
+         */
+        public void onAccessPointsChanged() {
+            refreshPage();
+        }
+    };
+
+    /**
+     * To get an instance of {@link WifiDetailPreferenceController2}
+     */
+    public static WifiDetailPreferenceController2 newInstance(
+            AccessPoint accessPoint,
+            ConnectivityManager connectivityManager,
+            Context context,
+            PreferenceFragmentCompat fragment,
+            Handler handler,
+            Lifecycle lifecycle,
+            WifiManager wifiManager,
+            MetricsFeatureProvider metricsFeatureProvider) {
+        return new WifiDetailPreferenceController2(
+                accessPoint, connectivityManager, context, fragment, handler, lifecycle,
+                wifiManager, metricsFeatureProvider, new IconInjector(context));
+    }
+
+    @VisibleForTesting
+        /* package */ WifiDetailPreferenceController2(
+            AccessPoint accessPoint,
+            ConnectivityManager connectivityManager,
+            Context context,
+            PreferenceFragmentCompat fragment,
+            Handler handler,
+            Lifecycle lifecycle,
+            WifiManager wifiManager,
+            MetricsFeatureProvider metricsFeatureProvider,
+            IconInjector injector) {
+        super(context);
+
+        mAccessPoint = accessPoint;
+        mConnectivityManager = connectivityManager;
+        mFragment = fragment;
+        mHandler = handler;
+        mSignalStr = context.getResources().getStringArray(R.array.wifi_signal);
+        mWifiConfig = accessPoint.getConfig();
+        mWifiManager = wifiManager;
+        mMetricsFeatureProvider = metricsFeatureProvider;
+        mIconInjector = injector;
+
+        mFilter = new IntentFilter();
+        mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
+        mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+
+        mLifecycle = lifecycle;
+        lifecycle.addObserver(this);
+
+        mWifiTracker = WifiTrackerFactory.create(
+                mFragment.getActivity(),
+                mWifiListener,
+                mLifecycle,
+                true /*includeSaved*/,
+                true /*includeScans*/);
+        mConnected = mAccessPoint.isActive();
+        // When lost the network connection, WifiInfo/NetworkInfo will be clear. So causes we
+        // could not check if the AccessPoint is ephemeral. Need to cache it in first.
+        mIsEphemeral = mAccessPoint.isEphemeral();
+        mConnectingState = STATE_NONE;
+        mConnectListener = new WifiManager.ActionListener() {
+            @Override
+            public void onSuccess() {
+                // Do nothing
+            }
+
+            @Override
+            public void onFailure(int reason) {
+                updateConnectingState(STATE_FAILED);
+            }
+        };
+
+        mIsExpired = mAccessPoint.isExpired();
+        mIsPasspointConfigurationR1 = mAccessPoint.isPasspointConfigurationR1();
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        // Returns null since this controller contains more than one Preference
+        return null;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+
+        setupEntityHeader(screen);
+
+        mButtonsPref = ((ActionButtonsPreference) screen.findPreference(KEY_BUTTONS_PREF))
+                .setButton1Text(R.string.forget)
+                .setButton1Icon(R.drawable.ic_settings_delete)
+                .setButton1OnClickListener(view -> forgetNetwork())
+                .setButton2Text(R.string.wifi_sign_in_button_text)
+                .setButton2Icon(R.drawable.ic_settings_sign_in)
+                .setButton2OnClickListener(view -> signIntoNetwork())
+                .setButton3Text(R.string.wifi_connect)
+                .setButton3Icon(R.drawable.ic_settings_wireless)
+                .setButton3OnClickListener(view -> connectNetwork())
+                .setButton3Enabled(true)
+                .setButton4Text(R.string.share)
+                .setButton4Icon(R.drawable.ic_qrcode_24dp)
+                .setButton4OnClickListener(view -> shareNetwork());
+
+        if (isPasspointConfigurationR1Expired()) {
+            // Hide Connect button.
+            mButtonsPref.setButton3Visible(false);
+        }
+
+        mSignalStrengthPref = screen.findPreference(KEY_SIGNAL_STRENGTH_PREF);
+        mTxLinkSpeedPref = screen.findPreference(KEY_TX_LINK_SPEED);
+        mRxLinkSpeedPref = screen.findPreference(KEY_RX_LINK_SPEED);
+        mFrequencyPref = screen.findPreference(KEY_FREQUENCY_PREF);
+        mSecurityPref = screen.findPreference(KEY_SECURITY_PREF);
+
+        mSsidPref = screen.findPreference(KEY_SSID_PREF);
+        mMacAddressPref = screen.findPreference(KEY_MAC_ADDRESS_PREF);
+        mIpAddressPref = screen.findPreference(KEY_IP_ADDRESS_PREF);
+        mGatewayPref = screen.findPreference(KEY_GATEWAY_PREF);
+        mSubnetPref = screen.findPreference(KEY_SUBNET_MASK_PREF);
+        mDnsPref = screen.findPreference(KEY_DNS_PREF);
+
+        mIpv6Category = screen.findPreference(KEY_IPV6_CATEGORY);
+        mIpv6AddressPref = screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
+
+        mSecurityPref.setSummary(mAccessPoint.getSecurityString(/* concise */ false));
+    }
+
+    private void setupEntityHeader(PreferenceScreen screen) {
+        LayoutPreference headerPref = screen.findPreference(KEY_HEADER);
+
+        if (usingDataUsageHeader(mContext)) {
+            headerPref.setVisible(false);
+            mDataUsageSummaryPref = screen.findPreference(KEY_DATA_USAGE_HEADER);
+            mDataUsageSummaryPref.setVisible(true);
+            mSummaryHeaderController =
+                new WifiDataUsageSummaryPreferenceController(mFragment.getActivity(),
+                        mLifecycle, (PreferenceFragmentCompat) mFragment, mAccessPoint.getSsid());
+            return;
+        }
+
+        mEntityHeaderController =
+                EntityHeaderController.newInstance(
+                        mFragment.getActivity(), mFragment,
+                        headerPref.findViewById(R.id.entity_header));
+
+        ImageView iconView = headerPref.findViewById(R.id.entity_header_icon);
+
+        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+
+        mEntityHeaderController.setLabel(mAccessPoint.getTitle());
+    }
+
+    private void refreshEntityHeader() {
+        if (usingDataUsageHeader(mContext)) {
+            mSummaryHeaderController.updateState(mDataUsageSummaryPref);
+        } else {
+            String summary;
+            if (isPasspointConfigurationR1Expired()) {
+                // Not able to get summary from AccessPoint because we may lost
+                // PasspointConfiguration information after updateAccessPointFromScannedList().
+                summary = mContext.getResources().getString(
+                        com.android.settingslib.R.string.wifi_passpoint_expired);
+            } else {
+                summary = mAccessPoint.getSettingsSummary(true /* convertSavedAsDisconnected */);
+            }
+
+            mEntityHeaderController
+                    .setSummary(summary)
+                    .setRecyclerView(mFragment.getListView(), mLifecycle)
+                    .done(mFragment.getActivity(), true /* rebind */);
+        }
+    }
+
+    private void updateNetworkInfo() {
+        mNetwork = mWifiManager.getCurrentNetwork();
+        mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork);
+        mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork);
+    }
+
+    @Override
+    public void onResume() {
+        // Ensure mNetwork is set before any callbacks above are delivered, since our
+        // NetworkCallback only looks at changes to mNetwork.
+        updateNetworkInfo();
+        refreshPage();
+        mContext.registerReceiver(mReceiver, mFilter);
+        mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
+                mHandler);
+    }
+
+    @Override
+    public void onPause() {
+        mNetwork = null;
+        mLinkProperties = null;
+        mNetworkCapabilities = null;
+        mNetworkInfo = null;
+        mWifiInfo = null;
+        mContext.unregisterReceiver(mReceiver);
+        mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+    }
+
+    private void refreshPage() {
+        if (!updateAccessPoint()) {
+            return;
+        }
+
+        Log.d(TAG, "Update UI!");
+
+        // refresh header
+        refreshEntityHeader();
+
+        // refresh Buttons
+        refreshButtons();
+
+        // Update Connection Header icon and Signal Strength Preference
+        refreshRssiViews();
+        // Frequency Pref
+        refreshFrequency();
+        // Transmit Link Speed Pref
+        refreshTxSpeed();
+        // Receive Link Speed Pref
+        refreshRxSpeed();
+        // IP related information
+        refreshIpLayerInfo();
+        // SSID Pref
+        refreshSsid();
+        // MAC Address Pref
+        refreshMacAddress();
+    }
+
+    @VisibleForTesting
+    boolean updateAccessPoint() {
+        boolean changed = false;
+        // remember mIsOutOfRange as old before updated
+        boolean oldState = mIsOutOfRange;
+        updateAccessPointFromScannedList();
+
+        if (mAccessPoint.isActive()) {
+            updateNetworkInfo();
+            mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
+            mWifiInfo = mWifiManager.getConnectionInfo();
+            if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) {
+                // Once connected, can't get mNetwork immediately, return false and wait for
+                // next time to update UI. also reset {@code mIsOutOfRange}
+                mIsOutOfRange = oldState;
+                return false;
+            }
+            changed |= mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
+        }
+
+        // signal level changed
+        changed |= mRssiSignalLevel != mAccessPoint.getLevel();
+        // In/Out of range changed
+        changed |= oldState != mIsOutOfRange;
+        // connect state changed
+        if (mConnected != mAccessPoint.isActive()) {
+            mConnected = mAccessPoint.isActive();
+            changed = true;
+            updateConnectingState(mAccessPoint.isActive() ? STATE_CONNECTED : STATE_DISCONNECTED);
+        }
+
+        return changed;
+    }
+
+    private void updateAccessPointFromScannedList() {
+        mIsOutOfRange = true;
+
+        for (AccessPoint ap : mWifiTracker.getAccessPoints()) {
+            if (mAccessPoint.matches(ap)) {
+                mAccessPoint = ap;
+                mWifiConfig = ap.getConfig();
+                mIsOutOfRange = !mAccessPoint.isReachable();
+                return;
+            }
+        }
+    }
+
+    private void exitActivity() {
+        if (DEBUG) {
+            Log.d(TAG, "Exiting the WifiNetworkDetailsPage");
+        }
+        mFragment.getActivity().finish();
+    }
+
+    private void refreshRssiViews() {
+        int signalLevel = mAccessPoint.getLevel();
+
+        // Disappears signal view if not in range. e.g. for saved networks.
+        if (mIsOutOfRange) {
+            mSignalStrengthPref.setVisible(false);
+            mRssiSignalLevel = -1;
+            return;
+        }
+
+        if (mRssiSignalLevel == signalLevel) {
+            return;
+        }
+        mRssiSignalLevel = signalLevel;
+        Drawable wifiIcon = mIconInjector.getIcon(mRssiSignalLevel);
+
+        if (mEntityHeaderController != null) {
+            mEntityHeaderController
+                    .setIcon(redrawIconForHeader(wifiIcon)).done(mFragment.getActivity(),
+                            true /* rebind */);
+        }
+
+        Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate();
+        wifiIconDark.setTintList(Utils.getColorAttr(mContext, android.R.attr.colorControlNormal));
+        mSignalStrengthPref.setIcon(wifiIconDark);
+
+        mSignalStrengthPref.setSummary(mSignalStr[mRssiSignalLevel]);
+        mSignalStrengthPref.setVisible(true);
+    }
+
+    private Drawable redrawIconForHeader(Drawable original) {
+        final int iconSize = mContext.getResources().getDimensionPixelSize(
+                R.dimen.wifi_detail_page_header_image_size);
+        final int actualWidth = original.getMinimumWidth();
+        final int actualHeight = original.getMinimumHeight();
+
+        if ((actualWidth == iconSize && actualHeight == iconSize)
+                || !VectorDrawable.class.isInstance(original)) {
+            return original;
+        }
+
+        // clear tint list to make sure can set 87% black after enlarge
+        original.setTintList(null);
+
+        // enlarge icon size
+        final Bitmap bitmap = Utils.createBitmap(original,
+                iconSize /*width*/,
+                iconSize /*height*/);
+        Drawable newIcon = new BitmapDrawable(null /*resource*/, bitmap);
+
+        // config color for 87% black after enlarge
+        newIcon.setTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary));
+
+        return newIcon;
+    }
+
+    private void refreshFrequency() {
+        if (mWifiInfo == null) {
+            mFrequencyPref.setVisible(false);
+            return;
+        }
+
+        final int frequency = mWifiInfo.getFrequency();
+        String band = null;
+        if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
+                && frequency < AccessPoint.HIGHER_FREQ_24GHZ) {
+            band = mContext.getResources().getString(R.string.wifi_band_24ghz);
+        } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ
+                && frequency < AccessPoint.HIGHER_FREQ_5GHZ) {
+            band = mContext.getResources().getString(R.string.wifi_band_5ghz);
+        } else {
+            Log.e(TAG, "Unexpected frequency " + frequency);
+            // Connecting state is unstable, make it disappeared if unexpected
+            if (mConnectingState == STATE_CONNECTING) {
+                mFrequencyPref.setVisible(false);
+            }
+            return;
+        }
+        mFrequencyPref.setSummary(band);
+        mFrequencyPref.setVisible(true);
+    }
+
+    private void refreshTxSpeed() {
+        if (mWifiInfo == null) {
+            mTxLinkSpeedPref.setVisible(false);
+            return;
+        }
+
+        int txLinkSpeedMbps = mWifiInfo.getTxLinkSpeedMbps();
+        mTxLinkSpeedPref.setVisible(txLinkSpeedMbps >= 0);
+        mTxLinkSpeedPref.setSummary(mContext.getString(
+                R.string.tx_link_speed, mWifiInfo.getTxLinkSpeedMbps()));
+    }
+
+    private void refreshRxSpeed() {
+        if (mWifiInfo == null) {
+            mRxLinkSpeedPref.setVisible(false);
+            return;
+        }
+
+        int rxLinkSpeedMbps = mWifiInfo.getRxLinkSpeedMbps();
+        mRxLinkSpeedPref.setVisible(rxLinkSpeedMbps >= 0);
+        mRxLinkSpeedPref.setSummary(mContext.getString(
+                R.string.rx_link_speed, mWifiInfo.getRxLinkSpeedMbps()));
+    }
+
+    private void refreshSsid() {
+        if (mAccessPoint.isPasspoint() || mAccessPoint.isOsuProvider()) {
+            mSsidPref.setVisible(true);
+            mSsidPref.setSummary(mAccessPoint.getSsidStr());
+        } else {
+            mSsidPref.setVisible(false);
+        }
+    }
+
+    private void refreshMacAddress() {
+        String macAddress = getMacAddress();
+        if (macAddress == null) {
+            mMacAddressPref.setVisible(false);
+            return;
+        }
+
+        mMacAddressPref.setVisible(true);
+        if (macAddress.equals(WifiInfo.DEFAULT_MAC_ADDRESS)) {
+            mMacAddressPref.setSummary(R.string.device_info_not_available);
+        } else {
+            mMacAddressPref.setSummary(macAddress);
+        }
+
+        // MAC Address Pref Title
+        refreshMacTitle();
+    }
+
+    private String getMacAddress() {
+        if (mWifiInfo != null) {
+            // get MAC address from connected network information
+            return mWifiInfo.getMacAddress();
+        }
+
+        // return randomized MAC address
+        if (mWifiConfig != null && mWifiConfig.macRandomizationSetting
+                == WifiConfiguration.RANDOMIZATION_PERSISTENT) {
+            return mWifiConfig.getRandomizedMacAddress().toString();
+        }
+
+        // return device MAC address
+        final String[] macAddresses = mWifiManager.getFactoryMacAddresses();
+        if (macAddresses != null && macAddresses.length > 0) {
+            return macAddresses[0];
+        }
+
+        Log.e(TAG, "Can't get device MAC address!");
+        return null;
+    }
+
+    private void updatePreference(Preference pref, String detailText) {
+        if (!TextUtils.isEmpty(detailText)) {
+            pref.setSummary(detailText);
+            pref.setVisible(true);
+        } else {
+            pref.setVisible(false);
+        }
+    }
+
+    private void refreshButtons() {
+        // Ephemeral network won't be removed permanently, but be putted in blacklist.
+        mButtonsPref.setButton1Text(
+                mIsEphemeral ? R.string.wifi_disconnect_button_text : R.string.forget);
+
+        boolean canForgetNetwork = canForgetNetwork();
+        boolean canSignIntoNetwork = canSignIntoNetwork();
+        boolean canConnectNetwork = canConnectNetwork() && !isPasspointConfigurationR1Expired();
+        boolean canShareNetwork = canShareNetwork();
+
+        mButtonsPref.setButton1Visible(canForgetNetwork);
+        mButtonsPref.setButton2Visible(canSignIntoNetwork);
+        mButtonsPref.setButton3Visible(canConnectNetwork);
+        mButtonsPref.setButton4Visible(canShareNetwork);
+        mButtonsPref.setVisible(canForgetNetwork
+                || canSignIntoNetwork
+                || canConnectNetwork
+                || canShareNetwork);
+    }
+
+    private boolean canConnectNetwork() {
+        // Display connect button for disconnected AP even not in the range.
+        return !mAccessPoint.isActive();
+    }
+
+    private boolean isPasspointConfigurationR1Expired() {
+        return mIsPasspointConfigurationR1 && mIsExpired;
+    }
+
+    private void refreshIpLayerInfo() {
+        // Hide IP layer info if not a connected network.
+        if (!mAccessPoint.isActive() || mNetwork == null || mLinkProperties == null) {
+            mIpAddressPref.setVisible(false);
+            mSubnetPref.setVisible(false);
+            mGatewayPref.setVisible(false);
+            mDnsPref.setVisible(false);
+            mIpv6Category.setVisible(false);
+            return;
+        }
+
+        // Find IPv4 and IPv6 addresses.
+        String ipv4Address = null;
+        String subnet = null;
+        StringJoiner ipv6Addresses = new StringJoiner("\n");
+
+        for (LinkAddress addr : mLinkProperties.getLinkAddresses()) {
+            if (addr.getAddress() instanceof Inet4Address) {
+                ipv4Address = addr.getAddress().getHostAddress();
+                subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength());
+            } else if (addr.getAddress() instanceof Inet6Address) {
+                ipv6Addresses.add(addr.getAddress().getHostAddress());
+            }
+        }
+
+        // Find IPv4 default gateway.
+        String gateway = null;
+        for (RouteInfo routeInfo : mLinkProperties.getRoutes()) {
+            if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) {
+                gateway = routeInfo.getGateway().getHostAddress();
+                break;
+            }
+        }
+
+        // Find all (IPv4 and IPv6) DNS addresses.
+        String dnsServers = mLinkProperties.getDnsServers().stream()
+                .map(InetAddress::getHostAddress)
+                .collect(Collectors.joining("\n"));
+
+        // Update UI.
+        updatePreference(mIpAddressPref, ipv4Address);
+        updatePreference(mSubnetPref, subnet);
+        updatePreference(mGatewayPref, gateway);
+        updatePreference(mDnsPref, dnsServers);
+
+        if (ipv6Addresses.length() > 0) {
+            mIpv6AddressPref.setSummary(
+                    BidiFormatter.getInstance().unicodeWrap(ipv6Addresses.toString()));
+            mIpv6Category.setVisible(true);
+        } else {
+            mIpv6Category.setVisible(false);
+        }
+    }
+
+    private static String ipv4PrefixLengthToSubnetMask(int prefixLength) {
+        try {
+            InetAddress all = InetAddress.getByAddress(
+                    new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255});
+            return NetworkUtils.getNetworkPart(all, prefixLength).getHostAddress();
+        } catch (UnknownHostException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns whether the network represented by this preference can be forgotten.
+     */
+    private boolean canForgetNetwork() {
+        return (mWifiInfo != null && mWifiInfo.isEphemeral()) || canModifyNetwork()
+                || mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig();
+    }
+
+    /**
+     * Returns whether the network represented by this preference can be modified.
+     */
+    public boolean canModifyNetwork() {
+        return mWifiConfig != null && !WifiUtils.isNetworkLockedDown(mContext, mWifiConfig);
+    }
+
+    /**
+     * Returns whether the user can sign into the network represented by this preference.
+     */
+    private boolean canSignIntoNetwork() {
+        return mAccessPoint.isActive() && WifiUtils.canSignIntoNetwork(mNetworkCapabilities);
+    }
+
+    /**
+     * Returns whether the user can share the network represented by this preference with QR code.
+     */
+    private boolean canShareNetwork() {
+        return mAccessPoint.getConfig() != null
+                && WifiDppUtils.isSupportConfiguratorQrCodeGenerator(mContext, mAccessPoint);
+    }
+
+    /**
+     * Forgets the wifi network associated with this preference.
+     */
+    private void forgetNetwork() {
+        if (mWifiInfo != null && mWifiInfo.isEphemeral()) {
+            mWifiManager.disableEphemeralNetwork(mWifiInfo.getSSID());
+        } else if (mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig()) {
+            // Post a dialog to confirm if user really want to forget the passpoint network.
+            showConfirmForgetDialog();
+            return;
+        } else if (mWifiConfig != null) {
+            mWifiManager.forget(mWifiConfig.networkId, null /* action listener */);
+        }
+
+        mMetricsFeatureProvider.action(
+                mFragment.getActivity(), SettingsEnums.ACTION_WIFI_FORGET);
+        mFragment.getActivity().finish();
+    }
+
+    @VisibleForTesting
+    protected void showConfirmForgetDialog() {
+        final AlertDialog dialog = new AlertDialog.Builder(mContext)
+                .setPositiveButton(R.string.forget, ((dialog1, which) -> {
+                    try {
+                        mWifiManager.removePasspointConfiguration(mAccessPoint.getPasspointFqdn());
+                    } catch (RuntimeException e) {
+                        Log.e(TAG, "Failed to remove Passpoint configuration for "
+                                + mAccessPoint.getPasspointFqdn());
+                    }
+                    mMetricsFeatureProvider.action(
+                            mFragment.getActivity(), SettingsEnums.ACTION_WIFI_FORGET);
+                    mFragment.getActivity().finish();
+                }))
+                .setNegativeButton(R.string.cancel, null /* listener */)
+                .setTitle(R.string.wifi_forget_dialog_title)
+                .setMessage(R.string.forget_passpoint_dialog_message)
+                .create();
+        dialog.show();
+    }
+
+    /**
+     * Show QR code to share the network represented by this preference.
+     */
+    private void launchWifiDppConfiguratorActivity() {
+        final Intent intent = WifiDppUtils.getConfiguratorQrCodeGeneratorIntentOrNull(mContext,
+                mWifiManager, mAccessPoint);
+
+        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);
+
+            mContext.startActivity(intent);
+        }
+    }
+
+    /**
+     * Share the wifi network with QR code.
+     */
+    private void shareNetwork() {
+        WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorActivity());
+    }
+
+    /**
+     * Sign in to the captive portal found on this wifi network associated with this preference.
+     */
+    private void signIntoNetwork() {
+        mMetricsFeatureProvider.action(
+                mFragment.getActivity(), SettingsEnums.ACTION_WIFI_SIGNIN);
+        mConnectivityManager.startCaptivePortalApp(mNetwork);
+    }
+
+    @Override
+    public void onSubmit(WifiDialog dialog) {
+        if (dialog.getController() != null) {
+            mWifiManager.save(dialog.getController().getConfig(), new WifiManager.ActionListener() {
+                @Override
+                public void onSuccess() {
+                }
+
+                @Override
+                public void onFailure(int reason) {
+                    Activity activity = mFragment.getActivity();
+                    if (activity != null) {
+                        Toast.makeText(activity,
+                                R.string.wifi_failed_save_message,
+                                Toast.LENGTH_SHORT).show();
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Wrapper for testing compatibility.
+     */
+    @VisibleForTesting
+    static class IconInjector {
+        private final Context mContext;
+
+        IconInjector(Context context) {
+            mContext = context;
+        }
+
+        public Drawable getIcon(int level) {
+            return mContext.getDrawable(Utils.getWifiIconResource(level)).mutate();
+        }
+    }
+
+    private boolean usingDataUsageHeader(Context context) {
+        return FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER);
+    }
+
+    @VisibleForTesting
+    void connectNetwork() {
+        final Activity activity = mFragment.getActivity();
+        // error handling, connected/saved network should have mWifiConfig.
+        if (mWifiConfig == null) {
+            Toast.makeText(activity,
+                    R.string.wifi_failed_connect_message,
+                    Toast.LENGTH_SHORT).show();
+            return;
+        }
+
+        // init state before connect
+        mConnectingState = STATE_NONE;
+
+        if (mWifiManager.isWifiEnabled()) {
+            updateConnectingState(STATE_CONNECTING);
+        } else {
+            // Enable Wi-Fi automatically to connect AP
+            updateConnectingState(STATE_ENABLE_WIFI);
+        }
+    }
+
+    private void updateConnectingState(int state) {
+        final Activity activity = mFragment.getActivity();
+        Log.d(TAG, "updateConnectingState from " + mConnectingState + " to " + state);
+        switch (mConnectingState) {
+            case STATE_NONE:
+            case STATE_ENABLE_WIFI:
+                if (state == STATE_ENABLE_WIFI) {
+                    Log.d(TAG, "Turn on Wi-Fi automatically!");
+                    updateConnectedButton(STATE_ENABLE_WIFI);
+                    Toast.makeText(activity,
+                            R.string.wifi_turned_on_message,
+                            Toast.LENGTH_SHORT).show();
+                    mWifiManager.setWifiEnabled(true);
+                    // start timer for error handling
+                    startTimer();
+                } else if (state == STATE_CONNECTING) {
+                    Log.d(TAG, "connecting...");
+                    updateConnectedButton(STATE_CONNECTING);
+                    if (mAccessPoint.isPasspoint()) {
+                        mWifiManager.connect(mWifiConfig, mConnectListener);
+                    } else {
+                        mWifiManager.connect(mWifiConfig.networkId, mConnectListener);
+                    }
+                    // start timer for error handling since framework didn't call back if failed
+                    startTimer();
+                } else if (state == STATE_ENABLE_WIFI_FAILED) {
+                    Log.e(TAG, "Wi-Fi failed to enable network!");
+                    stopTimer();
+                    // reset state
+                    state = STATE_NONE;
+                    Toast.makeText(activity,
+                            R.string.wifi_failed_connect_message,
+                            Toast.LENGTH_SHORT).show();
+                    updateConnectedButton(STATE_ENABLE_WIFI_FAILED);
+                }
+                // Do not break here for disconnected event.
+            case STATE_CONNECTED:
+                if (state == STATE_DISCONNECTED) {
+                    Log.d(TAG, "disconnected");
+                    // reset state
+                    state = STATE_NONE;
+                    updateConnectedButton(STATE_DISCONNECTED);
+                    refreshPage();
+                    // clear for getting MAC Address from saved configuration
+                    mWifiInfo = null;
+                }
+                break;
+            case STATE_CONNECTING:
+                if (state == STATE_CONNECTED) {
+                    Log.d(TAG, "connected");
+                    stopTimer();
+                    updateConnectedButton(STATE_CONNECTED);
+                    Toast.makeText(activity,
+                            mContext.getString(R.string.wifi_connected_to_message,
+                                    mAccessPoint.getTitle()),
+                            Toast.LENGTH_SHORT).show();
+
+                    refreshPage();
+                } else if (state == STATE_NOT_IN_RANGE) {
+                    Log.d(TAG, "AP not in range");
+                    stopTimer();
+                    // reset state
+                    state = STATE_NONE;
+                    Toast.makeText(activity,
+                            R.string.wifi_not_in_range_message,
+                            Toast.LENGTH_SHORT).show();
+                    updateConnectedButton(STATE_NOT_IN_RANGE);
+                } else if (state == STATE_FAILED) {
+                    Log.d(TAG, "failed");
+                    stopTimer();
+                    // reset state
+                    state = STATE_NONE;
+                    Toast.makeText(activity,
+                            R.string.wifi_failed_connect_message,
+                            Toast.LENGTH_SHORT).show();
+                    updateConnectedButton(STATE_FAILED);
+                }
+                break;
+            default:
+                Log.e(TAG, "Invalid state : " + mConnectingState);
+                // don't update invalid state
+                return;
+        }
+
+        mConnectingState = state;
+    }
+
+    private void updateConnectedButton(int state) {
+        switch (state) {
+            case STATE_ENABLE_WIFI:
+            case STATE_CONNECTING:
+                mButtonsPref.setButton3Text(R.string.wifi_connecting)
+                        .setButton3Enabled(false);
+                break;
+            case STATE_CONNECTED:
+                // init button state and set as invisible
+                mButtonsPref.setButton3Text(R.string.wifi_connect)
+                        .setButton3Icon(R.drawable.ic_settings_wireless)
+                        .setButton3Enabled(true)
+                        .setButton3Visible(false);
+                break;
+            case STATE_DISCONNECTED:
+            case STATE_NOT_IN_RANGE:
+            case STATE_FAILED:
+            case STATE_ENABLE_WIFI_FAILED:
+                if (isPasspointConfigurationR1Expired()) {
+                    // Hide Connect button.
+                    mButtonsPref.setButton3Visible(false);
+                } else {
+                    mButtonsPref.setButton3Text(R.string.wifi_connect)
+                            .setButton3Icon(R.drawable.ic_settings_wireless)
+                            .setButton3Enabled(true)
+                            .setButton3Visible(true);
+                }
+                break;
+            default:
+                Log.e(TAG, "Invalid connect button state : " + state);
+                break;
+        }
+    }
+
+    private void startTimer() {
+        if (sTimer != null) {
+            stopTimer();
+        }
+
+        sTimer = new CountDownTimer(TIMEOUT, TIMEOUT + 1) {
+            @Override
+            public void onTick(long millisUntilFinished) {
+                // Do nothing
+            }
+            @Override
+            public void onFinish() {
+                if (mFragment == null || mFragment.getActivity() == null) {
+                    Log.d(TAG, "Ignore timeout since activity not exist!");
+                    return;
+                }
+                Log.e(TAG, "Timeout for state:" + mConnectingState);
+                if (mConnectingState == STATE_ENABLE_WIFI) {
+                    updateConnectingState(STATE_ENABLE_WIFI_FAILED);
+                } else if (mConnectingState == STATE_CONNECTING) {
+                    updateAccessPointFromScannedList();
+                    if (mIsOutOfRange) {
+                        updateConnectingState(STATE_NOT_IN_RANGE);
+                    } else {
+                        updateConnectingState(STATE_FAILED);
+                    }
+                }
+            }
+        };
+        sTimer.start();
+    }
+
+    private void stopTimer() {
+        if (sTimer == null) return;
+
+        sTimer.cancel();
+        sTimer = null;
+    }
+
+    private void refreshMacTitle() {
+        if (mWifiConfig == null) {
+            return;
+        }
+
+        // For saved Passpoint network, framework doesn't have the field to keep the MAC choice
+        // persistently, so Passpoint network will always use the default value so far, which is
+        // randomized MAC address, so don't need to modify title.
+        if (mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig()) {
+            return;
+        }
+
+        mMacAddressPref.setTitle(
+                (mWifiConfig.macRandomizationSetting
+                        == WifiConfiguration.RANDOMIZATION_PERSISTENT)
+                        ? R.string.wifi_advanced_randomized_mac_address_title
+                        : R.string.wifi_advanced_device_mac_address_title);
+
+    }
+}
diff --git a/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2.java
new file mode 100644
index 0000000..99967dc
--- /dev/null
+++ b/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 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.details2;
+
+import android.app.backup.BackupManager;
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.DropDownPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.wifi.WifiDialog;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+/**
+ * {@link AbstractPreferenceController} that controls whether the wifi network is metered or not
+ */
+public class WifiMeteredPreferenceController2 extends BasePreferenceController implements
+        Preference.OnPreferenceChangeListener, WifiDialog.WifiDialogListener {
+
+    private static final String KEY_WIFI_METERED = "metered";
+    private WifiConfiguration mWifiConfiguration;
+    private WifiManager mWifiManager;
+    private Preference mPreference;
+
+    public WifiMeteredPreferenceController2(Context context, WifiConfiguration wifiConfiguration) {
+        super(context, KEY_WIFI_METERED);
+        mWifiConfiguration = wifiConfiguration;
+        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        final DropDownPreference dropDownPreference = (DropDownPreference) preference;
+        final int meteredOverride = getMeteredOverride();
+        dropDownPreference.setValue(Integer.toString(meteredOverride));
+        updateSummary(dropDownPreference, meteredOverride);
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        if (mWifiConfiguration != null) {
+            mWifiConfiguration.meteredOverride = Integer.parseInt((String) newValue);
+        }
+        mWifiManager.updateNetwork(mWifiConfiguration);
+        // Stage the backup of the SettingsProvider package which backs this up
+        BackupManager.dataChanged("com.android.providers.settings");
+        updateSummary((DropDownPreference) preference, getMeteredOverride());
+        return true;
+    }
+
+    @VisibleForTesting
+    int getMeteredOverride() {
+        if (mWifiConfiguration != null) {
+            // Wrap the meteredOverride since robolectric cannot recognize it
+            return mWifiConfiguration.meteredOverride;
+        }
+        return WifiConfiguration.METERED_OVERRIDE_NONE;
+    }
+
+    private void updateSummary(DropDownPreference preference, int meteredOverride) {
+        preference.setSummary(preference.getEntries()[meteredOverride]);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mPreference = screen.findPreference(getPreferenceKey());
+    }
+
+    @Override
+    public void onSubmit(WifiDialog dialog) {
+        if (dialog.getController() != null) {
+            final WifiConfiguration newConfig = dialog.getController().getConfig();
+            if (newConfig == null || mWifiConfiguration == null) {
+                return;
+            }
+
+            if (newConfig.meteredOverride != mWifiConfiguration.meteredOverride) {
+                mWifiConfiguration = newConfig;
+                onPreferenceChange(mPreference, String.valueOf(newConfig.meteredOverride));
+            }
+        }
+    }
+}
diff --git a/src/com/android/settings/wifi/details2/WifiNetworkDetailsFragment2.java b/src/com/android/settings/wifi/details2/WifiNetworkDetailsFragment2.java
new file mode 100644
index 0000000..5eb4b28
--- /dev/null
+++ b/src/com/android/settings/wifi/details2/WifiNetworkDetailsFragment2.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 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.details2;
+
+import static com.android.settings.wifi.WifiSettings.WIFI_DIALOG_ID;
+
+import android.app.Dialog;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.wifi.WifiConfigUiBase;
+import com.android.settings.wifi.WifiDialog;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.wifi.AccessPoint;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Detail page for the currently connected wifi network.
+ *
+ * <p>The AccessPoint should be saved to the intent Extras when launching this class via
+ * {@link AccessPoint#saveWifiState(Bundle)} in order to properly render this page.
+ */
+public class WifiNetworkDetailsFragment2 extends DashboardFragment implements
+        WifiDialog.WifiDialogListener {
+
+    private static final String TAG = "WifiNetworkDetailsFrg2";
+
+    private AccessPoint mAccessPoint;
+    private WifiDetailPreferenceController2 mWifiDetailPreferenceController2;
+    private List<WifiDialog.WifiDialogListener> mWifiDialogListeners = new ArrayList<>();
+
+    @Override
+    public void onAttach(Context context) {
+        mAccessPoint = new AccessPoint(context, getArguments());
+        super.onAttach(context);
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.WIFI_NETWORK_DETAILS;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.wifi_network_details_fragment;
+    }
+
+    @Override
+    public int getDialogMetricsCategory(int dialogId) {
+        if (dialogId == WIFI_DIALOG_ID) {
+            return SettingsEnums.DIALOG_WIFI_AP_EDIT;
+        }
+        return 0;
+    }
+
+    @Override
+    public Dialog onCreateDialog(int dialogId) {
+        if (getActivity() == null || mWifiDetailPreferenceController2 == null
+                || mAccessPoint == null) {
+            return null;
+        }
+        return WifiDialog.createModal(getActivity(), this, mAccessPoint,
+                WifiConfigUiBase.MODE_MODIFY);
+    }
+
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        MenuItem item = menu.add(0, Menu.FIRST, 0, R.string.wifi_modify);
+        item.setIcon(com.android.internal.R.drawable.ic_mode_edit);
+        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+        super.onCreateOptionsMenu(menu, inflater);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem menuItem) {
+        switch (menuItem.getItemId()) {
+            case Menu.FIRST:
+                if (!mWifiDetailPreferenceController2.canModifyNetwork()) {
+                    RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(),
+                            RestrictedLockUtilsInternal.getDeviceOwner(getContext()));
+                } else {
+                    showDialog(WIFI_DIALOG_ID);
+                }
+                return true;
+            default:
+                return super.onOptionsItemSelected(menuItem);
+        }
+    }
+
+    @Override
+    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+        final List<AbstractPreferenceController> controllers = new ArrayList<>();
+        final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+
+        mWifiDetailPreferenceController2 = WifiDetailPreferenceController2.newInstance(
+                mAccessPoint,
+                cm,
+                context,
+                this,
+                new Handler(Looper.getMainLooper()),  // UI thread.
+                getSettingsLifecycle(),
+                context.getSystemService(WifiManager.class),
+                mMetricsFeatureProvider);
+
+        controllers.add(mWifiDetailPreferenceController2);
+        controllers.add(new AddDevicePreferenceController2(context).init(mAccessPoint));
+
+        final WifiMeteredPreferenceController2 meteredPreferenceController2 =
+                new WifiMeteredPreferenceController2(context, mAccessPoint.getConfig());
+        controllers.add(meteredPreferenceController2);
+
+        final WifiPrivacyPreferenceController2 privacyController2 =
+                new WifiPrivacyPreferenceController2(context);
+        privacyController2.setWifiConfiguration(mAccessPoint.getConfig());
+        privacyController2.setIsEphemeral(mAccessPoint.isEphemeral());
+        privacyController2.setIsPasspoint(
+                mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig());
+        controllers.add(privacyController2);
+
+        // Sets callback listener for wifi dialog.
+        mWifiDialogListeners.add(mWifiDetailPreferenceController2);
+        mWifiDialogListeners.add(privacyController2);
+        mWifiDialogListeners.add(meteredPreferenceController2);
+
+        return controllers;
+    }
+
+    @Override
+    public void onSubmit(WifiDialog dialog) {
+        for (WifiDialog.WifiDialogListener listener : mWifiDialogListeners) {
+            listener.onSubmit(dialog);
+        }
+    }
+}
diff --git a/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2.java
new file mode 100644
index 0000000..d85b607
--- /dev/null
+++ b/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2019 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.details2;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.DropDownPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.wifi.WifiDialog;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+/**
+ * {@link AbstractPreferenceController} that controls whether the wifi network is mac randomized
+ * or not
+ */
+public class WifiPrivacyPreferenceController2 extends BasePreferenceController implements
+        Preference.OnPreferenceChangeListener, WifiDialog.WifiDialogListener {
+
+    private static final String KEY_WIFI_PRIVACY = "privacy";
+    private WifiConfiguration mWifiConfiguration;
+    private WifiManager mWifiManager;
+    private boolean mIsEphemeral = false;
+    private boolean mIsPasspoint = false;
+    private Preference mPreference;
+
+    public WifiPrivacyPreferenceController2(Context context) {
+        super(context, KEY_WIFI_PRIVACY);
+        mWifiConfiguration = null;
+        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+    }
+
+    public void setWifiConfiguration(WifiConfiguration wifiConfiguration) {
+        mWifiConfiguration = wifiConfiguration;
+    }
+
+    public void setIsEphemeral(boolean isEphemeral) {
+        mIsEphemeral = isEphemeral;
+    }
+
+    public void setIsPasspoint(boolean isPasspoint) {
+        mIsPasspoint = isPasspoint;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return mWifiManager.isConnectedMacRandomizationSupported()
+                ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mPreference = screen.findPreference(getPreferenceKey());
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        final DropDownPreference dropDownPreference = (DropDownPreference) preference;
+        final int randomizationLevel = getRandomizationValue();
+        dropDownPreference.setValue(Integer.toString(randomizationLevel));
+        updateSummary(dropDownPreference, randomizationLevel);
+
+        // Makes preference not selectable, when this is a ephemeral network.
+        if (mIsEphemeral || mIsPasspoint) {
+            preference.setSelectable(false);
+            dropDownPreference.setSummary(R.string.wifi_privacy_settings_ephemeral_summary);
+        }
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        if (mWifiConfiguration != null) {
+            mWifiConfiguration.macRandomizationSetting = Integer.parseInt((String) newValue);
+            mWifiManager.updateNetwork(mWifiConfiguration);
+
+            // To activate changing, we need to reconnect network. WiFi will auto connect to
+            // current network after disconnect(). Only needed when this is connected network.
+            final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+            if (wifiInfo != null && wifiInfo.getNetworkId() == mWifiConfiguration.networkId) {
+                mWifiManager.disconnect();
+            }
+        }
+        updateSummary((DropDownPreference) preference, Integer.parseInt((String) newValue));
+        return true;
+    }
+
+    @VisibleForTesting
+    int getRandomizationValue() {
+        if (mWifiConfiguration != null) {
+            return mWifiConfiguration.macRandomizationSetting;
+        }
+        return WifiConfiguration.RANDOMIZATION_PERSISTENT;
+    }
+
+    private static final int PREF_RANDOMIZATION_PERSISTENT = 0;
+    private static final int PREF_RANDOMIZATION_NONE = 1;
+
+    /**
+     * Returns preference index value.
+     *
+     * @param macRandomized is mac randomized value
+     * @return index value of preference
+     */
+    public static int translateMacRandomizedValueToPrefValue(int macRandomized) {
+        return (macRandomized == WifiConfiguration.RANDOMIZATION_PERSISTENT)
+            ? PREF_RANDOMIZATION_PERSISTENT : PREF_RANDOMIZATION_NONE;
+    }
+
+    /**
+     * Returns mac randomized value.
+     *
+     * @param prefMacRandomized is preference index value
+     * @return mac randomized value
+     */
+    public static int translatePrefValueToMacRandomizedValue(int prefMacRandomized) {
+        return (prefMacRandomized == PREF_RANDOMIZATION_PERSISTENT)
+            ? WifiConfiguration.RANDOMIZATION_PERSISTENT : WifiConfiguration.RANDOMIZATION_NONE;
+    }
+
+    private void updateSummary(DropDownPreference preference, int macRandomized) {
+        // Translates value here to set RANDOMIZATION_PERSISTENT as first item in UI for better UX.
+        final int prefMacRandomized = translateMacRandomizedValueToPrefValue(macRandomized);
+        preference.setSummary(preference.getEntries()[prefMacRandomized]);
+    }
+
+    @Override
+    public void onSubmit(WifiDialog dialog) {
+        if (dialog.getController() != null) {
+            final WifiConfiguration newConfig = dialog.getController().getConfig();
+            if (newConfig == null || mWifiConfiguration == null) {
+                return;
+            }
+
+            if (newConfig.macRandomizationSetting != mWifiConfiguration.macRandomizationSetting) {
+                mWifiConfiguration = newConfig;
+                onPreferenceChange(mPreference, String.valueOf(newConfig.macRandomizationSetting));
+            }
+        }
+    }
+}
diff --git a/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsPreferenceController2.java b/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsPreferenceController2.java
new file mode 100644
index 0000000..3b8eb27
--- /dev/null
+++ b/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsPreferenceController2.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2019 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.savedaccesspoints2;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.wifi.AccessPoint;
+import com.android.settingslib.wifi.AccessPointPreference;
+import com.android.settingslib.wifi.AccessPointPreference.UserBadgeCache;
+import com.android.settingslib.wifi.WifiSavedConfigUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Controller that manages a PreferenceGroup, which contains a list of saved access points.
+ */
+public class SavedAccessPointsPreferenceController2 extends BasePreferenceController implements
+        Preference.OnPreferenceClickListener {
+
+    protected final WifiManager mWifiManager;
+    private final UserBadgeCache mUserBadgeCache;
+    private PreferenceGroup mPreferenceGroup;
+    private SavedAccessPointsWifiSettings2 mHost;
+    @VisibleForTesting
+    List<AccessPoint> mAccessPoints;
+
+    public SavedAccessPointsPreferenceController2(Context context, String preferenceKey) {
+        super(context, preferenceKey);
+        mUserBadgeCache = new AccessPointPreference.UserBadgeCache(context.getPackageManager());
+        mWifiManager = context.getSystemService(WifiManager.class);
+    }
+
+    /**
+     * Set {@link SavedAccessPointsWifiSettings2} for click callback action.
+     */
+    public SavedAccessPointsPreferenceController2 setHost(SavedAccessPointsWifiSettings2 host) {
+        mHost = host;
+        return this;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return mAccessPoints.size() > 0 ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        mPreferenceGroup = screen.findPreference(getPreferenceKey());
+        refreshSavedAccessPoints();
+        updatePreference();
+        super.displayPreference(screen);
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        if (mHost != null) {
+            final Preference preferenceInGroup =
+                    mPreferenceGroup.findPreference(preference.getKey());
+            mHost.showWifiPage((AccessPointPreference) preferenceInGroup);
+        }
+        return false;
+    }
+
+    protected void refreshSavedAccessPoints() {
+        mAccessPoints = WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).stream()
+                .filter(accessPoint -> !accessPoint.isPasspointConfig())
+                .sorted(SavedNetworkComparator2.INSTANCE)
+                .collect(Collectors.toList());
+    }
+
+    private void updatePreference() {
+        mPreferenceGroup.removeAll();
+        for (AccessPoint accessPoint : mAccessPoints) {
+            final String key = accessPoint.getKey();
+
+            final AccessPointPreference preference = new AccessPointPreference(accessPoint,
+                    mContext, mUserBadgeCache, true /* forSavedNetworks */);
+            preference.setKey(key);
+            preference.setIcon(null);
+            preference.setOnPreferenceClickListener(this);
+
+            mPreferenceGroup.addPreference(preference);
+        }
+    }
+}
diff --git a/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsWifiSettings2.java b/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsWifiSettings2.java
new file mode 100644
index 0000000..a1b7733
--- /dev/null
+++ b/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsWifiSettings2.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019 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.savedaccesspoints2;
+
+import android.annotation.Nullable;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.wifi.WifiSettings;
+import com.android.settings.wifi.details2.WifiNetworkDetailsFragment2;
+import com.android.settingslib.wifi.AccessPoint;
+import com.android.settingslib.wifi.AccessPointPreference;
+
+/**
+ * UI to manage saved networks/access points.
+ */
+public class SavedAccessPointsWifiSettings2 extends DashboardFragment {
+
+    private static final String TAG = "SavedAccessPoints2";
+
+    @VisibleForTesting
+    Bundle mAccessPointSavedState;
+    private AccessPoint mSelectedAccessPoint;
+
+    // Instance state key
+    private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
+
+    @Override
+    public int getMetricsCategory() {
+        return SettingsEnums.WIFI_SAVED_ACCESS_POINTS;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.wifi_display_saved_access_points2;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        use(SavedAccessPointsPreferenceController2.class)
+                .setHost(this);
+        use(SubscribedAccessPointsPreferenceController2.class)
+                .setHost(this);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState != null) {
+            if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
+                mAccessPointSavedState =
+                        savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
+            } else {
+                mAccessPointSavedState = null;
+            }
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        if (mAccessPointSavedState != null) {
+            final PreferenceScreen screen = getPreferenceScreen();
+            use(SavedAccessPointsPreferenceController2.class).displayPreference(screen);
+            use(SubscribedAccessPointsPreferenceController2.class).displayPreference(screen);
+        }
+    }
+
+    /**
+     * Shows {@link WifiNetworkDetailsFragment2} for assigned {@link AccessPointPreference}.
+     */
+    public void showWifiPage(@Nullable AccessPointPreference accessPoint) {
+        removeDialog(WifiSettings.WIFI_DIALOG_ID);
+
+        if (accessPoint != null) {
+            // Save the access point and edit mode
+            mSelectedAccessPoint = accessPoint.getAccessPoint();
+        } else {
+            // No access point is selected. Clear saved state.
+            mSelectedAccessPoint = null;
+            mAccessPointSavedState = null;
+        }
+
+        if (mSelectedAccessPoint == null) {
+            mSelectedAccessPoint = new AccessPoint(getActivity(), mAccessPointSavedState);
+        }
+        final Bundle savedState = new Bundle();
+        mSelectedAccessPoint.saveWifiState(savedState);
+
+        new SubSettingLauncher(getContext())
+                .setTitleText(mSelectedAccessPoint.getTitle())
+                .setDestination(WifiNetworkDetailsFragment2.class.getName())
+                .setArguments(savedState)
+                .setSourceMetricsCategory(getMetricsCategory())
+                .launch();
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        // If the dialog is showing (indicated by the existence of mSelectedAccessPoint), then we
+        // save its state.
+        if (mSelectedAccessPoint != null) {
+            mAccessPointSavedState = new Bundle();
+            mSelectedAccessPoint.saveWifiState(mAccessPointSavedState);
+            outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
+        }
+    }
+}
diff --git a/src/com/android/settings/wifi/savedaccesspoints2/SavedNetworkComparator2.java b/src/com/android/settings/wifi/savedaccesspoints2/SavedNetworkComparator2.java
new file mode 100644
index 0000000..8388531
--- /dev/null
+++ b/src/com/android/settings/wifi/savedaccesspoints2/SavedNetworkComparator2.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 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.savedaccesspoints2;
+
+import android.icu.text.Collator;
+
+import com.android.settingslib.wifi.AccessPoint;
+
+import java.util.Comparator;
+
+/**
+ * For {@link AccessPoint} sorting before desplaying.
+ */
+public final class SavedNetworkComparator2 {
+    public static final Comparator<AccessPoint> INSTANCE =
+            new Comparator<AccessPoint>() {
+                final Collator mCollator = Collator.getInstance();
+
+                @Override
+                public int compare(AccessPoint ap1, AccessPoint ap2) {
+                    return mCollator.compare(
+                            nullToEmpty(ap1.getTitle()), nullToEmpty(ap2.getTitle()));
+                }
+
+                private String nullToEmpty(String string) {
+                    return (string == null) ? "" : string;
+                }
+            };
+}
diff --git a/src/com/android/settings/wifi/savedaccesspoints2/SubscribedAccessPointsPreferenceController2.java b/src/com/android/settings/wifi/savedaccesspoints2/SubscribedAccessPointsPreferenceController2.java
new file mode 100644
index 0000000..00ae221
--- /dev/null
+++ b/src/com/android/settings/wifi/savedaccesspoints2/SubscribedAccessPointsPreferenceController2.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 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.savedaccesspoints2;
+
+import android.content.Context;
+
+import com.android.settingslib.wifi.WifiSavedConfigUtils;
+
+import java.util.stream.Collectors;
+
+/**
+ * Controller that manages a PreferenceGroup, which contains a list of subscribed access points.
+ */
+public class SubscribedAccessPointsPreferenceController2 extends
+        SavedAccessPointsPreferenceController2 {
+
+    public SubscribedAccessPointsPreferenceController2(Context context, String preferenceKey) {
+        super(context, preferenceKey);
+    }
+
+    @Override
+    protected void refreshSavedAccessPoints() {
+        mAccessPoints = WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).stream()
+                .filter(accessPoint -> accessPoint.isPasspointConfig())
+                .sorted(SavedNetworkComparator2.INSTANCE)
+                .collect(Collectors.toList());
+    }
+}
diff --git a/src/com/android/settings/wifi/slice/WifiSlice.java b/src/com/android/settings/wifi/slice/WifiSlice.java
index e5c8de5..97ac9c5 100644
--- a/src/com/android/settings/wifi/slice/WifiSlice.java
+++ b/src/com/android/settings/wifi/slice/WifiSlice.java
@@ -38,6 +38,7 @@
 import android.net.wifi.WifiManager;
 import android.os.Bundle;
 import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
 
 import androidx.annotation.VisibleForTesting;
 import androidx.core.graphics.drawable.IconCompat;
@@ -54,8 +55,10 @@
 import com.android.settings.slices.SliceBuilderUtils;
 import com.android.settings.wifi.WifiDialogActivity;
 import com.android.settings.wifi.WifiSettings;
+import com.android.settings.wifi.WifiSettings2;
 import com.android.settings.wifi.WifiUtils;
 import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
+import com.android.settings.wifi.details2.WifiNetworkDetailsFragment2;
 import com.android.settingslib.wifi.AccessPoint;
 
 import java.util.Arrays;
@@ -247,13 +250,24 @@
         accessPoint.saveWifiState(extras);
 
         if (accessPoint.isActive()) {
-            final Intent intent = new SubSettingLauncher(mContext)
-                    .setTitleRes(R.string.pref_title_network_details)
-                    .setDestination(WifiNetworkDetailsFragment.class.getName())
-                    .setArguments(extras)
-                    .setSourceMetricsCategory(SettingsEnums.WIFI)
-                    .toIntent();
-            return getActivityAction(requestCode, intent, icon, title);
+            Intent intent;
+            if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) {
+                intent = new SubSettingLauncher(mContext)
+                        .setTitleRes(R.string.pref_title_network_details)
+                        .setDestination(WifiNetworkDetailsFragment2.class.getName())
+                        .setArguments(extras)
+                        .setSourceMetricsCategory(SettingsEnums.WIFI)
+                        .toIntent();
+                return getActivityAction(requestCode, intent, icon, title);
+            } else {
+                intent = new SubSettingLauncher(mContext)
+                        .setTitleRes(R.string.pref_title_network_details)
+                        .setDestination(WifiNetworkDetailsFragment.class.getName())
+                        .setArguments(extras)
+                        .setSourceMetricsCategory(SettingsEnums.WIFI)
+                        .toIntent();
+                return getActivityAction(requestCode, intent, icon, title);
+            }
         } else if (WifiUtils.getConnectingType(accessPoint) != WifiUtils.CONNECT_TYPE_OTHERS) {
             final Intent intent = new Intent(mContext, ConnectToWifiHandler.class)
                     .putExtra(WifiDialogActivity.KEY_ACCESS_POINT_STATE, extras);
@@ -317,11 +331,21 @@
     public Intent getIntent() {
         final String screenTitle = mContext.getText(R.string.wifi_settings).toString();
         final Uri contentUri = new Uri.Builder().appendPath(KEY_WIFI).build();
-        final Intent intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext,
+
+        Intent intent;
+        if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) {
+            intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext,
+                WifiSettings2.class.getName(), KEY_WIFI, screenTitle,
+                SettingsEnums.DIALOG_WIFI_AP_EDIT)
+                .setClassName(mContext.getPackageName(), SubSettings.class.getName())
+                .setData(contentUri);
+        } else {
+            intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext,
                 WifiSettings.class.getName(), KEY_WIFI, screenTitle,
                 SettingsEnums.DIALOG_WIFI_AP_EDIT)
                 .setClassName(mContext.getPackageName(), SubSettings.class.getName())
                 .setData(contentUri);
+        }
 
         return intent;
     }
diff --git a/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java
new file mode 100644
index 0000000..8d15224
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java
@@ -0,0 +1,1905 @@
+/*
+ * Copyright (C) 2019 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.details2;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.MacAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.net.RouteInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.provider.Settings;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageView;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
+import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
+import com.android.settings.widget.EntityHeaderController;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.widget.ActionButtonsPreference;
+import com.android.settingslib.widget.LayoutPreference;
+import com.android.settingslib.wifi.AccessPoint;
+import com.android.settingslib.wifi.WifiTracker;
+import com.android.settingslib.wifi.WifiTrackerFactory;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowToast;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowDevicePolicyManager.class, ShadowEntityHeaderController.class})
+public class WifiDetailPreferenceController2Test {
+
+    private static final int LEVEL = 1;
+    private static final int RSSI = -55;
+    private static final int TX_LINK_SPEED = 123;
+    private static final int RX_LINK_SPEED = 54;
+    private static final String SSID = "ssid";
+    private static final String MAC_ADDRESS = "01:23:45:67:89:ab";
+    private static final String RANDOMIZED_MAC_ADDRESS = "RANDOMIZED_MAC_ADDRESS";
+    private static final String FACTORY_MAC_ADDRESS = "FACTORY_MAC_ADDRESS";
+    private static final String SECURITY = "None";
+    private static final String FQDN = "fqdn";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private PreferenceScreen mMockScreen;
+
+    @Mock
+    private AccessPoint mMockAccessPoint;
+    @Mock
+    private FragmentActivity mMockActivity;
+    @Mock
+    private ConnectivityManager mMockConnectivityManager;
+    @Mock
+    private Network mMockNetwork;
+    @Mock
+    private NetworkInfo mMockNetworkInfo;
+    @Mock
+    private WifiConfiguration mMockWifiConfig;
+    @Mock
+    private WifiInfo mMockWifiInfo;
+    @Mock
+    private WifiNetworkDetailsFragment2 mMockFragment;
+    @Mock
+    private WifiManager mMockWifiManager;
+    @Mock
+    private WifiTracker mMockWifiTracker;
+    @Mock
+    private MetricsFeatureProvider mMockMetricsFeatureProvider;
+    @Mock
+    private WifiDetailPreferenceController2.IconInjector mMockIconInjector;
+    @Mock
+    private MacAddress mMockMacAddress;
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private EntityHeaderController mMockHeaderController;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private LayoutPreference mMockHeaderLayoutPreference;
+    @Mock
+    private ImageView mMockHeaderIcon;
+
+    @Mock
+    private ActionButtonsPreference mMockButtonsPref;
+    @Mock
+    private Preference mMockSignalStrengthPref;
+    @Mock
+    private Preference mMockTxLinkSpeedPref;
+    @Mock
+    private Preference mMockRxLinkSpeedPref;
+    @Mock
+    private Preference mMockFrequencyPref;
+    @Mock
+    private Preference mMockSecurityPref;
+    @Mock
+    private Preference mMockSsidPref;
+    @Mock
+    private Preference mMockMacAddressPref;
+    @Mock
+    private Preference mMockIpAddressPref;
+    @Mock
+    private Preference mMockGatewayPref;
+    @Mock
+    private Preference mMockSubnetPref;
+    @Mock
+    private Preference mMockDnsPref;
+    @Mock
+    private PreferenceCategory mMockIpv6Category;
+    @Mock
+    private Preference mMockIpv6AddressesPref;
+    @Mock
+    private PackageManager mMockPackageManager;
+
+    @Captor
+    private ArgumentCaptor<NetworkCallback> mCallbackCaptor;
+    @Captor
+    private ArgumentCaptor<View.OnClickListener> mForgetClickListener;
+
+    private Context mContext;
+    private Lifecycle mLifecycle;
+    private LifecycleOwner mLifecycleOwner;
+    private LinkProperties mLinkProperties;
+    private WifiDetailPreferenceController2 mController;
+
+    // This class exists so that these values can be made static final. They can't be static final
+    // members of the test class, because any attempt to call IpPrefix or RouteInfo constructors
+    // during static initialization of the test class results in NoSuchMethorError being thrown
+    // when the test is run.
+    private static class Constants {
+        static final int IPV4_PREFIXLEN = 25;
+        static final LinkAddress IPV4_ADDR;
+        static final Inet4Address IPV4_GATEWAY;
+        static final RouteInfo IPV4_DEFAULT;
+        static final RouteInfo IPV4_SUBNET;
+        static final LinkAddress IPV6_LINKLOCAL;
+        static final LinkAddress IPV6_GLOBAL1;
+        static final LinkAddress IPV6_GLOBAL2;
+        static final InetAddress IPV4_DNS1;
+        static final InetAddress IPV4_DNS2;
+        static final InetAddress IPV6_DNS;
+
+        private static LinkAddress ipv6LinkAddress(String addr) throws UnknownHostException {
+            return new LinkAddress(InetAddress.getByName(addr), 64);
+        }
+
+        private static LinkAddress ipv4LinkAddress(String addr, int prefixlen)
+                throws UnknownHostException {
+            return new LinkAddress(InetAddress.getByName(addr), prefixlen);
+        }
+
+        static {
+            try {
+                // We create our test constants in these roundabout ways because the robolectric
+                // shadows don't contain NetworkUtils.parseNumericAddress and other utility methods,
+                // so the easy ways to do things fail with NoSuchMethodError.
+                IPV4_ADDR = ipv4LinkAddress("192.0.2.2", IPV4_PREFIXLEN);
+                IPV4_GATEWAY = (Inet4Address) InetAddress.getByName("192.0.2.127");
+
+                final Inet4Address any4 = (Inet4Address) InetAddress.getByName("0.0.0.0");
+                IpPrefix subnet = new IpPrefix(IPV4_ADDR.getAddress(), IPV4_PREFIXLEN);
+                IPV4_SUBNET = new RouteInfo(subnet, any4);
+                IPV4_DEFAULT = new RouteInfo(new IpPrefix(any4, 0), IPV4_GATEWAY);
+
+                IPV6_LINKLOCAL = ipv6LinkAddress("fe80::211:25ff:fef8:7cb2%1");
+                IPV6_GLOBAL1 = ipv6LinkAddress("2001:db8:1::211:25ff:fef8:7cb2");
+                IPV6_GLOBAL2 = ipv6LinkAddress("2001:db8:1::3dfe:8902:f98f:739d");
+
+                IPV4_DNS1 = InetAddress.getByName("8.8.8.8");
+                IPV4_DNS2 = InetAddress.getByName("8.8.4.4");
+                IPV6_DNS = InetAddress.getByName("2001:4860:4860::64");
+            } catch (UnknownHostException e) {
+                throw new RuntimeException("Invalid hardcoded IP addresss: " + e);
+            }
+        }
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(RuntimeEnvironment.application);
+        mLifecycleOwner = () -> mLifecycle;
+        mLifecycle = new Lifecycle(mLifecycleOwner);
+
+        when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockAccessPoint.getConfig()).thenReturn(mMockWifiConfig);
+        when(mMockAccessPoint.getLevel()).thenReturn(LEVEL);
+        when(mMockAccessPoint.getSecurityString(false)).thenReturn(SECURITY);
+        when(mMockAccessPoint.getSsidStr()).thenReturn(SSID);
+        when(mMockConnectivityManager.getNetworkInfo(any(Network.class)))
+                .thenReturn(mMockNetworkInfo);
+        doNothing().when(mMockConnectivityManager).registerNetworkCallback(
+                nullable(NetworkRequest.class), mCallbackCaptor.capture(), nullable(Handler.class));
+        mMockButtonsPref = createMock();
+        when(mMockButtonsPref.setButton1OnClickListener(mForgetClickListener.capture()))
+                .thenReturn(mMockButtonsPref);
+
+        when(mMockWifiInfo.getTxLinkSpeedMbps()).thenReturn(TX_LINK_SPEED);
+        when(mMockWifiInfo.getRxLinkSpeedMbps()).thenReturn(RX_LINK_SPEED);
+        when(mMockWifiInfo.getRssi()).thenReturn(RSSI);
+        when(mMockWifiInfo.getMacAddress()).thenReturn(MAC_ADDRESS);
+        when(mMockWifiManager.getConnectionInfo()).thenReturn(mMockWifiInfo);
+
+        when(mMockWifiManager.getCurrentNetwork()).thenReturn(mMockNetwork);
+        mLinkProperties = new LinkProperties();
+        when(mMockConnectivityManager.getLinkProperties(mMockNetwork)).thenReturn(mLinkProperties);
+
+        when(mMockFragment.getActivity()).thenReturn(mMockActivity);
+
+        ShadowEntityHeaderController.setUseMock(mMockHeaderController);
+        // builder pattern
+        when(mMockHeaderController.setRecyclerView(mMockFragment.getListView(), mLifecycle))
+                .thenReturn(mMockHeaderController);
+        when(mMockHeaderController.setSummary(anyString())).thenReturn(mMockHeaderController);
+        when(mMockIconInjector.getIcon(anyInt())).thenReturn(new ColorDrawable());
+
+        setupMockedPreferenceScreen();
+    }
+
+    private void setUpForConnectedNetwork() {
+        when(mMockAccessPoint.isActive()).thenReturn(true);
+        ArrayList list = new ArrayList<>();
+        list.add(mMockAccessPoint);
+        when(mMockWifiTracker.getAccessPoints()).thenReturn(list);
+        WifiTrackerFactory.setTestingWifiTracker(mMockWifiTracker);
+        when(mMockAccessPoint.matches(any(AccessPoint.class))).thenReturn(true);
+        when(mMockAccessPoint.isReachable()).thenReturn(true);
+
+        mController = newWifiDetailPreferenceController2();
+    }
+
+    private void setUpForDisconnectedNetwork() {
+        when(mMockAccessPoint.isActive()).thenReturn(false);
+        ArrayList list = new ArrayList<>();
+        list.add(mMockAccessPoint);
+        when(mMockWifiTracker.getAccessPoints()).thenReturn(list);
+        WifiTrackerFactory.setTestingWifiTracker(mMockWifiTracker);
+        when(mMockAccessPoint.matches(any(AccessPoint.class))).thenReturn(true);
+        when(mMockAccessPoint.isReachable()).thenReturn(true);
+
+        mController = newWifiDetailPreferenceController2();
+    }
+
+    private void setUpForNotInRangeNetwork() {
+        when(mMockAccessPoint.isActive()).thenReturn(false);
+        ArrayList list = new ArrayList<>();
+        list.add(mMockAccessPoint);
+        when(mMockWifiTracker.getAccessPoints()).thenReturn(list);
+        WifiTrackerFactory.setTestingWifiTracker(mMockWifiTracker);
+        when(mMockAccessPoint.matches(any(AccessPoint.class))).thenReturn(false);
+        when(mMockAccessPoint.isReachable()).thenReturn(false);
+
+        mController = newWifiDetailPreferenceController2();
+    }
+
+    private WifiDetailPreferenceController2 newWifiDetailPreferenceController2() {
+        return new WifiDetailPreferenceController2(
+                mMockAccessPoint,
+                mMockConnectivityManager,
+                mContext,
+                mMockFragment,
+                null,  // Handler
+                mLifecycle,
+                mMockWifiManager,
+                mMockMetricsFeatureProvider,
+                mMockIconInjector);
+    }
+
+    private void setupMockedPreferenceScreen() {
+        when(mMockScreen.getPreferenceManager().getContext()).thenReturn(mContext);
+
+        when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_HEADER))
+                .thenReturn(mMockHeaderLayoutPreference);
+        when(mMockHeaderLayoutPreference.findViewById(R.id.entity_header_icon))
+                .thenReturn(mMockHeaderIcon);
+
+        when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_BUTTONS_PREF))
+                .thenReturn(mMockButtonsPref);
+        when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_SIGNAL_STRENGTH_PREF))
+                .thenReturn(mMockSignalStrengthPref);
+        when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_TX_LINK_SPEED))
+                .thenReturn(mMockTxLinkSpeedPref);
+        when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_RX_LINK_SPEED))
+                .thenReturn(mMockRxLinkSpeedPref);
+        when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_FREQUENCY_PREF))
+                .thenReturn(mMockFrequencyPref);
+        when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_SECURITY_PREF))
+                .thenReturn(mMockSecurityPref);
+        when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_SSID_PREF))
+                .thenReturn(mMockSsidPref);
+        when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_MAC_ADDRESS_PREF))
+                .thenReturn(mMockMacAddressPref);
+        when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_IP_ADDRESS_PREF))
+                .thenReturn(mMockIpAddressPref);
+        when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_GATEWAY_PREF))
+                .thenReturn(mMockGatewayPref);
+        when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_SUBNET_MASK_PREF))
+                .thenReturn(mMockSubnetPref);
+        when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_DNS_PREF))
+                .thenReturn(mMockDnsPref);
+        when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_IPV6_CATEGORY))
+                .thenReturn(mMockIpv6Category);
+        when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_IPV6_ADDRESSES_PREF))
+                .thenReturn(mMockIpv6AddressesPref);
+    }
+
+    private void displayAndResume() {
+        mController.displayPreference(mMockScreen);
+        mController.onResume();
+    }
+
+    @Test
+    public void isAvailable_shouldAlwaysReturnTrue() {
+        setUpForConnectedNetwork();
+        mController.displayPreference(mMockScreen);
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void securityPreference_stringShouldBeSet() {
+        setUpForConnectedNetwork();
+        displayAndResume();
+
+        verify(mMockSecurityPref).setSummary(SECURITY);
+    }
+
+    @Test
+    public void latestWifiInfo_shouldBeFetchedInDisplayPreferenceForConnectedNetwork() {
+        setUpForConnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockWifiManager, times(1)).getConnectionInfo();
+    }
+
+    @Test
+    public void latestWifiInfo_shouldNotBeFetchedInDisplayPreferenceForDisconnectedNetwork() {
+        setUpForDisconnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockWifiManager, never()).getConnectionInfo();
+    }
+
+    @Test
+    public void latestWifiInfo_shouldNotBeFetchedInDisplayPreferenceForNotInRangeNetwork() {
+        setUpForNotInRangeNetwork();
+
+        displayAndResume();
+
+        verify(mMockWifiManager, never()).getConnectionInfo();
+    }
+
+    @Test
+    public void latestNetworkInfo_shouldBeFetchedInDisplayPreferenceForConnectedNetwork() {
+        setUpForConnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockConnectivityManager, times(1)).getNetworkInfo(any(Network.class));
+    }
+
+    @Test
+    public void latestNetworkInfo_shouldNotBeFetchedInDisplayPreferenceForDisconnectedNetwork() {
+        setUpForDisconnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockConnectivityManager, never()).getNetworkInfo(any(Network.class));
+    }
+
+    @Test
+    public void latestNetworkInfo_shouldNotBeFetchedInDisplayPreferenceForNotInRangeNetwork() {
+        setUpForNotInRangeNetwork();
+
+        displayAndResume();
+
+        verify(mMockConnectivityManager, never()).getNetworkInfo(any(Network.class));
+    }
+
+    @Test
+    public void networkCallback_shouldBeRegisteredOnResume() {
+        setUpForConnectedNetwork();
+        displayAndResume();
+
+        verify(mMockConnectivityManager, times(1)).registerNetworkCallback(
+                nullable(NetworkRequest.class), mCallbackCaptor.capture(), nullable(Handler.class));
+    }
+
+    @Test
+    public void networkCallback_shouldBeUnregisteredOnPause() {
+        setUpForConnectedNetwork();
+        displayAndResume();
+        mController.onPause();
+
+        verify(mMockConnectivityManager, times(1))
+                .unregisterNetworkCallback(mCallbackCaptor.getValue());
+    }
+
+    @Test
+    public void entityHeader_shouldHaveIconSetForConnectedNetwork() {
+        setUpForConnectedNetwork();
+        Drawable expectedIcon = mMockIconInjector.getIcon(LEVEL);
+
+        displayAndResume();
+
+        verify(mMockHeaderController).setIcon(expectedIcon);
+    }
+
+    @Test
+    public void entityHeader_shouldHaveIconSetForDisconnectedNetwork() {
+        setUpForDisconnectedNetwork();
+        Drawable expectedIcon = mMockIconInjector.getIcon(LEVEL);
+
+        displayAndResume();
+
+        verify(mMockHeaderController).setIcon(expectedIcon);
+    }
+
+    @Test
+    public void entityHeader_shouldNotHaveIconSetForNotInRangeNetwork() {
+        setUpForNotInRangeNetwork();
+
+        displayAndResume();
+
+        verify(mMockHeaderController, never()).setIcon(any(Drawable.class));
+    }
+
+    @Test
+    public void entityHeader_shouldHaveLabelSetToTitle() {
+        setUpForConnectedNetwork();
+        String label = "title";
+        when(mMockAccessPoint.getTitle()).thenReturn(label);
+
+        displayAndResume();
+
+        verify(mMockHeaderController).setLabel(label);
+    }
+
+    @Test
+    public void entityHeader_shouldHaveSummarySet() {
+        setUpForConnectedNetwork();
+        String summary = "summary";
+        when(mMockAccessPoint.getSettingsSummary(true /*convertSavedAsDisconnected*/))
+                .thenReturn(summary);
+
+        displayAndResume();
+
+        verify(mMockHeaderController).setSummary(summary);
+    }
+
+    @Test
+    public void entityHeader_shouldConvertSavedAsDisconnected() {
+        setUpForDisconnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockAccessPoint, times(1)).getSettingsSummary(true /*convertSavedAsDisconnected*/);
+    }
+
+    @Test
+    public void signalStrengthPref_shouldHaveIconSetForConnectedNetwork() {
+        setUpForConnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockSignalStrengthPref).setIcon(any(Drawable.class));
+    }
+
+    @Test
+    public void signalStrengthPref_shouldHaveIconSetForDisconnectedNetwork() {
+        setUpForDisconnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockSignalStrengthPref).setIcon(any(Drawable.class));
+    }
+
+    @Test
+    public void signalStrengthPref_shouldNotHaveIconSetForOutOfRangeNetwork() {
+        setUpForNotInRangeNetwork();
+
+        displayAndResume();
+
+        verify(mMockSignalStrengthPref, never()).setIcon(any(Drawable.class));
+    }
+
+    @Test
+    public void signalStrengthPref_shouldHaveDetailTextSetForConnectedNetwork() {
+        setUpForConnectedNetwork();
+        String expectedStrength =
+                mContext.getResources().getStringArray(R.array.wifi_signal)[LEVEL];
+
+        displayAndResume();
+
+        verify(mMockSignalStrengthPref).setSummary(expectedStrength);
+    }
+
+    @Test
+    public void signalStrengthPref_shouldHaveDetailTextSetForDisconnectedNetwork() {
+        setUpForDisconnectedNetwork();
+        String expectedStrength =
+                mContext.getResources().getStringArray(R.array.wifi_signal)[LEVEL];
+
+        displayAndResume();
+
+        verify(mMockSignalStrengthPref).setSummary(expectedStrength);
+    }
+
+    @Test
+    public void signalStrengthPref_shouldNotHaveDetailTextSetForNotInRangeNetwork() {
+        setUpForNotInRangeNetwork();
+
+        displayAndResume();
+
+        verify(mMockSignalStrengthPref, never()).setSummary(any(String.class));
+    }
+
+    @Test
+    public void linkSpeedPref_shouldNotShowIfNotSet() {
+        setUpForConnectedNetwork();
+        when(mMockWifiInfo.getTxLinkSpeedMbps()).thenReturn(WifiInfo.LINK_SPEED_UNKNOWN);
+
+        displayAndResume();
+
+        verify(mMockTxLinkSpeedPref).setVisible(false);
+    }
+
+    @Test
+    public void linkSpeedPref_shouldVisibleForConnectedNetwork() {
+        setUpForConnectedNetwork();
+        String expectedLinkSpeed = mContext.getString(R.string.tx_link_speed, TX_LINK_SPEED);
+
+        displayAndResume();
+
+        verify(mMockTxLinkSpeedPref).setVisible(true);
+        verify(mMockTxLinkSpeedPref).setSummary(expectedLinkSpeed);
+    }
+
+    @Test
+    public void linkSpeedPref_shouldInvisibleForDisconnectedNetwork() {
+        setUpForDisconnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockTxLinkSpeedPref).setVisible(false);
+        verify(mMockTxLinkSpeedPref, never()).setSummary(any(String.class));
+    }
+
+    @Test
+    public void linkSpeedPref_shouldInvisibleForNotInRangeNetwork() {
+        setUpForNotInRangeNetwork();
+
+        displayAndResume();
+
+        verify(mMockTxLinkSpeedPref).setVisible(false);
+        verify(mMockTxLinkSpeedPref, never()).setSummary(any(String.class));
+    }
+
+    @Test
+    public void rxLinkSpeedPref_shouldNotShowIfNotSet() {
+        setUpForConnectedNetwork();
+        when(mMockWifiInfo.getRxLinkSpeedMbps()).thenReturn(WifiInfo.LINK_SPEED_UNKNOWN);
+
+        displayAndResume();
+
+        verify(mMockRxLinkSpeedPref).setVisible(false);
+    }
+
+    @Test
+    public void rxLinkSpeedPref_shouldVisibleForConnectedNetwork() {
+        setUpForConnectedNetwork();
+        String expectedLinkSpeed = mContext.getString(R.string.rx_link_speed, RX_LINK_SPEED);
+
+        displayAndResume();
+
+        verify(mMockRxLinkSpeedPref).setVisible(true);
+        verify(mMockRxLinkSpeedPref).setSummary(expectedLinkSpeed);
+    }
+
+    @Test
+    public void rxLinkSpeedPref_shouldInvisibleForDisconnectedNetwork() {
+        setUpForDisconnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockRxLinkSpeedPref).setVisible(false);
+        verify(mMockRxLinkSpeedPref, never()).setSummary(any(String.class));
+    }
+
+    @Test
+    public void rxLinkSpeedPref_shouldInvisibleForNotInRangeNetwork() {
+        setUpForNotInRangeNetwork();
+
+        displayAndResume();
+
+        verify(mMockRxLinkSpeedPref).setVisible(false);
+        verify(mMockRxLinkSpeedPref, never()).setSummary(any(String.class));
+    }
+
+    @Test
+    public void ssidPref_shouldHaveDetailTextSetForPasspointR1() {
+        setUpForConnectedNetwork();
+        when(mMockAccessPoint.isPasspoint()).thenReturn(true);
+        when(mMockAccessPoint.isOsuProvider()).thenReturn(false);
+
+        displayAndResume();
+
+        verify(mMockSsidPref, times(1)).setSummary(SSID);
+        verify(mMockSsidPref, times(1)).setVisible(true);
+    }
+
+    @Test
+    public void ssidPref_shouldHaveDetailTextSetForPasspointR2() {
+        setUpForConnectedNetwork();
+        when(mMockAccessPoint.isPasspoint()).thenReturn(false);
+        when(mMockAccessPoint.isOsuProvider()).thenReturn(true);
+
+        displayAndResume();
+
+        verify(mMockSsidPref, times(1)).setSummary(SSID);
+        verify(mMockSsidPref, times(1)).setVisible(true);
+    }
+
+    @Test
+    public void ssidPref_shouldNotShowIfNotPasspoint() {
+        setUpForConnectedNetwork();
+        when(mMockAccessPoint.isPasspoint()).thenReturn(false);
+        when(mMockAccessPoint.isOsuProvider()).thenReturn(false);
+
+        displayAndResume();
+
+        verify(mMockSsidPref).setVisible(false);
+    }
+
+    @Test
+    public void macAddressPref_shouldVisibleForConnectedNetwork() {
+        setUpForConnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockMacAddressPref).setVisible(true);
+        verify(mMockMacAddressPref).setSummary(MAC_ADDRESS);
+    }
+
+    @Test
+    public void macAddressPref_shouldVisibleAsRandomizedForDisconnectedNetwork() {
+        setUpForDisconnectedNetwork();
+        mMockWifiConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        when(mMockWifiConfig.getRandomizedMacAddress()).thenReturn(mMockMacAddress);
+        when(mMockMacAddress.toString()).thenReturn(RANDOMIZED_MAC_ADDRESS);
+
+        displayAndResume();
+
+        verify(mMockMacAddressPref).setVisible(true);
+        verify(mMockMacAddressPref).setSummary(RANDOMIZED_MAC_ADDRESS);
+    }
+
+    @Test
+    public void macAddressPref_shouldVisibleAsFactoryForDisconnectedNetwork() {
+        setUpForDisconnectedNetwork();
+        mMockWifiConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
+        when(mMockWifiManager.getFactoryMacAddresses())
+                .thenReturn(new String[]{FACTORY_MAC_ADDRESS});
+
+        displayAndResume();
+
+        verify(mMockMacAddressPref).setVisible(true);
+        verify(mMockMacAddressPref).setSummary(FACTORY_MAC_ADDRESS);
+    }
+
+    @Test
+    public void ipAddressPref_shouldHaveDetailTextSetForConnectedNetwork() {
+        setUpForConnectedNetwork();
+        mLinkProperties.addLinkAddress(Constants.IPV4_ADDR);
+
+        displayAndResume();
+
+        verify(mMockIpAddressPref).setSummary(Constants.IPV4_ADDR.getAddress().getHostAddress());
+        verify(mMockIpAddressPref).setVisible(true);
+    }
+
+    @Test
+    public void ipAddressPref_shouldInvisibleForDisconnectedNetwork() {
+        setUpForDisconnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockIpAddressPref).setVisible(false);
+    }
+
+    @Test
+    public void gatewayAndSubnet_shouldHaveDetailTextSetForConnectedNetwork() {
+        setUpForConnectedNetwork();
+        mLinkProperties.addLinkAddress(Constants.IPV4_ADDR);
+        mLinkProperties.addRoute(Constants.IPV4_DEFAULT);
+        mLinkProperties.addRoute(Constants.IPV4_SUBNET);
+
+        displayAndResume();
+
+        verify(mMockSubnetPref).setSummary("255.255.255.128");
+        verify(mMockGatewayPref).setSummary("192.0.2.127");
+        verify(mMockSubnetPref).setVisible(true);
+    }
+
+    @Test
+    public void gatewayAndSubnet_shouldInvisibleSetForDisconnectedNetwork() {
+        setUpForDisconnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockSubnetPref).setVisible(false);
+    }
+
+    @Test
+    public void dnsServersPref_shouldHaveDetailTextSetForConnectedNetwork()
+            throws UnknownHostException {
+        setUpForConnectedNetwork();
+        mLinkProperties.addDnsServer(InetAddress.getByAddress(new byte[] {8, 8, 4, 4}));
+        mLinkProperties.addDnsServer(InetAddress.getByAddress(new byte[] {8, 8, 8, 8}));
+        mLinkProperties.addDnsServer(Constants.IPV6_DNS);
+
+        displayAndResume();
+
+        verify(mMockDnsPref).setSummary(
+                "8.8.4.4\n" + "8.8.8.8\n" + Constants.IPV6_DNS.getHostAddress());
+        verify(mMockDnsPref).setVisible(true);
+    }
+
+    @Test
+    public void dnsServersPref_shouldInvisibleSetForDisconnectedNetwork()
+            throws UnknownHostException {
+        setUpForDisconnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockDnsPref).setVisible(false);
+    }
+
+    @Test
+    public void noCurrentNetwork_shouldNotFinishActivityForConnectedNetwork() {
+        setUpForConnectedNetwork();
+        when(mMockWifiManager.getCurrentNetwork()).thenReturn(null);
+
+        displayAndResume();
+
+        verify(mMockActivity, never()).finish();
+    }
+
+    @Test
+    public void noLinkProperties_allIpDetailsHidden() {
+        setUpForConnectedNetwork();
+        when(mMockConnectivityManager.getLinkProperties(mMockNetwork)).thenReturn(null);
+        reset(mMockIpv6Category, mMockIpAddressPref, mMockSubnetPref, mMockGatewayPref,
+                mMockDnsPref);
+
+        displayAndResume();
+
+        verify(mMockIpv6Category).setVisible(false);
+        verify(mMockIpAddressPref).setVisible(false);
+        verify(mMockSubnetPref).setVisible(false);
+        verify(mMockGatewayPref).setVisible(false);
+        verify(mMockDnsPref).setVisible(false);
+        verify(mMockIpv6Category, never()).setVisible(true);
+        verify(mMockIpAddressPref, never()).setVisible(true);
+        verify(mMockSubnetPref, never()).setVisible(true);
+        verify(mMockGatewayPref, never()).setVisible(true);
+        verify(mMockDnsPref, never()).setVisible(true);
+    }
+
+    @Test
+    public void disconnectedNetwork_allIpDetailsHidden() {
+        setUpForDisconnectedNetwork();
+        reset(mMockIpv6Category, mMockIpAddressPref, mMockSubnetPref, mMockGatewayPref,
+                mMockDnsPref);
+
+        displayAndResume();
+
+        verify(mMockIpv6Category).setVisible(false);
+        verify(mMockIpAddressPref).setVisible(false);
+        verify(mMockSubnetPref).setVisible(false);
+        verify(mMockGatewayPref).setVisible(false);
+        verify(mMockDnsPref).setVisible(false);
+        verify(mMockIpv6Category, never()).setVisible(true);
+        verify(mMockIpAddressPref, never()).setVisible(true);
+        verify(mMockSubnetPref, never()).setVisible(true);
+        verify(mMockGatewayPref, never()).setVisible(true);
+        verify(mMockDnsPref, never()).setVisible(true);
+    }
+
+    // Convenience method to convert a LinkAddress to a string without a prefix length.
+    private String asString(LinkAddress l) {
+        return l.getAddress().getHostAddress();
+    }
+
+    // Pretend that the NetworkCallback was triggered with a new copy of lp. We need to create a
+    // new copy because the code only updates if !mLinkProperties.equals(lp).
+    private void updateLinkProperties(LinkProperties lp) {
+        mCallbackCaptor.getValue().onLinkPropertiesChanged(mMockNetwork, new LinkProperties(lp));
+    }
+
+    private void updateNetworkCapabilities(NetworkCapabilities nc) {
+        mCallbackCaptor.getValue().onCapabilitiesChanged(mMockNetwork, new NetworkCapabilities(nc));
+    }
+
+    private NetworkCapabilities makeNetworkCapabilities() {
+        NetworkCapabilities nc = new NetworkCapabilities();
+        nc.clearAll();
+        nc.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+        return nc;
+    }
+
+    private void verifyDisplayedIpv6Addresses(InOrder inOrder, LinkAddress... addresses) {
+        String text = Arrays.stream(addresses)
+                .map(address -> asString(address))
+                .collect(Collectors.joining("\n"));
+        inOrder.verify(mMockIpv6AddressesPref).setSummary(text);
+    }
+
+    @Test
+    public void onLinkPropertiesChanged_updatesFields() {
+        setUpForConnectedNetwork();
+        displayAndResume();
+
+        InOrder inOrder = inOrder(mMockIpAddressPref, mMockGatewayPref, mMockSubnetPref,
+                mMockDnsPref, mMockIpv6Category, mMockIpv6AddressesPref);
+
+        LinkProperties lp = new LinkProperties();
+
+        lp.addLinkAddress(Constants.IPV6_LINKLOCAL);
+        updateLinkProperties(lp);
+        verifyDisplayedIpv6Addresses(inOrder, Constants.IPV6_LINKLOCAL);
+        inOrder.verify(mMockIpv6Category).setVisible(true);
+
+        lp.addRoute(Constants.IPV4_DEFAULT);
+        updateLinkProperties(lp);
+        inOrder.verify(mMockGatewayPref).setSummary(Constants.IPV4_GATEWAY.getHostAddress());
+        inOrder.verify(mMockGatewayPref).setVisible(true);
+
+        lp.addLinkAddress(Constants.IPV4_ADDR);
+        lp.addRoute(Constants.IPV4_SUBNET);
+        updateLinkProperties(lp);
+        inOrder.verify(mMockIpAddressPref).setSummary(asString(Constants.IPV4_ADDR));
+        inOrder.verify(mMockIpAddressPref).setVisible(true);
+        inOrder.verify(mMockSubnetPref).setSummary("255.255.255.128");
+        inOrder.verify(mMockSubnetPref).setVisible(true);
+
+        lp.addLinkAddress(Constants.IPV6_GLOBAL1);
+        lp.addLinkAddress(Constants.IPV6_GLOBAL2);
+        updateLinkProperties(lp);
+        verifyDisplayedIpv6Addresses(inOrder,
+                Constants.IPV6_LINKLOCAL,
+                Constants.IPV6_GLOBAL1,
+                Constants.IPV6_GLOBAL2);
+
+        lp.removeLinkAddress(Constants.IPV6_GLOBAL1);
+        updateLinkProperties(lp);
+        verifyDisplayedIpv6Addresses(inOrder,
+                Constants.IPV6_LINKLOCAL,
+                Constants.IPV6_GLOBAL2);
+
+        lp.addDnsServer(Constants.IPV6_DNS);
+        updateLinkProperties(lp);
+        inOrder.verify(mMockDnsPref).setSummary(Constants.IPV6_DNS.getHostAddress());
+        inOrder.verify(mMockDnsPref).setVisible(true);
+
+        lp.addDnsServer(Constants.IPV4_DNS1);
+        lp.addDnsServer(Constants.IPV4_DNS2);
+        updateLinkProperties(lp);
+        inOrder.verify(mMockDnsPref).setSummary(
+                Constants.IPV6_DNS.getHostAddress() + "\n"
+                        + Constants.IPV4_DNS1.getHostAddress() + "\n"
+                        + Constants.IPV4_DNS2.getHostAddress());
+        inOrder.verify(mMockDnsPref).setVisible(true);
+    }
+
+    @Test
+    public void onCapabilitiesChanged_callsRefreshIfNecessary() {
+        setUpForConnectedNetwork();
+        NetworkCapabilities nc = makeNetworkCapabilities();
+        when(mMockConnectivityManager.getNetworkCapabilities(mMockNetwork))
+                .thenReturn(new NetworkCapabilities(nc));
+
+        String summary = "Connected, no Internet";
+        when(mMockAccessPoint.getSettingsSummary(true /*convertSavedAsDisconnected*/))
+                .thenReturn(summary);
+
+        InOrder inOrder = inOrder(mMockHeaderController);
+        displayAndResume();
+        inOrder.verify(mMockHeaderController).setSummary(summary);
+
+        // Check that an irrelevant capability update does not update the access point summary, as
+        // doing so could cause unnecessary jank...
+        summary = "Connected";
+        when(mMockAccessPoint.getSettingsSummary(true /*convertSavedAsDisconnected*/))
+                .thenReturn(summary);
+        updateNetworkCapabilities(nc);
+        inOrder.verify(mMockHeaderController, never()).setSummary(any(CharSequence.class));
+
+        // ... but that if the network validates, then we do refresh.
+        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+        updateNetworkCapabilities(nc);
+        inOrder.verify(mMockHeaderController).setSummary(summary);
+
+        summary = "Connected, no Internet";
+        when(mMockAccessPoint.getSettingsSummary(true /*convertSavedAsDisconnected*/))
+                .thenReturn(summary);
+
+        // Another irrelevant update won't cause the UI to refresh...
+        updateNetworkCapabilities(nc);
+        inOrder.verify(mMockHeaderController, never()).setSummary(any(CharSequence.class));
+
+        // ... but if the network is no longer validated, then we display "connected, no Internet".
+        nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+        updateNetworkCapabilities(nc);
+        inOrder.verify(mMockHeaderController).setSummary(summary);
+
+        // UI will be refreshed when private DNS is broken.
+        summary = "Private DNS server cannot be accessed";
+        when(mMockAccessPoint.getSettingsSummary(true /* convertSavedAsDisconnected */))
+                .thenReturn(summary);
+        nc.setPrivateDnsBroken(true);
+        updateNetworkCapabilities(nc);
+        inOrder.verify(mMockHeaderController).setSummary(summary);
+
+        // UI will be refreshed when device connects to a partial connectivity network.
+        summary = "Limited connection";
+        when(mMockAccessPoint.getSettingsSummary(true /*convertSavedAsDisconnected*/))
+                .thenReturn(summary);
+        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+        updateNetworkCapabilities(nc);
+        inOrder.verify(mMockHeaderController).setSummary(summary);
+
+        // Although UI will be refreshed when network become validated. The Settings should
+        // continue to display "Limited connection" if network still provides partial connectivity.
+        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+        updateNetworkCapabilities(nc);
+        inOrder.verify(mMockHeaderController).setSummary(summary);
+    }
+
+    @Test
+    public void canForgetNetwork_shouldInvisibleIfWithoutConfiguration() {
+        setUpForConnectedNetwork();
+        when(mMockAccessPoint.getConfig()).thenReturn(null);
+        mController = newWifiDetailPreferenceController2();
+
+        displayAndResume();
+
+        verify(mMockButtonsPref).setButton1Visible(false);
+    }
+
+    @Test
+    public void canForgetNetwork_ephemeral() {
+        setUpForConnectedNetwork();
+        when(mMockWifiInfo.isEphemeral()).thenReturn(true);
+        when(mMockAccessPoint.getConfig()).thenReturn(null);
+
+        displayAndResume();
+
+        verify(mMockButtonsPref).setButton1Visible(true);
+    }
+
+    @Test
+    public void canForgetNetwork_saved() {
+        setUpForConnectedNetwork();
+        displayAndResume();
+
+        verify(mMockButtonsPref).setButton1Visible(true);
+    }
+
+    @Test
+    public void canForgetNetwork_lockedDown() {
+        setUpForConnectedNetwork();
+        lockDownNetwork();
+
+        displayAndResume();
+
+        verify(mMockButtonsPref).setButton1Visible(false);
+    }
+
+    @Test
+    public void canShareNetwork_shouldInvisibleIfWithoutConfiguration() {
+        setUpForConnectedNetwork();
+        when(mMockAccessPoint.getConfig()).thenReturn(null);
+
+        displayAndResume();
+
+        verify(mMockButtonsPref).setButton4Visible(false);
+    }
+
+    @Test
+    public void canModifyNetwork_saved() {
+        setUpForConnectedNetwork();
+        assertThat(mController.canModifyNetwork()).isTrue();
+    }
+
+    @Test
+    public void canModifyNetwork_lockedDown() {
+        setUpForConnectedNetwork();
+        lockDownNetwork();
+
+        assertThat(mController.canModifyNetwork()).isFalse();
+    }
+
+    /**
+     * Pretends that current network is locked down by device owner.
+     */
+    private void lockDownNetwork() {
+        final int doUserId = 123;
+        final int doUid = 1234;
+        String doPackage = "some.package";
+
+        mMockWifiConfig.creatorUid = doUid;
+        ComponentName doComponent = new ComponentName(doPackage, "some.Class");
+        try {
+            when(mMockPackageManager.getPackageUidAsUser(Matchers.anyString(), Matchers.anyInt()))
+                    .thenReturn(doUid);
+        } catch (PackageManager.NameNotFoundException e) {
+            //do nothing
+        }
+        ShadowDevicePolicyManager.getShadow().setDeviceOwnerComponentOnAnyUser(doComponent);
+        ShadowDevicePolicyManager.getShadow().setDeviceOwnerUserId(doUserId);
+
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 1);
+    }
+
+    @Test
+    public void forgetNetwork_ephemeral() {
+        setUpForConnectedNetwork();
+        String ssid = "ssid";
+        when(mMockWifiInfo.isEphemeral()).thenReturn(true);
+        when(mMockWifiInfo.getSSID()).thenReturn(ssid);
+
+        displayAndResume();
+        mForgetClickListener.getValue().onClick(null);
+
+        verify(mMockWifiManager).disableEphemeralNetwork(ssid);
+        verify(mMockMetricsFeatureProvider)
+                .action(mMockActivity, MetricsProto.MetricsEvent.ACTION_WIFI_FORGET);
+    }
+
+    @Test
+    public void forgetNetwork_saved() {
+        setUpForConnectedNetwork();
+        mMockWifiConfig.networkId = 5;
+
+        mController.displayPreference(mMockScreen);
+        mForgetClickListener.getValue().onClick(null);
+
+        verify(mMockWifiManager).forget(mMockWifiConfig.networkId, null);
+        verify(mMockMetricsFeatureProvider)
+                .action(mMockActivity, MetricsProto.MetricsEvent.ACTION_WIFI_FORGET);
+    }
+
+    @Test
+    public void forgetNetwork_shouldShowDialog() {
+        setUpForConnectedNetwork();
+        final WifiDetailPreferenceController2 spyController = spy(mController);
+
+        mMockWifiConfig.networkId = 5;
+        when(mMockAccessPoint.isPasspoint()).thenReturn(true);
+        when(mMockAccessPoint.getPasspointFqdn()).thenReturn(FQDN);
+        spyController.displayPreference(mMockScreen);
+
+        mForgetClickListener.getValue().onClick(null);
+
+        verify(mMockWifiManager, times(0)).removePasspointConfiguration(FQDN);
+        verify(mMockMetricsFeatureProvider, times(0))
+                .action(mMockActivity, MetricsProto.MetricsEvent.ACTION_WIFI_FORGET);
+        verify(spyController).showConfirmForgetDialog();
+    }
+
+    @Test
+    public void networkStateChangedIntent_shouldRefetchInfo() {
+        setUpForConnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockConnectivityManager, times(1)).getNetworkInfo(any(Network.class));
+        verify(mMockWifiManager, times(1)).getConnectionInfo();
+
+        mContext.sendBroadcast(new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION));
+
+        verify(mMockConnectivityManager, times(2)).getNetworkInfo(any(Network.class));
+        verify(mMockWifiManager, times(2)).getConnectionInfo();
+    }
+
+    @Test
+    public void networkStateChangedIntent_shouldRefetchInfoForConnectedNetwork() {
+        setUpForConnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockConnectivityManager, times(1)).getNetworkInfo(any(Network.class));
+        verify(mMockWifiManager, times(1)).getConnectionInfo();
+
+        mContext.sendBroadcast(new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION));
+
+        verify(mMockConnectivityManager, times(2)).getNetworkInfo(any(Network.class));
+        verify(mMockWifiManager, times(2)).getConnectionInfo();
+    }
+
+    @Test
+    public void rssiChangedIntent_shouldRefetchInfo() {
+        setUpForConnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockConnectivityManager, times(1)).getNetworkInfo(any(Network.class));
+        verify(mMockWifiManager, times(1)).getConnectionInfo();
+
+        mContext.sendBroadcast(new Intent(WifiManager.RSSI_CHANGED_ACTION));
+
+        verify(mMockConnectivityManager, times(2)).getNetworkInfo(any(Network.class));
+        verify(mMockWifiManager, times(2)).getConnectionInfo();
+    }
+
+    @Test
+    public void rssiChangedIntent_shouldRefetchInfoForConnectedNetwork() {
+        setUpForConnectedNetwork();
+        displayAndResume();
+
+        verify(mMockConnectivityManager, times(1)).getNetworkInfo(any(Network.class));
+        verify(mMockWifiManager, times(1)).getConnectionInfo();
+
+        mContext.sendBroadcast(new Intent(WifiManager.RSSI_CHANGED_ACTION));
+
+        verify(mMockConnectivityManager, times(2)).getNetworkInfo(any(Network.class));
+        verify(mMockWifiManager, times(2)).getConnectionInfo();
+    }
+
+    @Test
+    public void networkDisconnectedState_shouldNotFinishActivityForConnectedNetwork() {
+        setUpForConnectedNetwork();
+
+        displayAndResume();
+
+        when(mMockConnectivityManager.getNetworkInfo(any(Network.class))).thenReturn(null);
+        mContext.sendBroadcast(new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION));
+
+        verify(mMockActivity, never()).finish();
+    }
+
+    @Test
+    public void networkOnLost_shouldNotFinishActivityForConnectedNetwork() {
+        setUpForConnectedNetwork();
+
+        displayAndResume();
+
+        mCallbackCaptor.getValue().onLost(mMockNetwork);
+
+        verify(mMockActivity, never()).finish();
+    }
+
+    @Test
+    public void ipv6AddressPref_shouldHaveHostAddressTextSet() {
+        setUpForConnectedNetwork();
+        mLinkProperties.addLinkAddress(Constants.IPV6_LINKLOCAL);
+        mLinkProperties.addLinkAddress(Constants.IPV6_GLOBAL1);
+        mLinkProperties.addLinkAddress(Constants.IPV6_GLOBAL2);
+
+        displayAndResume();
+
+        String expectedAddresses = String.join("\n",
+                asString(Constants.IPV6_LINKLOCAL),
+                asString(Constants.IPV6_GLOBAL1),
+                asString(Constants.IPV6_GLOBAL2));
+
+        verify(mMockIpv6AddressesPref).setSummary(expectedAddresses);
+    }
+
+    @Test
+    public void ipv6AddressPref_shouldNotBeSelectable() {
+        setUpForConnectedNetwork();
+        mLinkProperties.addLinkAddress(Constants.IPV6_GLOBAL2);
+
+        displayAndResume();
+
+        assertThat(mMockIpv6AddressesPref.isSelectable()).isFalse();
+    }
+
+    @Test
+    public void captivePortal_shouldShowSignInButton() {
+        setUpForConnectedNetwork();
+
+        InOrder inOrder = inOrder(mMockButtonsPref);
+
+        displayAndResume();
+
+        inOrder.verify(mMockButtonsPref).setButton2Visible(false);
+
+        NetworkCapabilities nc = makeNetworkCapabilities();
+        updateNetworkCapabilities(nc);
+        inOrder.verify(mMockButtonsPref).setButton2Visible(false);
+
+        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
+        updateNetworkCapabilities(nc);
+        inOrder.verify(mMockButtonsPref).setButton2Visible(true);
+
+        nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
+        updateNetworkCapabilities(nc);
+        inOrder.verify(mMockButtonsPref).setButton2Visible(false);
+    }
+
+    @Test
+    public void testSignInButton_shouldStartCaptivePortalApp() {
+        setUpForConnectedNetwork();
+
+        displayAndResume();
+
+        ArgumentCaptor<OnClickListener> captor = ArgumentCaptor.forClass(OnClickListener.class);
+        verify(mMockButtonsPref).setButton2OnClickListener(captor.capture());
+        captor.getValue().onClick(null);
+        verify(mMockConnectivityManager).startCaptivePortalApp(mMockNetwork);
+        verify(mMockMetricsFeatureProvider)
+                .action(mMockActivity, MetricsProto.MetricsEvent.ACTION_WIFI_SIGNIN);
+    }
+
+    @Test
+    public void testSignInButton_shouldHideSignInButtonForDisconnectedNetwork() {
+        setUpForDisconnectedNetwork();
+        NetworkCapabilities nc = makeNetworkCapabilities();
+        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
+        when(mMockConnectivityManager.getNetworkCapabilities(mMockNetwork))
+                .thenReturn(new NetworkCapabilities(nc));
+
+        // verify onResume
+        displayAndResume();
+
+        verify(mMockButtonsPref, never()).setButton2Visible(true);
+        verify(mMockButtonsPref).setButton2Visible(false);
+
+        // verify onCapabilitiesChanged
+        updateNetworkCapabilities(nc);
+
+        verify(mMockButtonsPref, never()).setButton2Visible(true);
+        verify(mMockButtonsPref).setButton2Visible(false);
+    }
+
+    @Test
+    public void testConnectButton_shouldInvisibleForConnectNetwork() {
+        setUpForConnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockButtonsPref, times(1)).setButton3Visible(false);
+    }
+
+    @Test
+    public void testConnectButton_shouldVisibleForDisconnectNetwork() {
+        setUpForDisconnectedNetwork();
+
+        displayAndResume();
+
+        verify(mMockButtonsPref, times(1)).setButton3Visible(true);
+        verify(mMockButtonsPref, times(1)).setButton3Text(R.string.wifi_connect);
+    }
+
+    private void setUpForToast() {
+        Resources res = mContext.getResources();
+        when(mMockActivity.getResources()).thenReturn(res);
+    }
+
+    @Test
+    public void testConnectButton_clickConnect_displayAsSuccess() {
+        setUpForDisconnectedNetwork();
+        when(mMockWifiManager.isWifiEnabled()).thenReturn(true);
+        InOrder inOrder = inOrder(mMockButtonsPref);
+        String label = "title";
+        when(mMockAccessPoint.getTitle()).thenReturn(label);
+        setUpForToast();
+
+        displayAndResume();
+
+        // check connect button exist
+        verifyConnectBtnSetUpAsVisible(inOrder);
+
+        // click connect button
+        mController.connectNetwork();
+
+        // check display button as connecting
+        verify(mMockWifiManager, times(1)).connect(anyInt(), any(WifiManager.ActionListener.class));
+        verifyConnectBtnSetUpAsConnecting(inOrder);
+
+        // update as connected
+        when(mMockAccessPoint.isActive()).thenReturn(true);
+        mController.updateAccessPoint();
+
+        // check connect button invisible, be init as default state and toast success message
+        verifyConnectBtnBeInitAsDefault(inOrder);
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(false);
+        assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(
+                mContext.getString(R.string.wifi_connected_to_message, label));
+    }
+
+    @Test
+    public void testConnectButton_clickConnectButFailed_displayFailMessage() {
+        setUpForDisconnectedNetwork();
+        ArgumentCaptor<WifiManager.ActionListener> connectListenerCaptor =
+                ArgumentCaptor.forClass(WifiManager.ActionListener.class);
+        when(mMockWifiManager.isWifiEnabled()).thenReturn(true);
+        InOrder inOrder = inOrder(mMockButtonsPref);
+        setUpForToast();
+
+        displayAndResume();
+
+        // check connect button exist
+        verifyConnectBtnSetUpAsVisible(inOrder);
+
+        // click connect button
+        mController.connectNetwork();
+
+        // check display button as connecting
+        verify(mMockWifiManager, times(1)).connect(anyInt(), connectListenerCaptor.capture());
+        verifyConnectBtnSetUpAsConnecting(inOrder);
+
+        // update as failed
+        connectListenerCaptor.getValue().onFailure(-1);
+
+        // check connect button visible, be init as default and toast failed message
+        verifyConnectBtnBeInitAsDefault(inOrder);
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(true);
+        assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(
+                mContext.getString(R.string.wifi_failed_connect_message));
+    }
+
+    private void verifyConnectBtnSetUpAsVisible(InOrder inOrder) {
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Text(R.string.wifi_connect);
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Icon(R.drawable.ic_settings_wireless);
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(true);
+    }
+
+    private void verifyConnectBtnSetUpAsConnecting(InOrder inOrder) {
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Text(R.string.wifi_connecting);
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Enabled(false);
+    }
+
+    private void verifyConnectBtnBeInitAsDefault(InOrder inOrder) {
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Text(R.string.wifi_connect);
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Icon(R.drawable.ic_settings_wireless);
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Enabled(true);
+    }
+
+    @Test
+    public void testConnectButton_clickConnectButTimeout_displayFailMessage() {
+        setUpForDisconnectedNetwork();
+        when(mMockWifiManager.isWifiEnabled()).thenReturn(true);
+        InOrder inOrder = inOrder(mMockButtonsPref);
+        setUpForToast();
+
+        displayAndResume();
+
+        // check connect button exist
+        verifyConnectBtnSetUpAsVisible(inOrder);
+
+        // click connect button
+        mController.connectNetwork();
+
+        // check display button as connecting
+        verify(mMockWifiManager, times(1)).connect(anyInt(), any(WifiManager.ActionListener.class));
+        verifyConnectBtnSetUpAsConnecting(inOrder);
+
+        // update as failed
+        mController.sTimer.onFinish();
+
+        // check connect button visible, be init as default and toast failed message
+        verifyConnectBtnBeInitAsDefault(inOrder);
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(true);
+        assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(
+                mContext.getString(R.string.wifi_failed_connect_message));
+    }
+
+    @Test
+    public void testConnectButton_clickConnectButTimeout_displayNotInRangeMessage() {
+        setUpForNotInRangeNetwork();
+        when(mMockWifiManager.isWifiEnabled()).thenReturn(true);
+        InOrder inOrder = inOrder(mMockButtonsPref);
+        setUpForToast();
+
+        displayAndResume();
+
+        // check connect button exist
+        verifyConnectBtnSetUpAsVisible(inOrder);
+
+        // click connect button
+        mController.connectNetwork();
+
+        // check display button as connecting
+        verify(mMockWifiManager, times(1)).connect(anyInt(), any(WifiManager.ActionListener.class));
+        verifyConnectBtnSetUpAsConnecting(inOrder);
+
+        // update as failed
+        mController.sTimer.onFinish();
+
+        // check connect button visible, be init as default and toast failed message
+        verifyConnectBtnBeInitAsDefault(inOrder);
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(true);
+        assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(
+                mContext.getString(R.string.wifi_not_in_range_message));
+    }
+
+    @Test
+    public void testConnectButton_clickConnectWhenWiFiDisabled_displaySuccessMessage() {
+        setUpForDisconnectedNetwork();
+        when(mMockWifiManager.isWifiEnabled()).thenReturn(false); // wifi disabled
+        InOrder inOrder = inOrder(mMockButtonsPref);
+        String label = "title";
+        when(mMockAccessPoint.getTitle()).thenReturn(label);
+        setUpForToast();
+
+        displayAndResume();
+
+        // check connect button exist
+        verifyConnectBtnSetUpAsVisible(inOrder);
+
+        // click connect button
+        mController.connectNetwork();
+
+        // check turn on Wi-Fi, display button as connecting and toast turn on Wi-Fi message
+        verify(mMockWifiManager, times(1)).setWifiEnabled(true);
+        verifyConnectBtnSetUpAsConnecting(inOrder);
+        assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(
+                mContext.getString(R.string.wifi_turned_on_message));
+
+        // notify Wi-Fi enabled
+        mController.mWifiListener.onWifiStateChanged(WifiManager.WIFI_STATE_ENABLED);
+
+        // check had connect network and icon display as expected
+        verify(mMockWifiManager, times(1)).connect(anyInt(), any(WifiManager.ActionListener.class));
+        verifyConnectBtnSetUpAsConnecting(inOrder);
+
+        // update as connected
+        when(mMockAccessPoint.isActive()).thenReturn(true);
+        mController.updateAccessPoint();
+
+        // check connect button invisible, be init as default state and toast success message
+        verifyConnectBtnBeInitAsDefault(inOrder);
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(false);
+        assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(
+                mContext.getString(R.string.wifi_connected_to_message, label));
+    }
+
+    @Test
+    public void testConnectButton_clickConnectWhenWiFiDisabled_failedToConnectWiFi() {
+        setUpForDisconnectedNetwork();
+        when(mMockWifiManager.isWifiEnabled()).thenReturn(false); // wifi disabled
+        InOrder inOrder = inOrder(mMockButtonsPref);
+        setUpForToast();
+
+        displayAndResume();
+
+        // check connect button exist
+        verifyConnectBtnSetUpAsVisible(inOrder);
+
+        // click connect button
+        mController.connectNetwork();
+
+        // check turn on Wi-Fi, display button as connecting and toast turn on Wi-Fi message
+        verify(mMockWifiManager, times(1)).setWifiEnabled(true);
+        verifyConnectBtnSetUpAsConnecting(inOrder);
+        assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(
+                mContext.getString(R.string.wifi_turned_on_message));
+
+        // notify Wi-Fi enabled
+        mController.mWifiListener.onWifiStateChanged(WifiManager.WIFI_STATE_ENABLED);
+
+        // check had connect network and icon display as expected
+        verify(mMockWifiManager, times(1)).connect(anyInt(), any(WifiManager.ActionListener.class));
+        verifyConnectBtnSetUpAsConnecting(inOrder);
+
+        // update as failed
+        mController.sTimer.onFinish();
+
+        // check connect button visible, be init as default and toast failed message
+        verifyConnectBtnBeInitAsDefault(inOrder);
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(true);
+        assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(
+                mContext.getString(R.string.wifi_failed_connect_message));
+    }
+
+    @Test
+    public void
+            testConnectButton_clickConnectWhenWiFiDisabled_failedToConnectWifiBecauseNotInRange() {
+        setUpForNotInRangeNetwork();
+        when(mMockWifiManager.isWifiEnabled()).thenReturn(false); // wifi disabled
+        InOrder inOrder = inOrder(mMockButtonsPref);
+        setUpForToast();
+
+        displayAndResume();
+
+        // check connect button exist
+        verifyConnectBtnSetUpAsVisible(inOrder);
+
+        // click connect button
+        mController.connectNetwork();
+
+        // check turn on Wi-Fi, display button as connecting and toast turn on Wi-Fi message
+        verify(mMockWifiManager, times(1)).setWifiEnabled(true);
+        verifyConnectBtnSetUpAsConnecting(inOrder);
+        assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(
+                mContext.getString(R.string.wifi_turned_on_message));
+
+        // notify Wi-Fi enabled
+        mController.mWifiListener.onWifiStateChanged(WifiManager.WIFI_STATE_ENABLED);
+
+        // check had connect network and icon display as expected
+        verify(mMockWifiManager, times(1)).connect(anyInt(), any(WifiManager.ActionListener.class));
+        verifyConnectBtnSetUpAsConnecting(inOrder);
+
+        // update as failed
+        mController.sTimer.onFinish();
+
+        // check connect button visible, be init as default and toast failed message
+        verifyConnectBtnBeInitAsDefault(inOrder);
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(true);
+        assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(
+                mContext.getString(R.string.wifi_not_in_range_message));
+    }
+
+    @Test
+    public void testConnectButton_clickConnectWhenWiFiDisabled_failedToEnableWifi() {
+        setUpForDisconnectedNetwork();
+        when(mMockWifiManager.isWifiEnabled()).thenReturn(false); // wifi disabled
+        InOrder inOrder = inOrder(mMockButtonsPref);
+        setUpForToast();
+
+        displayAndResume();
+
+        // check connect button exist
+        verifyConnectBtnSetUpAsVisible(inOrder);
+
+        // click connect button
+        mController.connectNetwork();
+
+        // check turn on Wi-Fi, display button as connecting and toast turn on Wi-Fi message
+        verify(mMockWifiManager, times(1)).setWifiEnabled(true);
+        verifyConnectBtnSetUpAsConnecting(inOrder);
+        assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(
+                mContext.getString(R.string.wifi_turned_on_message));
+
+        // notify turn on Wi-Fi failed
+        mController.sTimer.onFinish();
+
+        // check connect button visible, be init as default and toast failed message
+        verifyConnectBtnBeInitAsDefault(inOrder);
+        inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(true);
+        assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(
+                mContext.getString(R.string.wifi_failed_connect_message));
+    }
+
+    @Test
+    public void testConnectButton_clickConnectAndBackKey_ignoreTimeoutEvent() {
+        setUpForDisconnectedNetwork();
+        when(mMockWifiManager.isWifiEnabled()).thenReturn(true);
+        InOrder inOrder = inOrder(mMockButtonsPref);
+        setUpForToast();
+
+        displayAndResume();
+
+        // check connect button exist
+        verifyConnectBtnSetUpAsVisible(inOrder);
+
+        // click connect button
+        mController.connectNetwork();
+
+        // check display button as connecting
+        verify(mMockWifiManager, times(1)).connect(anyInt(), any(WifiManager.ActionListener.class));
+        verifyConnectBtnSetUpAsConnecting(inOrder);
+
+        // leave detail page
+        when(mMockFragment.getActivity()).thenReturn(null);
+
+        // timeout happened
+        mController.sTimer.onFinish();
+
+        // check connect button visible, be init as default and toast failed message
+        inOrder.verify(mMockButtonsPref, never()).setButton3Text(R.string.wifi_connect);
+        inOrder.verify(mMockButtonsPref, never()).setButton3Icon(R.drawable.ic_settings_wireless);
+        inOrder.verify(mMockButtonsPref, never()).setButton3Enabled(true);
+        inOrder.verify(mMockButtonsPref, never()).setButton3Visible(true);
+        assertThat(ShadowToast.shownToastCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void updateAccessPoint_returnFalseForNothingChanged() {
+        setUpForDisconnectedNetwork();
+
+        displayAndResume();
+        boolean changed = mController.updateAccessPoint();
+
+        assertThat(changed).isFalse();
+    }
+
+    @Test
+    public void updateAccessPoint_returnTrueForSignalLevelChanged() {
+        setUpForDisconnectedNetwork();
+
+        displayAndResume();
+
+        // Level changed
+        when(mMockAccessPoint.getLevel()).thenReturn(LEVEL + 1);
+        boolean changed = mController.updateAccessPoint();
+
+        assertThat(changed).isTrue();
+    }
+
+    @Test
+    public void updateAccessPoint_returnTrueForChangeAsNotInRange() {
+        setUpForDisconnectedNetwork();
+
+        displayAndResume();
+
+        // change as not in range
+        when(mMockAccessPoint.matches(any(AccessPoint.class))).thenReturn(false);
+        boolean changed = mController.updateAccessPoint();
+
+        assertThat(changed).isTrue();
+    }
+
+    @Test
+    public void updateAccessPoint_returnTrueForChangeAsInRange() {
+        setUpForNotInRangeNetwork();
+
+        displayAndResume();
+
+        // change as in range
+        when(mMockAccessPoint.matches(any(AccessPoint.class))).thenReturn(true);
+        boolean changed = mController.updateAccessPoint();
+
+        assertThat(changed).isTrue();
+    }
+
+    @Test
+    public void updateAccessPoint_returnTrueForChangeAsConnected() {
+        setUpForDisconnectedNetwork();
+
+        displayAndResume();
+
+        // change as connected
+        when(mMockAccessPoint.isActive()).thenReturn(true);
+        boolean changed = mController.updateAccessPoint();
+
+        assertThat(changed).isTrue();
+    }
+
+    @Test
+    public void updateAccessPoint_returnTrueForChangeAsDisconnected() {
+        setUpForConnectedNetwork();
+
+        displayAndResume();
+
+        // change as disconnected
+        when(mMockAccessPoint.isActive()).thenReturn(false);
+        boolean changed = mController.updateAccessPoint();
+
+        assertThat(changed).isTrue();
+    }
+
+    @Test
+    public void updateAccessPoint_returnTrueForAccessPointUpdated() {
+        setUpForConnectedNetwork();
+
+        displayAndResume();
+
+        // change as disconnected
+        when(mMockAccessPoint.update(mMockWifiConfig, mMockWifiInfo, mMockNetworkInfo))
+                .thenReturn(true);
+        boolean changed = mController.updateAccessPoint();
+
+        assertThat(changed).isTrue();
+    }
+
+    @Test
+    public void testRefreshRssiViews_shouldNotUpdateIfLevelIsSameForConnectedNetwork() {
+        setUpForConnectedNetwork();
+        displayAndResume();
+
+        mContext.sendBroadcast(new Intent(WifiManager.RSSI_CHANGED_ACTION));
+
+        verify(mMockAccessPoint, times(3)).getLevel();
+        verify(mMockIconInjector, times(1)).getIcon(anyInt());
+    }
+
+    @Test
+    public void testRefreshRssiViews_shouldUpdateOnLevelChangeForConnectedNetwork() {
+        setUpForConnectedNetwork();
+        displayAndResume();
+
+        when(mMockAccessPoint.getLevel()).thenReturn(0);
+        mContext.sendBroadcast(new Intent(WifiManager.RSSI_CHANGED_ACTION));
+
+        verify(mMockAccessPoint, times(4)).getLevel();
+        verify(mMockIconInjector, times(2)).getIcon(anyInt());
+    }
+
+    @Test
+    public void testRefreshRssiViews_shouldNotUpdateForNotInRangeNetwork() {
+        setUpForNotInRangeNetwork();
+
+        displayAndResume();
+
+        when(mMockAccessPoint.getLevel()).thenReturn(0);
+        mContext.sendBroadcast(new Intent(WifiManager.RSSI_CHANGED_ACTION));
+
+        verify(mMockSignalStrengthPref, times(2)).setVisible(false);
+    }
+
+    @Test
+    public void testRedrawIconForHeader_shouldEnlarge() {
+        setUpForConnectedNetwork();
+        ArgumentCaptor<BitmapDrawable> drawableCaptor =
+                ArgumentCaptor.forClass(BitmapDrawable.class);
+        Drawable original = mContext.getDrawable(Utils.getWifiIconResource(LEVEL)).mutate();
+        when(mMockIconInjector.getIcon(anyInt())).thenReturn(original);
+
+        displayAndResume();
+
+        verify(mMockHeaderController, times(1)).setIcon(drawableCaptor.capture());
+
+        int expectedSize = mContext.getResources().getDimensionPixelSize(
+                R.dimen.wifi_detail_page_header_image_size);
+        BitmapDrawable icon = drawableCaptor.getValue();
+        assertThat(icon.getMinimumWidth()).isEqualTo(expectedSize);
+        assertThat(icon.getMinimumHeight()).isEqualTo(expectedSize);
+    }
+
+    @Test
+    public void testRedrawIconForHeader_shouldEnlargeForDisconnectedNetwork() {
+        setUpForDisconnectedNetwork();
+        ArgumentCaptor<BitmapDrawable> drawableCaptor =
+                ArgumentCaptor.forClass(BitmapDrawable.class);
+        Drawable original = mContext.getDrawable(Utils.getWifiIconResource(LEVEL)).mutate();
+        when(mMockIconInjector.getIcon(anyInt())).thenReturn(original);
+
+        displayAndResume();
+
+        verify(mMockHeaderController, times(1)).setIcon(drawableCaptor.capture());
+
+        int expectedSize = mContext.getResources().getDimensionPixelSize(
+                R.dimen.wifi_detail_page_header_image_size);
+        BitmapDrawable icon = drawableCaptor.getValue();
+        assertThat(icon.getMinimumWidth()).isEqualTo(expectedSize);
+        assertThat(icon.getMinimumHeight()).isEqualTo(expectedSize);
+    }
+
+    @Test
+    public void testRedrawIconForHeader_shouldNotEnlargeIfNotVectorDrawable() {
+        setUpForConnectedNetwork();
+        ArgumentCaptor<ColorDrawable> drawableCaptor =
+                ArgumentCaptor.forClass(ColorDrawable.class);
+
+        displayAndResume();
+
+        verify(mMockHeaderController, times(1)).setIcon(drawableCaptor.capture());
+        ColorDrawable icon = drawableCaptor.getValue();
+        assertThat(icon).isNotNull();
+    }
+
+    @Test
+    public void checkMacTitle_whenPrivacyRandomizedMac_shouldBeRandom() {
+        setUpForDisconnectedNetwork();
+        mMockWifiConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        when(mMockWifiConfig.getRandomizedMacAddress()).thenReturn(mMockMacAddress);
+        when(mMockMacAddress.toString()).thenReturn(RANDOMIZED_MAC_ADDRESS);
+
+        displayAndResume();
+
+        verify(mMockMacAddressPref).setTitle(R.string.wifi_advanced_randomized_mac_address_title);
+    }
+
+    @Test
+    public void checkMacTitle_whenPrivacyDeviceMac_shouldBeFactory() {
+        setUpForDisconnectedNetwork();
+        mMockWifiConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
+        when(mMockWifiConfig.getRandomizedMacAddress()).thenReturn(mMockMacAddress);
+        when(mMockWifiManager.getFactoryMacAddresses())
+                .thenReturn(new String[]{FACTORY_MAC_ADDRESS});
+
+        displayAndResume();
+
+        verify(mMockMacAddressPref).setTitle(R.string.wifi_advanced_device_mac_address_title);
+    }
+
+    @Test
+    @Ignore
+    public void entityHeader_expiredPasspointR1_shouldHandleExpiration() {
+        setUpForDisconnectedNetwork();
+        when(mMockAccessPoint.isPasspoint()).thenReturn(true);
+        when(mMockAccessPoint.isPasspointConfigurationR1()).thenReturn(true);
+        when(mMockAccessPoint.isExpired()).thenReturn(true);
+        String expireSummary = mContext.getResources().getString(
+                com.android.settingslib.R.string.wifi_passpoint_expired);
+
+        displayAndResume();
+
+        verify(mMockButtonsPref).setButton3Visible(false);
+        verify(mMockHeaderController).setSummary(expireSummary);
+    }
+
+    private ActionButtonsPreference createMock() {
+        final ActionButtonsPreference pref = mock(ActionButtonsPreference.class);
+        when(pref.setButton1Text(anyInt())).thenReturn(pref);
+        when(pref.setButton1Icon(anyInt())).thenReturn(pref);
+        when(pref.setButton1Enabled(anyBoolean())).thenReturn(pref);
+        when(pref.setButton1Visible(anyBoolean())).thenReturn(pref);
+        when(pref.setButton1OnClickListener(any(View.OnClickListener.class))).thenReturn(pref);
+
+        when(pref.setButton2Text(anyInt())).thenReturn(pref);
+        when(pref.setButton2Icon(anyInt())).thenReturn(pref);
+        when(pref.setButton2Enabled(anyBoolean())).thenReturn(pref);
+        when(pref.setButton2Visible(anyBoolean())).thenReturn(pref);
+        when(pref.setButton2OnClickListener(any(View.OnClickListener.class))).thenReturn(pref);
+
+        when(pref.setButton3Text(anyInt())).thenReturn(pref);
+        when(pref.setButton3Icon(anyInt())).thenReturn(pref);
+        when(pref.setButton3Enabled(anyBoolean())).thenReturn(pref);
+        when(pref.setButton3Visible(anyBoolean())).thenReturn(pref);
+        when(pref.setButton3OnClickListener(any(View.OnClickListener.class))).thenReturn(pref);
+
+        when(pref.setButton4Text(anyInt())).thenReturn(pref);
+        when(pref.setButton4Icon(anyInt())).thenReturn(pref);
+        when(pref.setButton4Enabled(anyBoolean())).thenReturn(pref);
+        when(pref.setButton4Visible(anyBoolean())).thenReturn(pref);
+        when(pref.setButton4OnClickListener(any(View.OnClickListener.class))).thenReturn(pref);
+
+        return pref;
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2Test.java
new file mode 100644
index 0000000..517c96a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2Test.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 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.details2;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+
+import androidx.preference.DropDownPreference;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class WifiMeteredPreferenceController2Test {
+
+    private static final int METERED_OVERRIDE_NONE = 0;
+    private static final int METERED_OVERRIDE_METERED = 1;
+    private static final int METERED_OVERRIDE_NOT_METERED = 2;
+
+    @Mock
+    private WifiConfiguration mWifiConfiguration;
+
+    private WifiMeteredPreferenceController2 mPreferenceController;
+    private Context mContext;
+    private DropDownPreference mDropDownPreference;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+
+        mPreferenceController = spy(
+                new WifiMeteredPreferenceController2(mContext, mWifiConfiguration));
+        mDropDownPreference = new DropDownPreference(mContext);
+        mDropDownPreference.setEntries(R.array.wifi_metered_entries);
+        mDropDownPreference.setEntryValues(R.array.wifi_metered_values);
+    }
+
+    @Test
+    public void testUpdateState_wifiMetered_setCorrectValue() {
+        doReturn(METERED_OVERRIDE_METERED).when(mPreferenceController).getMeteredOverride();
+
+        mPreferenceController.updateState(mDropDownPreference);
+
+        assertThat(mDropDownPreference.getEntry()).isEqualTo("Treat as metered");
+    }
+
+    @Test
+    public void testUpdateState_wifiNotMetered_setCorrectValue() {
+        doReturn(METERED_OVERRIDE_NOT_METERED).when(mPreferenceController).getMeteredOverride();
+
+        mPreferenceController.updateState(mDropDownPreference);
+
+        assertThat(mDropDownPreference.getEntry()).isEqualTo("Treat as unmetered");
+    }
+
+    @Test
+    public void testUpdateState_wifiAuto_setCorrectValue() {
+        doReturn(METERED_OVERRIDE_NONE).when(mPreferenceController).getMeteredOverride();
+
+        mPreferenceController.updateState(mDropDownPreference);
+
+        assertThat(mDropDownPreference.getEntry()).isEqualTo("Detect automatically");
+    }
+
+    @Test
+    public void testController_resilientToNullConfig() {
+        mPreferenceController = spy(new WifiMeteredPreferenceController2(mContext, null));
+
+        mPreferenceController.getMeteredOverride();
+        mPreferenceController.onPreferenceChange(mDropDownPreference, 1);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2Test.java
new file mode 100644
index 0000000..91cc01e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2Test.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 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.details2;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+
+import androidx.preference.DropDownPreference;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class WifiPrivacyPreferenceController2Test {
+
+    private static final int PRIVACY_RANDOMIZED = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+    private static final int PRIVACY_TRUSTED = WifiConfiguration.RANDOMIZATION_NONE;
+
+    @Mock
+    private WifiConfiguration mWifiConfiguration;
+
+    private WifiPrivacyPreferenceController2 mPreferenceController;
+    private Context mContext;
+    private DropDownPreference mDropDownPreference;
+    private String[] mPerferenceStrings;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+
+        WifiPrivacyPreferenceController2 preferenceController =
+                new WifiPrivacyPreferenceController2(mContext);
+        preferenceController.setWifiConfiguration(mWifiConfiguration);
+        mPreferenceController = spy(preferenceController);
+        mDropDownPreference = new DropDownPreference(mContext);
+        mDropDownPreference.setEntries(R.array.wifi_privacy_entries);
+        mDropDownPreference.setEntryValues(R.array.wifi_privacy_values);
+
+        mPerferenceStrings = mContext.getResources().getStringArray(R.array.wifi_privacy_entries);
+    }
+
+    @Test
+    public void testUpdateState_wifiPrivacy_setCorrectValue() {
+        doReturn(PRIVACY_TRUSTED).when(mPreferenceController).getRandomizationValue();
+
+        mPreferenceController.updateState(mDropDownPreference);
+
+        int prefValue = mPreferenceController.translateMacRandomizedValueToPrefValue(
+                PRIVACY_TRUSTED);
+        assertThat(mDropDownPreference.getEntry()).isEqualTo(mPerferenceStrings[prefValue]);
+    }
+
+    @Test
+    public void testUpdateState_wifiNotMetered_setCorrectValue() {
+        doReturn(PRIVACY_RANDOMIZED).when(mPreferenceController).getRandomizationValue();
+
+        mPreferenceController.updateState(mDropDownPreference);
+
+        int prefValue = mPreferenceController.translateMacRandomizedValueToPrefValue(
+                PRIVACY_RANDOMIZED);
+        assertThat(mDropDownPreference.getEntry()).isEqualTo(mPerferenceStrings[prefValue]);
+    }
+
+    @Test
+    public void testController_resilientToNullConfig() {
+        mPreferenceController = spy(new WifiPrivacyPreferenceController2(mContext));
+
+        mPreferenceController.getRandomizationValue();
+        mPreferenceController.onPreferenceChange(mDropDownPreference, "1");
+    }
+
+    @Test
+    public void testUpdateState_isNotEphemeralNetwork_shouldBeSelectable() {
+        mPreferenceController.setIsEphemeral(false);
+        mPreferenceController.updateState(mDropDownPreference);
+
+        assertThat(mDropDownPreference.isSelectable()).isTrue();
+    }
+
+    @Test
+    public void testUpdateState_isEphemeralNetwork_shouldNotSelectable() {
+        mPreferenceController.setIsEphemeral(true);
+        mPreferenceController.updateState(mDropDownPreference);
+
+        assertThat(mDropDownPreference.isSelectable()).isFalse();
+    }
+
+    @Test
+    public void testUpdateState_isNotPasspointNetwork_shouldBeSelectable() {
+        mPreferenceController.setIsPasspoint(false);
+        mPreferenceController.updateState(mDropDownPreference);
+
+        assertThat(mDropDownPreference.isSelectable()).isTrue();
+    }
+
+    @Test
+    public void testUpdateState_isPasspointNetwork_shouldNotSelectable() {
+        mPreferenceController.setIsPasspoint(true);
+        mPreferenceController.updateState(mDropDownPreference);
+
+        assertThat(mDropDownPreference.isSelectable()).isFalse();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsPreferenceController2Test.java
new file mode 100644
index 0000000..6be9852
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsPreferenceController2Test.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 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.savedaccesspoints2;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.testutils.shadow.ShadowAccessPoint;
+import com.android.settings.testutils.shadow.ShadowWifiManager;
+import com.android.settingslib.wifi.AccessPoint;
+import com.android.settingslib.wifi.AccessPointPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowWifiManager.class})
+public class SavedAccessPointsPreferenceController2Test {
+
+    @Mock
+    private PreferenceScreen mPreferenceScreen;
+    @Mock
+    private PreferenceCategory mPreferenceCategory;
+
+    private Context mContext;
+    private WifiManager mWifiManager;
+    private SavedAccessPointsWifiSettings2 mSettings;
+    private SavedAccessPointsPreferenceController2 mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mWifiManager = mContext.getSystemService(WifiManager.class);
+        mSettings = spy(new SavedAccessPointsWifiSettings2());
+        mController = spy(new SavedAccessPointsPreferenceController2(mContext, "test_key"));
+        mController.setHost(mSettings);
+
+        when(mPreferenceScreen.findPreference(mController.getPreferenceKey()))
+                .thenReturn(mPreferenceCategory);
+        when(mPreferenceCategory.getContext()).thenReturn(mContext);
+    }
+
+    @Test
+    public void getAvailability_noSavedAccessPoint_shouldNotAvailable() {
+        mController.mAccessPoints = new ArrayList<>();
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
+    }
+
+    @Test
+    public void getAvailability_oneSavedAccessPoint_shouldAvailable() {
+        final AccessPoint accessPoint = new AccessPoint(mContext, new Bundle() /* savedState */);
+        mController.mAccessPoints = new ArrayList<AccessPoint>(Arrays.asList(accessPoint));
+
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    @Config(shadows = ShadowAccessPoint.class)
+    public void displayPreference_oneAccessPoint_shouldListNonSubscribedAPs() {
+        final WifiConfiguration config = new WifiConfiguration();
+        config.SSID = "SSID";
+        config.BSSID = "BSSID";
+        config.networkId = 2;
+        mWifiManager.addNetwork(config);
+
+        final ArgumentCaptor<AccessPointPreference> captor =
+                ArgumentCaptor.forClass(AccessPointPreference.class);
+        mController.displayPreference(mPreferenceScreen);
+
+        verify(mPreferenceCategory).addPreference(captor.capture());
+
+        final AccessPointPreference pref = captor.getValue();
+        assertThat(pref.getTitle()).isEqualTo(config.SSID);
+    }
+
+    @Test
+    @Config(shadows = ShadowAccessPoint.class)
+    public void displayPreference_onePasspoint_shouldNotListSubscribedAPs() {
+        mWifiManager.addOrUpdatePasspointConfiguration(
+                SubscribedAccessPointsPreferenceController2Test.createMockPasspointConfiguration());
+
+        mController.displayPreference(mPreferenceScreen);
+
+        verify(mPreferenceCategory, never()).addPreference(any(AccessPointPreference.class));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsWifiSettings2Test.java b/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsWifiSettings2Test.java
new file mode 100644
index 0000000..ab1b51c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsWifiSettings2Test.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 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.savedaccesspoints2;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class SavedAccessPointsWifiSettings2Test {
+
+    @Mock
+    private SubscribedAccessPointsPreferenceController2 mSubscribedApController;
+    @Mock
+    private SavedAccessPointsPreferenceController2 mSavedApController;
+
+    private TestFragment mSettings;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mSettings = spy(new TestFragment());
+
+        doReturn(mSubscribedApController).when(mSettings)
+                .use(SubscribedAccessPointsPreferenceController2.class);
+        doReturn(mSavedApController).when(mSettings)
+                .use(SavedAccessPointsPreferenceController2.class);
+    }
+
+    @Test
+    public void verifyConstants() {
+        assertThat(mSettings.getMetricsCategory()).isEqualTo(MetricsEvent.WIFI_SAVED_ACCESS_POINTS);
+        assertThat(mSettings.getPreferenceScreenResId())
+                .isEqualTo(R.xml.wifi_display_saved_access_points2);
+    }
+
+    public static class TestFragment extends SavedAccessPointsWifiSettings2 {
+
+        public <T extends AbstractPreferenceController> T use(Class<T> clazz) {
+            return super.use(clazz);
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SubscribedAccessPointsPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SubscribedAccessPointsPreferenceController2Test.java
new file mode 100644
index 0000000..2ce5d44
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SubscribedAccessPointsPreferenceController2Test.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 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.savedaccesspoints2;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.HomeSp;
+
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.testutils.shadow.ShadowAccessPoint;
+import com.android.settings.testutils.shadow.ShadowWifiManager;
+import com.android.settingslib.wifi.AccessPointPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowWifiManager.class})
+public class SubscribedAccessPointsPreferenceController2Test {
+
+    @Mock
+    private PreferenceScreen mPreferenceScreen;
+    @Mock
+    private PreferenceCategory mPreferenceCategory;
+
+    private Context mContext;
+    private WifiManager mWifiManager;
+    private SavedAccessPointsWifiSettings2 mSettings;
+    private SubscribedAccessPointsPreferenceController2 mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mWifiManager = mContext.getSystemService(WifiManager.class);
+        mSettings = spy(new SavedAccessPointsWifiSettings2());
+        mController = spy(new SubscribedAccessPointsPreferenceController2(mContext, "test_key"));
+        mController.setHost(mSettings);
+
+        when(mPreferenceScreen.findPreference(mController.getPreferenceKey()))
+                .thenReturn(mPreferenceCategory);
+        when(mPreferenceCategory.getContext()).thenReturn(mContext);
+    }
+
+    @Test
+    @Config(shadows = ShadowAccessPoint.class)
+    public void displayPreference_oneAccessPoint_shouldNotListNonSubscribedAPs() {
+        final WifiConfiguration config = new WifiConfiguration();
+        config.SSID = "SSID";
+        config.BSSID = "BSSID";
+        config.networkId = 2;
+        mWifiManager.addNetwork(config);
+
+        mController.displayPreference(mPreferenceScreen);
+
+        verify(mPreferenceCategory, never()).addPreference(any(AccessPointPreference.class));
+    }
+
+    @Test
+    @Config(shadows = ShadowAccessPoint.class)
+    public void displayPreference_onePasspoint_shouldListSubscribedAPs() {
+        mWifiManager.addOrUpdatePasspointConfiguration(createMockPasspointConfiguration());
+
+        mController.displayPreference(mPreferenceScreen);
+
+        final ArgumentCaptor<AccessPointPreference> captor =
+                ArgumentCaptor.forClass(AccessPointPreference.class);
+        verify(mPreferenceCategory).addPreference(captor.capture());
+
+        final AccessPointPreference pref = captor.getValue();
+        assertThat(pref.getTitle()).isEqualTo("TESTPASSPOINT");
+    }
+
+    public static PasspointConfiguration createMockPasspointConfiguration() {
+        final PasspointConfiguration config = new PasspointConfiguration();
+        final HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("FQDN");
+        homeSp.setFriendlyName("TESTPASSPOINT");
+        config.setHomeSp(homeSp);
+        return config;
+    }
+}