OSDN Git Service

Merge "Fix overlapping problem on wifi detail screen" into qt-dev
[android-x86/packages-apps-Settings.git] / src / com / android / settings / wifi / details / WifiDetailPreferenceController.java
index 3540925..5e49b2e 100644 (file)
 package com.android.settings.wifi.details;
 
 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.Fragment;
+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.NetworkBadging;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkRequest;
@@ -39,36 +44,49 @@ 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.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceCategory;
-import android.support.v7.preference.PreferenceScreen;
 import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
-import android.view.View;
-import android.widget.Button;
 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.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.R;
 import com.android.settings.Utils;
-import com.android.settings.applications.LayoutPreference;
-import com.android.settings.core.PreferenceController;
-import com.android.settings.core.instrumentation.MetricsFeatureProvider;
-import com.android.settings.vpn2.ConnectivityManagerWrapper;
+import com.android.settings.core.FeatureFlags;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.datausage.WifiDataUsageSummaryPreferenceController;
+import com.android.settings.development.featureflags.FeatureFlagPersistent;
 import com.android.settings.widget.EntityHeaderController;
-import com.android.settings.wifi.WifiDetailPreference;
+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;
 
@@ -76,24 +94,32 @@ import java.util.stream.Collectors;
  * Controller for logic pertaining to displaying Wifi information for the
  * {@link WifiNetworkDetailsFragment}.
  */
-public class WifiDetailPreferenceController extends PreferenceController implements
-        LifecycleObserver, OnPause, OnResume {
+public class WifiDetailPreferenceController extends AbstractPreferenceController
+        implements PreferenceControllerMixin, WifiDialogListener, LifecycleObserver, OnPause,
+        OnResume {
+
     private static final String TAG = "WifiDetailsPrefCtrl";
     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_LINK_SPEED = "link_speed";
+    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";
@@ -108,47 +134,82 @@ public class WifiDetailPreferenceController extends PreferenceController impleme
     @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 mTimer;
+
     private AccessPoint mAccessPoint;
-    private final ConnectivityManagerWrapper mConnectivityManagerWrapper;
     private final ConnectivityManager mConnectivityManager;
-    private final Fragment mFragment;
+    private final PreferenceFragmentCompat mFragment;
     private final Handler mHandler;
     private LinkProperties mLinkProperties;
     private Network mNetwork;
     private NetworkInfo mNetworkInfo;
     private NetworkCapabilities mNetworkCapabilities;
-    private int mRssi;
+    private int mRssiSignalLevel = -1;
     private String[] mSignalStr;
-    private final WifiConfiguration mWifiConfig;
+    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 LayoutPreference mButtonsPref;
+    private ActionButtonsPreference mButtonsPref;
     private EntityHeaderController mEntityHeaderController;
-    private Button mForgetButton;
-    private Button mSignInButton;
-    private WifiDetailPreference mSignalStrengthPref;
-    private WifiDetailPreference mLinkSpeedPref;
-    private WifiDetailPreference mFrequencyPref;
-    private WifiDetailPreference mSecurityPref;
-    private WifiDetailPreference mMacAddressPref;
-    private WifiDetailPreference mIpAddressPref;
-    private WifiDetailPreference mGatewayPref;
-    private WifiDetailPreference mSubnetPref;
-    private WifiDetailPreference mDnsPref;
+    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;
     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:
-                    updateInfo();
+                    refreshPage();
+                    break;
             }
         }
     };
@@ -162,7 +223,7 @@ public class WifiDetailPreferenceController extends PreferenceController impleme
         public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
             if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) {
                 mLinkProperties = lp;
-                updateIpLayerInfo();
+                refreshIpLayerInfo();
             }
         }
 
@@ -176,53 +237,127 @@ public class WifiDetailPreferenceController extends PreferenceController impleme
 
         @Override
         public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
-            // If the network just validated or lost Internet access, refresh network state.
-            // Don't do this on every NetworkCapabilities change because refreshNetworkState
-            // sends IPCs to the system server from the UI thread, which can cause jank.
+            // If the network just validated or lost Internet access or detected partial internet
+            // connectivity, refresh network state. Don't do this on every NetworkCapabilities
+            // change because refreshNetworkState sends IPCs to the system server from the UI
+            // thread, which can cause jank.
             if (network.equals(mNetwork) && !nc.equals(mNetworkCapabilities)) {
-                if (hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED) ||
-                        hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL)) {
-                    refreshNetworkState();
+                if (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;
-                updateIpLayerInfo();
+                refreshButtons();
+                refreshIpLayerInfo();
             }
         }
 
         @Override
         public void onLost(Network network) {
-            if (network.equals(mNetwork)) {
+            // Ephemeral network not a saved network, leave detail page once disconnected
+            if (mIsEphemeral && network.equals(mNetwork)) {
                 exitActivity();
             }
         }
     };
 
