Show accurate connection status for ephemeral networks.

Configurations for these networks are not returned in
getConfiguredNetworks() and likely shouldn't be as clients of this API
would not expect them. (Note also that the ephemeral bit is marked
@hide). But the framework may connect to them regardless.

In these cases, as long as the connection status is something other
than the coarse-level DISCONNECTED, we show the status to be an
accurate representation of Wi-Fi state. (To make this possible, we
pass around the full NetworkInfo instead of just the DetailedState,
allowing us to get the coarse state where needed).

When long pressing on a non-DISCONNECTED ephemeral network, we offer
the ability to save the configuration. (Note that this flow is
currently broken and being tracked by another bug, but the behavior is
consistent with what happens when you simply click on the SSID).

Bug: 18205278
Change-Id: I30592c89546068c796f458a86bb26eb3b28c64df
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 25bacb3..fbf37ab 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1505,6 +1505,8 @@
     <string name="wifi_menu_advanced">Advanced</string>
     <!-- Menu option to connect to a Wi-Fi network -->
     <string name="wifi_menu_connect">Connect to network</string>
+    <!-- Menu option to remember a temporary Wi-Fi network -->
+    <string name="wifi_menu_remember">Remember network</string>
     <!-- Menu option to delete a Wi-Fi network -->
     <string name="wifi_menu_forget">Forget network</string>
     <!-- Menu option to modify a Wi-Fi network configuration -->
diff --git a/src/com/android/settings/wifi/AccessPoint.java b/src/com/android/settings/wifi/AccessPoint.java
index ffd4bf3..6b6175e 100644
--- a/src/com/android/settings/wifi/AccessPoint.java
+++ b/src/com/android/settings/wifi/AccessPoint.java
@@ -21,7 +21,9 @@
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.StateListDrawable;
+import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkInfo.State;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
@@ -70,7 +72,7 @@
     public LruCache<String, ScanResult> mScanResultCache;
 
 
-    private static final String KEY_DETAILEDSTATE = "key_detailedstate";
+    private static final String KEY_NETWORKINFO = "key_networkinfo";
     private static final String KEY_WIFIINFO = "key_wifiinfo";
     private static final String KEY_SCANRESULT = "key_scanresult";
     private static final String KEY_CONFIG = "key_config";
@@ -113,7 +115,8 @@
     private long mSeen = 0;
 
     private WifiInfo mInfo;
-    private DetailedState mState;
+    private NetworkInfo mNetworkInfo;
+    private TextView mSummaryView;
 
     private static final int VISIBILITY_MAX_AGE_IN_MILLI = 1000000;
     private static final int VISIBILITY_OUTDATED_AGE_IN_MILLI = 20000;
@@ -211,18 +214,18 @@
             loadResult(mScanResult);
         }
         mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO);
-        if (savedState.containsKey(KEY_DETAILEDSTATE)) {
-            mState = DetailedState.valueOf(savedState.getString(KEY_DETAILEDSTATE));
+        if (savedState.containsKey(KEY_NETWORKINFO)) {
+            mNetworkInfo = savedState.getParcelable(KEY_NETWORKINFO);
         }
-        update(mInfo, mState);
+        update(mInfo, mNetworkInfo);
     }
 
     public void saveWifiState(Bundle savedState) {
         savedState.putParcelable(KEY_CONFIG, mConfig);
         savedState.putParcelable(KEY_SCANRESULT, mScanResult);
         savedState.putParcelable(KEY_WIFIINFO, mInfo);
-        if (mState != null) {
-            savedState.putString(KEY_DETAILEDSTATE, mState.toString());
+        if (mNetworkInfo != null) {
+            savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo);
         }
     }
 
@@ -253,9 +256,8 @@
         super.onBindView(view);
         updateIcon(getLevel(), getContext());
 
-        final TextView summaryView = (TextView) view.findViewById(
-                com.android.internal.R.id.summary);
-        summaryView.setVisibility(showSummary ? View.VISIBLE : View.GONE);
+        mSummaryView = (TextView) view.findViewById(com.android.internal.R.id.summary);
+        mSummaryView.setVisibility(showSummary ? View.VISIBLE : View.GONE);
 
         notifyChanged();
     }
@@ -293,8 +295,8 @@
         }
         AccessPoint other = (AccessPoint) preference;
         // Active one goes first.
-        if (mInfo != null && other.mInfo == null) return -1;
-        if (mInfo == null && other.mInfo != null) return 1;
+        if (isActive() && !other.isActive()) return -1;
+        if (!isActive() && other.isActive()) return 1;
 
         // Reachable one goes before unreachable one.
         if (mRssi != Integer.MAX_VALUE && other.mRssi == Integer.MAX_VALUE) return -1;
@@ -361,19 +363,30 @@
         return false;
     }
 
-    void update(WifiInfo info, DetailedState state) {
+    /** Return whether the given {@link WifiInfo} is for this access point. */
+    private boolean isInfoForThisAccessPoint(WifiInfo info) {
+        if (networkId != WifiConfiguration.INVALID_NETWORK_ID) {
+            return networkId == info.getNetworkId();
+        } else {
+            // Might be an ephemeral connection with no WifiConfiguration. Try matching on SSID.
+            // (Note that we only do this if the WifiConfiguration explicitly equals INVALID).
+            // TODO: Handle hex string SSIDs.
+            return ssid.equals(removeDoubleQuotes(info.getSSID()));
+        }
+    }
+
+    void update(WifiInfo info, NetworkInfo networkInfo) {
         boolean reorder = false;
-        if (info != null && networkId != WifiConfiguration.INVALID_NETWORK_ID
-                && networkId == info.getNetworkId()) {
+        if (info != null && isInfoForThisAccessPoint(info)) {
             reorder = (mInfo == null);
             mRssi = info.getRssi();
             mInfo = info;
-            mState = state;
+            mNetworkInfo = networkInfo;
             refresh();
         } else if (mInfo != null) {
             reorder = true;
             mInfo = null;
-            mState = null;
+            mNetworkInfo = null;
             refresh();
         }
         if (reorder) {
@@ -396,8 +409,12 @@
         return mInfo;
     }
 
+    NetworkInfo getNetworkInfo() {
+        return mNetworkInfo;
+    }
+
     DetailedState getState() {
-        return mState;
+        return mNetworkInfo != null ? mNetworkInfo.getDetailedState() : null;
     }
 
     static String removeDoubleQuotes(String string) {
@@ -418,8 +435,11 @@
      *
      * @param showSummary true will show the summary, false will hide the summary
      */
-    public void setShowSummary(boolean showSummary){
+    public void setShowSummary(boolean showSummary) {
         this.showSummary = showSummary;
+        if (mSummaryView != null) {
+            mSummaryView.setVisibility(showSummary ? View.VISIBLE : View.GONE);
+        } // otherwise, will be handled in onBindView.
     }
 
     /**
@@ -571,6 +591,17 @@
     }
 
     /**
+     * Return whether this is the active connection.
+     * For ephemeral connections (networkId is invalid), this returns false if the network is
+     * disconnected.
+     */
+    private boolean isActive() {
+        return mNetworkInfo != null &&
+                (networkId != WifiConfiguration.INVALID_NETWORK_ID ||
+                 mNetworkInfo.getState() != State.DISCONNECTED);
+    }
+
+    /**
      * Updates the title and summary; may indirectly call notifyChanged().
      */
     private void refresh() {
@@ -585,8 +616,8 @@
         // Update to new summary
         StringBuilder summary = new StringBuilder();
 
-        if (mState != null) { // This is the active connection
-            summary.append(Summary.get(context, mState));
+        if (isActive()) {
+            summary.append(Summary.get(context, getState()));
         } else if (mConfig != null && mConfig.noInternetAccess) {
             summary.append(context.getString(R.string.wifi_no_internet));
         } else if (mConfig != null && ((mConfig.status == WifiConfiguration.Status.DISABLED &&
@@ -628,7 +659,7 @@
         if (WifiSettings.mVerboseLogging > 0) {
             //add RSSI/band information for this config, what was seen up to 6 seconds ago
             //verbose WiFi Logging is only turned on thru developers settings
-            if (mInfo != null && mState != null) { // This is the active connection
+            if (mInfo != null && mNetworkInfo != null) { // This is the active connection
                 summary.append(" f=" + Integer.toString(mInfo.getFrequency()));
             }
             summary.append(" " + getVisibilityStatus());
@@ -660,8 +691,9 @@
 
         if (summary.length() > 0) {
             setSummary(summary.toString());
+            setShowSummary(true);
         } else {
-            showSummary = false;
+            setShowSummary(false);
         }
     }
 
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index 1ef9cce..76ebcca 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -34,6 +34,7 @@
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkInfo.State;
 import android.net.NetworkScoreManager;
 import android.net.NetworkScorerAppManager;
 import android.net.NetworkScorerAppManager.NetworkScorerAppData;
@@ -132,7 +133,7 @@
     // An access point being editted is stored here.
     private AccessPoint mSelectedAccessPoint;
 
-    private DetailedState mLastState;
+    private NetworkInfo mLastNetworkInfo;
     private WifiInfo mLastInfo;
 
     private final AtomicBoolean mConnected = new AtomicBoolean(false);
@@ -529,9 +530,22 @@
             if (preference instanceof AccessPoint) {
                 mSelectedAccessPoint = (AccessPoint) preference;
                 menu.setHeaderTitle(mSelectedAccessPoint.ssid);
-                if (mSelectedAccessPoint.getLevel() != -1
-                        && mSelectedAccessPoint.getState() == null) {
-                    menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
+                if (mSelectedAccessPoint.getLevel() != -1) {
+                    int connectStringRes = 0;
+                    if (mSelectedAccessPoint.getState() == null) {
+                        connectStringRes = R.string.wifi_menu_connect;
+                    } else if (mSelectedAccessPoint.networkId == INVALID_NETWORK_ID &&
+                            mSelectedAccessPoint.getNetworkInfo().getState()
+                                != State.DISCONNECTED) {
+                        // State is non-null (and not disconnected) but this network has no
+                        // configuration, which means it is ephemeral. Allow the user to save the
+                        // configuration permanently (but still issue this as a CONNECT command).
+                        connectStringRes = R.string.wifi_menu_remember;
+                    }
+
+                    if (connectStringRes != 0) {
+                        menu.add(Menu.NONE, MENU_ID_CONNECT, 0, connectStringRes);
+                    }
                 }
                 if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
                     if (ActivityManager.getCurrentUser() == UserHandle.USER_OWNER) {
@@ -671,7 +685,8 @@
             case WifiManager.WIFI_STATE_ENABLED:
                 // AccessPoints are automatically sorted with TreeSet.
                 final Collection<AccessPoint> accessPoints =
-                        constructAccessPoints(getActivity(), mWifiManager, mLastInfo, mLastState);
+                        constructAccessPoints(getActivity(), mWifiManager, mLastInfo,
+                                mLastNetworkInfo);
                 getPreferenceScreen().removeAll();
                 if (accessPoints.size() == 0) {
                     addMessagePreference(R.string.wifi_empty_list_wifi_on);
@@ -833,7 +848,7 @@
 
     /** Returns sorted list of access points */
     private static List<AccessPoint> constructAccessPoints(Context context,
-            WifiManager wifiManager, WifiInfo lastInfo, DetailedState lastState) {
+            WifiManager wifiManager, WifiInfo lastInfo, NetworkInfo lastNetworkInfo) {
         ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
         /** Lookup table to more quickly update AccessPoints by only considering objects with the
          * correct SSID.  Maps SSID -> List of AccessPoints with the given SSID.  */
@@ -853,8 +868,8 @@
                     continue;
                 }
                 AccessPoint accessPoint = new AccessPoint(context, config);
-                if (lastInfo != null && lastState != null) {
-                    accessPoint.update(lastInfo, lastState);
+                if (lastInfo != null && lastNetworkInfo != null) {
+                    accessPoint.update(lastInfo, lastNetworkInfo);
                 }
                 accessPoints.add(accessPoint);
                 apMap.put(accessPoint.ssid, accessPoint);
@@ -877,6 +892,9 @@
                 }
                 if (!found) {
                     AccessPoint accessPoint = new AccessPoint(context, result);
+                    if (lastInfo != null && lastNetworkInfo != null) {
+                        accessPoint.update(lastInfo, lastNetworkInfo);
+                    }
                     accessPoints.add(accessPoint);
                     apMap.put(accessPoint.ssid, accessPoint);
                 }
@@ -903,28 +921,29 @@
             mConnected.set(info.isConnected());
             changeNextButtonState(info.isConnected());
             updateAccessPoints();
-            updateConnectionState(info.getDetailedState());
+            updateNetworkInfo(info);
         } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
-            updateConnectionState(null);
+            updateNetworkInfo(null);
         }
     }
 
-    private void updateConnectionState(DetailedState state) {
+    private void updateNetworkInfo(NetworkInfo networkInfo) {
         /* sticky broadcasts can call this when wifi is disabled */
         if (!mWifiManager.isWifiEnabled()) {
             mScanner.pause();
             return;
         }
 
-        if (state == DetailedState.OBTAINING_IPADDR) {
+        if (networkInfo != null &&
+                networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) {
             mScanner.pause();
         } else {
             mScanner.resume();
         }
 
         mLastInfo = mWifiManager.getConnectionInfo();
-        if (state != null) {
-            mLastState = state;
+        if (networkInfo != null) {
+            mLastNetworkInfo = networkInfo;
         }
 
         for (int i = getPreferenceScreen().getPreferenceCount() - 1; i >= 0; --i) {
@@ -932,7 +951,7 @@
             Preference preference = getPreferenceScreen().getPreference(i);
             if (preference instanceof AccessPoint) {
                 final AccessPoint accessPoint = (AccessPoint) preference;
-                accessPoint.update(mLastInfo, mLastState);
+                accessPoint.update(mLastInfo, mLastNetworkInfo);
             }
         }
     }
@@ -958,7 +977,7 @@
         }
 
         mLastInfo = null;
-        mLastState = null;
+        mLastNetworkInfo = null;
         mScanner.pause();
     }