-    public WifiDetailPreferenceController(
+    @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();
+        }
+    };
+
+    public static WifiDetailPreferenceController newInstance(
             AccessPoint accessPoint,
-            ConnectivityManagerWrapper connectivityManagerWrapper,
+            ConnectivityManager connectivityManager,
             Context context,
-            Fragment fragment,
+            PreferenceFragmentCompat fragment,
             Handler handler,
             Lifecycle lifecycle,
             WifiManager wifiManager,
             MetricsFeatureProvider metricsFeatureProvider) {
+        return new WifiDetailPreferenceController(
+                accessPoint, connectivityManager, context, fragment, handler, lifecycle,
+                wifiManager, metricsFeatureProvider, new IconInjector(context));
+    }
+
+    @VisibleForTesting
+        /* package */ WifiDetailPreferenceController(
+            AccessPoint accessPoint,
+            ConnectivityManager connectivityManager,
+            Context context,
+            PreferenceFragmentCompat fragment,
+            Handler handler,
+            Lifecycle lifecycle,
+            WifiManager wifiManager,
+            MetricsFeatureProvider metricsFeatureProvider,
+            IconInjector injector) {
         super(context);
 
         mAccessPoint = accessPoint;
-        mConnectivityManager = connectivityManagerWrapper.getConnectivityManager();
-        mConnectivityManagerWrapper = connectivityManagerWrapper;
+        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);
+            }
+        };
     }
 
     @Override
@@ -242,57 +377,91 @@ public class WifiDetailPreferenceController extends PreferenceController impleme
 
         setupEntityHeader(screen);
 
-        mButtonsPref = (LayoutPreference) screen.findPreference(KEY_BUTTONS_PREF);
-        mSignInButton = mButtonsPref.findViewById(R.id.signin_button);
-        mSignInButton.setText(R.string.support_sign_in_button_text);
-        mSignInButton.setOnClickListener(view -> signIntoNetwork());
+        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());
 
-        mSignalStrengthPref =
-                (WifiDetailPreference) screen.findPreference(KEY_SIGNAL_STRENGTH_PREF);
-        mLinkSpeedPref = (WifiDetailPreference) screen.findPreference(KEY_LINK_SPEED);
-        mFrequencyPref = (WifiDetailPreference) screen.findPreference(KEY_FREQUENCY_PREF);
-        mSecurityPref = (WifiDetailPreference) screen.findPreference(KEY_SECURITY_PREF);
+        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);
 
-        mMacAddressPref = (WifiDetailPreference) screen.findPreference(KEY_MAC_ADDRESS_PREF);
-        mIpAddressPref = (WifiDetailPreference) screen.findPreference(KEY_IP_ADDRESS_PREF);
-        mGatewayPref = (WifiDetailPreference) screen.findPreference(KEY_GATEWAY_PREF);
-        mSubnetPref = (WifiDetailPreference) screen.findPreference(KEY_SUBNET_MASK_PREF);
-        mDnsPref = (WifiDetailPreference) screen.findPreference(KEY_DNS_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 = (PreferenceCategory) screen.findPreference(KEY_IPV6_CATEGORY);
+        mIpv6Category = screen.findPreference(KEY_IPV6_CATEGORY);
         mIpv6AddressPref = screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
 
-        mSecurityPref.setDetailText(mAccessPoint.getSecurityString(false /* concise */));
-        mForgetButton = mButtonsPref.findViewById(R.id.forget_button);
-        mForgetButton.setText(R.string.forget);
-        mForgetButton.setOnClickListener(view -> forgetNetwork());
+        mSecurityPref.setSummary(mAccessPoint.getSecurityString(/* concise */ false));
     }
 
     private void setupEntityHeader(PreferenceScreen screen) {
-        LayoutPreference headerPref = (LayoutPreference) screen.findPreference(KEY_HEADER);
+        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.setBackground(
-                mContext.getDrawable(R.drawable.ic_settings_widget_background));
+
         iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
 
-        mEntityHeaderController.setLabel(mAccessPoint.getSsidStr());
+        mEntityHeaderController.setLabel(mAccessPoint.getTitle());
+    }
+
+    private void refreshEntityHeader() {
+        if (usingDataUsageHeader(mContext)) {
+            mSummaryHeaderController.updateState(mDataUsageSummaryPref);
+        } else {
+            mEntityHeaderController
+                    .setSummary(
+                            mAccessPoint.getSettingsSummary(true /*convertSavedAsDisconnected*/))
+                    .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.
-        mNetwork = mWifiManager.getCurrentNetwork();
-        mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork);
-        mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork);
-        updateInfo();
+        updateNetworkInfo();
+        refreshPage();
         mContext.registerReceiver(mReceiver, mFilter);
-        mConnectivityManagerWrapper.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
+        mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback,
                 mHandler);
     }
 
@@ -307,35 +476,151 @@ public class WifiDetailPreferenceController extends PreferenceController impleme
         mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
     }
 
-    private void updateInfo() {
-        // No need to fetch LinkProperties and NetworkCapabilities, they are updated by the
-        // callbacks. mNetwork doesn't change except in onResume.
-        mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork);
-        mWifiInfo = mWifiManager.getConnectionInfo();
-        if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) {
-            exitActivity();
+    private void refreshPage() {
+        if(!updateAccessPoint()) {
             return;
         }
 
-        // Update whether the forgot button should be displayed.
-        mForgetButton.setVisibility(canForgetNetwork() ? View.VISIBLE : View.INVISIBLE);
+        Log.d(TAG, "Update UI!");
+
+        // refresh header
+        refreshEntityHeader();
 
-        refreshNetworkState();
+        // refresh Buttons
+        refreshButtons();
 
         // Update Connection Header icon and Signal Strength Preference
-        mRssi = mWifiInfo.getRssi();
         refreshRssiViews();
-
+        // Frequency Pref
+        refreshFrequency();
+        // Transmit Link Speed Pref
+        refreshTxSpeed();
+        // Receive Link Speed Pref
+        refreshRxSpeed();
+        // IP related information
+        refreshIpLayerInfo();
+        // SSID Pref
+        refreshSsid();
         // MAC Address Pref
-        mMacAddressPref.setDetailText(mWifiInfo.getMacAddress());
+        refreshMacAddress();
+    }
 
-        // Link Speed Pref
-        int linkSpeedMbps = mWifiInfo.getLinkSpeed();
-        mLinkSpeedPref.setVisible(linkSpeedMbps >= 0);
-        mLinkSpeedPref.setDetailText(mContext.getString(
-                R.string.link_speed, mWifiInfo.getLinkSpeed()));
+    @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;
+        }
 
-        // Frequency Pref
         final int frequency = mWifiInfo.getFrequency();
         String band = null;
         if (frequency >= AccessPoint.LOWER_FREQ_24GHZ
@@ -346,58 +631,119 @@ public class WifiDetailPreferenceController extends PreferenceController impleme
             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.setDetailText(band);
+        mFrequencyPref.setSummary(band);
+        mFrequencyPref.setVisible(true);
+    }
 
-        updateIpLayerInfo();
+    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 exitActivity() {
-        if (DEBUG) {
-            Log.d(TAG, "Exiting the WifiNetworkDetailsPage");
+    private void refreshRxSpeed() {
+        if (mWifiInfo == null) {
+            mRxLinkSpeedPref.setVisible(false);
+            return;
         }
-        mFragment.getActivity().finish();
+
+        int rxLinkSpeedMbps = mWifiInfo.getRxLinkSpeedMbps();
+        mRxLinkSpeedPref.setVisible(rxLinkSpeedMbps >= 0);
+        mRxLinkSpeedPref.setSummary(mContext.getString(
+                R.string.rx_link_speed, mWifiInfo.getRxLinkSpeedMbps()));
     }
 
-    private void refreshNetworkState() {
-        mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo);
-        mEntityHeaderController.setSummary(mAccessPoint.getSettingsSummary())
-                .done(mFragment.getActivity(), true /* rebind */);
+    private void refreshSsid() {
+        if (mAccessPoint.isPasspoint() || mAccessPoint.isOsuProvider()) {
+            mSsidPref.setVisible(true);
+            mSsidPref.setSummary(mAccessPoint.getSsidStr());
+        } else {
+            mSsidPref.setVisible(false);
+        }
     }
 
-    private void refreshRssiViews() {
-        int iconSignalLevel = WifiManager.calculateSignalLevel(
-                mRssi, WifiManager.RSSI_LEVELS);
-        Drawable wifiIcon = NetworkBadging.getWifiIcon(
-                iconSignalLevel, NetworkBadging.BADGING_NONE, mContext.getTheme()).mutate();
+    private void refreshMacAddress() {
+        String macAddress = getMacAddress();
+        if (macAddress == null) {
+            mMacAddressPref.setVisible(false);
+            return;
+        }
 
-        wifiIcon.setTint(Utils.getColorAccent(mContext));
-        mEntityHeaderController.setIcon(wifiIcon).done(mFragment.getActivity(), true /* rebind */);
+        mMacAddressPref.setVisible(true);
+        mMacAddressPref.setSummary(macAddress);
+    }
 
-        Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate();
-        wifiIconDark.setTint(mContext.getResources().getColor(
-                R.color.wifi_details_icon_color, mContext.getTheme()));
-        mSignalStrengthPref.setIcon(wifiIconDark);
+    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();
+        }
 
-        int summarySignalLevel = mAccessPoint.getLevel();
-        mSignalStrengthPref.setDetailText(mSignalStr[summarySignalLevel]);
+        // 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(WifiDetailPreference pref, String detailText) {
+    private void updatePreference(Preference pref, String detailText) {
         if (!TextUtils.isEmpty(detailText)) {
-            pref.setDetailText(detailText);
+            pref.setSummary(detailText);
             pref.setVisible(true);
         } else {
             pref.setVisible(false);
         }
     }
 
-    private void updateIpLayerInfo() {
-        mSignInButton.setVisibility(canSignIntoNetwork() ? View.VISIBLE : View.INVISIBLE);
-        mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE
-                || mSignInButton.getVisibility() == View.VISIBLE);
+    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();
+        boolean canShareNetwork = canShareNetwork();
 
-        if (mNetwork == null || mLinkProperties == null) {
+        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 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);
@@ -429,11 +775,10 @@ public class WifiDetailPreferenceController extends PreferenceController impleme
             }
         }
 
-        // Find IPv4 DNS addresses.
+        // Find all (IPv4 and IPv6) DNS addresses.
         String dnsServers = mLinkProperties.getDnsServers().stream()
-                .filter(Inet4Address.class::isInstance)
                 .map(InetAddress::getHostAddress)
-                .collect(Collectors.joining(","));
+                .collect(Collectors.joining("\n"));
 
         // Update UI.
         updatePreference(mIpAddressPref, ipv4Address);
@@ -442,7 +787,8 @@ public class WifiDetailPreferenceController extends PreferenceController impleme
         updatePreference(mDnsPref, dnsServers);
 
         if (ipv6Addresses.length() > 0) {
-            mIpv6AddressPref.setSummary(ipv6Addresses.toString());
+            mIpv6AddressPref.setSummary(
+                    BidiFormatter.getInstance().unicodeWrap(ipv6Addresses.toString()));
             mIpv6Category.setVisible(true);
         } else {
             mIpv6Category.setVisible(false);
@@ -463,15 +809,30 @@ public class WifiDetailPreferenceController extends PreferenceController impleme
      * Returns whether the network represented by this preference can be forgotten.
      */
     private boolean canForgetNetwork() {
-        return mWifiInfo != null && mWifiInfo.isEphemeral() || mWifiConfig != null;
+        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 mNetworkCapabilities != null && mNetworkCapabilities.hasCapability(
-                NET_CAPABILITY_CAPTIVE_PORTAL);
+        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);
     }
 
     /**
@@ -480,24 +841,300 @@ public class WifiDetailPreferenceController extends PreferenceController impleme
     private void forgetNetwork() {
         if (mWifiInfo != null && mWifiInfo.isEphemeral()) {
             mWifiManager.disableEphemeralNetwork(mWifiInfo.getSSID());
-        } else if (mWifiConfig != null) {
-            if (mWifiConfig.isPasspoint()) {
-                mWifiManager.removePasspointConfiguration(mWifiConfig.FQDN);
-            } else {
-                mWifiManager.forget(mWifiConfig.networkId, null /* action listener */);
+        } else if (mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig()) {
+            // Post a dialog to confirm if user really want to forget the passpoint network.
+            if (FeatureFlagPersistent.isEnabled(mContext, FeatureFlags.NETWORK_INTERNET_V2)) {
+                showConfirmForgetDialog();
+                return;
             }
+
+            try {
+                mWifiManager.removePasspointConfiguration(mAccessPoint.getPasspointFqdn());
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Failed to remove Passpoint configuration for "
+                        + mAccessPoint.getPasspointFqdn());
+            }
+        } else if (mWifiConfig != null) {
+            mWifiManager.forget(mWifiConfig.networkId, null /* action listener */);
         }
+
         mMetricsFeatureProvider.action(
-                mFragment.getActivity(), MetricsProto.MetricsEvent.ACTION_WIFI_FORGET);
+                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(), MetricsProto.MetricsEvent.ACTION_WIFI_SIGNIN);
-        mConnectivityManagerWrapper.startCaptivePortalApp(mNetwork);
+                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;
+
+        public 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:
+                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 (mTimer != null) {
+            stopTimer();
+        }
+
+        mTimer = 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);
+                    }
+                }
+            }
+        };
+        mTimer.start();
+    }
+
+    private void stopTimer() {
+        if (mTimer == null) return;
+
+        mTimer.cancel();
+        mTimer = null;
     }
 }