WifiInfo: Add info elements for connected bssid

Also, remove isAtLeastS() from WifiInfoTest (we're no longer guarding unit
tests using SDK version).

Bug: 175853962
Test: atest android.net.wifi
Change-Id: I384c9321e1f74bf4ad4431b6dbe8f8fc2392dff9
diff --git a/framework/api/current.txt b/framework/api/current.txt
index a8bf4ce..421b9fd 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -51,11 +51,14 @@
     field @Deprecated public CharSequence venueName;
   }
 
-  public static class ScanResult.InformationElement {
+  public static class ScanResult.InformationElement implements android.os.Parcelable {
     ctor public ScanResult.InformationElement(@NonNull android.net.wifi.ScanResult.InformationElement);
+    method public int describeContents();
     method @NonNull public java.nio.ByteBuffer getBytes();
     method public int getId();
     method public int getIdExt();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.ScanResult.InformationElement> CREATOR;
   }
 
   public final class SoftApConfiguration implements android.os.Parcelable {
@@ -276,6 +279,7 @@
     method public static android.net.NetworkInfo.DetailedState getDetailedStateOf(android.net.wifi.SupplicantState);
     method public int getFrequency();
     method public boolean getHiddenSSID();
+    method @Nullable public java.util.List<android.net.wifi.ScanResult.InformationElement> getInformationElements();
     method @Deprecated public int getIpAddress();
     method public int getLinkSpeed();
     method public String getMacAddress();
diff --git a/framework/java/android/net/wifi/ScanResult.java b/framework/java/android/net/wifi/ScanResult.java
index 783be1f..b483c5f 100644
--- a/framework/java/android/net/wifi/ScanResult.java
+++ b/framework/java/android/net/wifi/ScanResult.java
@@ -26,6 +26,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -789,7 +791,7 @@
     /**
      * information elements from beacon.
      */
-    public static class InformationElement {
+    public static class InformationElement implements Parcelable {
         /** @hide */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public static final int EID_SSID = 0;
@@ -885,6 +887,57 @@
         public ByteBuffer getBytes() {
             return ByteBuffer.wrap(bytes).asReadOnlyBuffer();
         }
+
+        /** Implement the Parcelable interface {@hide} */
+        public int describeContents() {
+            return 0;
+        }
+
+        /** Implement the Parcelable interface {@hide} */
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(id);
+            dest.writeInt(idExt);
+            dest.writeByteArray(bytes);
+        }
+
+        /** Implement the Parcelable interface */
+        public static final @NonNull Creator<InformationElement> CREATOR =
+                new Creator<InformationElement>() {
+                    public InformationElement createFromParcel(Parcel in) {
+                        InformationElement informationElement = new InformationElement();
+                        informationElement.id = in.readInt();
+                        informationElement.idExt = in.readInt();
+                        informationElement.bytes = in.createByteArray();
+                        return informationElement;
+                    }
+
+                    public InformationElement[] newArray(int size) {
+                        return new InformationElement[size];
+                    }
+                };
+
+        @Override
+        public boolean equals(Object that) {
+            if (this == that) return true;
+
+            // Potential API behavior change, so don't change behavior on older devices.
+            if (!SdkLevel.isAtLeastS()) return false;
+
+            if (!(that instanceof InformationElement)) return false;
+
+            InformationElement thatIE = (InformationElement) that;
+            return id == thatIE.id
+                    && idExt == thatIE.idExt
+                    && Arrays.equals(bytes, thatIE.bytes);
+        }
+
+        @Override
+        public int hashCode() {
+            // Potential API behavior change, so don't change behavior on older devices.
+            if (!SdkLevel.isAtLeastS()) return System.identityHashCode(this);
+
+            return Objects.hash(id, idExt, Arrays.hashCode(bytes));
+        }
     }
 
     /**
@@ -1094,18 +1147,7 @@
         dest.writeString((venueName != null) ? venueName.toString() : "");
         dest.writeString((operatorFriendlyName != null) ? operatorFriendlyName.toString() : "");
         dest.writeLong(this.flags);
-
-        if (informationElements != null) {
-            dest.writeInt(informationElements.length);
-            for (int i = 0; i < informationElements.length; i++) {
-                dest.writeInt(informationElements[i].id);
-                dest.writeInt(informationElements[i].idExt);
-                dest.writeInt(informationElements[i].bytes.length);
-                dest.writeByteArray(informationElements[i].bytes);
-            }
-        } else {
-            dest.writeInt(0);
-        }
+        dest.writeTypedArray(informationElements, flags);
 
         if (anqpLines != null) {
             dest.writeInt(anqpLines.size());
@@ -1174,20 +1216,9 @@
                 sr.venueName = in.readString();
                 sr.operatorFriendlyName = in.readString();
                 sr.flags = in.readLong();
-                int n = in.readInt();
-                if (n != 0) {
-                    sr.informationElements = new InformationElement[n];
-                    for (int i = 0; i < n; i++) {
-                        sr.informationElements[i] = new InformationElement();
-                        sr.informationElements[i].id = in.readInt();
-                        sr.informationElements[i].idExt = in.readInt();
-                        int len = in.readInt();
-                        sr.informationElements[i].bytes = new byte[len];
-                        in.readByteArray(sr.informationElements[i].bytes);
-                    }
-                }
+                sr.informationElements = in.createTypedArray(InformationElement.CREATOR);
 
-                n = in.readInt();
+                int n = in.readInt();
                 if (n != 0) {
                     sr.anqpLines = new ArrayList<String>();
                     for (int i = 0; i < n; i++) {
diff --git a/framework/java/android/net/wifi/WifiInfo.java b/framework/java/android/net/wifi/WifiInfo.java
index dca2fbf..64d6d6c 100644
--- a/framework/java/android/net/wifi/WifiInfo.java
+++ b/framework/java/android/net/wifi/WifiInfo.java
@@ -42,7 +42,9 @@
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
 import java.util.EnumMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
 
@@ -57,6 +59,7 @@
  * {@link #getNetworkId()} will return {@code -1}.
  * {@link #getPasspointFqdn()} will return null.
  * {@link #getPasspointProviderFriendlyName()} will return null.
+ * {@link #getInformationElements()} will return null.
  */
 public class WifiInfo implements TransportInfo, Parcelable {
     private static final String TAG = "WifiInfo";
@@ -340,6 +343,12 @@
      */
     private String mPasspointUniqueId;
 
+    /**
+     * information elements found in the beacon of the connected bssid.
+     */
+    @Nullable
+    private List<ScanResult.InformationElement> mInformationElements;
+
     /** @hide */
     @UnsupportedAppUsage
     public WifiInfo() {
@@ -385,6 +394,7 @@
         setProviderFriendlyName(null);
         setPasspointUniqueId(null);
         setSubscriptionId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        setInformationElements(null);
         txBad = 0;
         txSuccess = 0;
         rxSuccess = 0;
@@ -447,6 +457,9 @@
             mMaxSupportedTxLinkSpeed = source.mMaxSupportedTxLinkSpeed;
             mMaxSupportedRxLinkSpeed = source.mMaxSupportedRxLinkSpeed;
             mPasspointUniqueId = source.mPasspointUniqueId;
+            if (source.mInformationElements != null) {
+                mInformationElements = new ArrayList<>(source.mInformationElements);
+            }
         }
     }
 
@@ -1147,6 +1160,9 @@
         dest.writeInt(mMaxSupportedRxLinkSpeed);
         dest.writeString(mParcelLocationSenstiveFields ? mPasspointUniqueId : null);
         dest.writeInt(mSubscriptionId);
+        if (SdkLevel.isAtLeastS()) {
+            dest.writeTypedList(mParcelLocationSenstiveFields ? mInformationElements : null);
+        }
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -1196,6 +1212,10 @@
                 info.mMaxSupportedRxLinkSpeed = in.readInt();
                 info.mPasspointUniqueId = in.readString();
                 info.mSubscriptionId = in.readInt();
+                if (SdkLevel.isAtLeastS()) {
+                    info.mInformationElements = in.createTypedArrayList(
+                            ScanResult.InformationElement.CREATOR);
+                }
                 return info;
             }
 
@@ -1224,6 +1244,36 @@
         return mPasspointUniqueId;
     }
 
+    /**
+     * Set the information elements found in the becaon of the connected bssid.
+     * @hide
+     */
+    public void setInformationElements(@Nullable List<ScanResult.InformationElement> infoElements) {
+        if (infoElements == null) {
+            mInformationElements = null;
+            return;
+        }
+        mInformationElements = new ArrayList<>(infoElements);
+    }
+
+    /**
+     * Get all information elements found in the beacon of the connected bssid.
+     * <p>
+     * The information elements will be {@code null} if there is no network currently connected or
+     * if the caller has insufficient permissions to access the info elements.
+     * </p>
+     *
+     * @return List of information elements {@link ScanResult.InformationElement} or null.
+     */
+    @Nullable
+    public List<ScanResult.InformationElement> getInformationElements() {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        if (mInformationElements == null) return null;
+        return new ArrayList<>(mInformationElements);
+    }
+
     @Override
     public boolean equals(Object that) {
         if (this == that) return true;
@@ -1271,7 +1321,8 @@
                 && Objects.equals(mWifiStandard, thatWifiInfo.mWifiStandard)
                 && Objects.equals(mMaxSupportedTxLinkSpeed, thatWifiInfo.mMaxSupportedTxLinkSpeed)
                 && Objects.equals(mMaxSupportedRxLinkSpeed, thatWifiInfo.mMaxSupportedRxLinkSpeed)
-                && Objects.equals(mPasspointUniqueId, thatWifiInfo.mPasspointUniqueId);
+                && Objects.equals(mPasspointUniqueId, thatWifiInfo.mPasspointUniqueId)
+                && Objects.equals(mInformationElements, thatWifiInfo.mInformationElements);
     }
 
     @Override
@@ -1313,7 +1364,8 @@
                 mWifiStandard,
                 mMaxSupportedTxLinkSpeed,
                 mMaxSupportedRxLinkSpeed,
-                mPasspointUniqueId);
+                mPasspointUniqueId,
+                mInformationElements);
     }
 
     /**
diff --git a/framework/tests/src/android/net/wifi/WifiInfoTest.java b/framework/tests/src/android/net/wifi/WifiInfoTest.java
index 16e6e2e..b107bd3 100644
--- a/framework/tests/src/android/net/wifi/WifiInfoTest.java
+++ b/framework/tests/src/android/net/wifi/WifiInfoTest.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -23,18 +24,17 @@
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
 
 import android.os.Parcel;
 import android.telephony.SubscriptionManager;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.modules.utils.build.SdkLevel;
-
 import org.junit.Test;
 
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Unit tests for {@link android.net.wifi.WifiInfo}.
@@ -63,8 +63,6 @@
      */
     @Test
     public void testWifiInfoParcelWriteReadWithLocationSensitiveInfo() throws Exception {
-        assumeTrue(SdkLevel.isAtLeastS());
-
         WifiInfo writeWifiInfo = new WifiInfo();
         writeWifiInfo.txSuccess = TEST_TX_SUCCESS;
         writeWifiInfo.txRetries = TEST_TX_RETRIES;
@@ -85,6 +83,8 @@
         writeWifiInfo.setMaxSupportedTxLinkSpeedMbps(TEST_MAX_SUPPORTED_TX_LINK_SPEED_MBPS);
         writeWifiInfo.setMaxSupportedRxLinkSpeedMbps(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS);
         writeWifiInfo.setSubscriptionId(TEST_SUB_ID);
+        List<ScanResult.InformationElement> informationElements = generateIes();
+        writeWifiInfo.setInformationElements(informationElements);
 
         // Make a copy which allows parcelling of location sensitive data.
         WifiInfo writeWifiInfoWithLocationSensitiveInfo = writeWifiInfo.makeCopy(true);
@@ -118,6 +118,19 @@
         assertEquals(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS,
                 readWifiInfo.getMaxSupportedRxLinkSpeedMbps());
         assertEquals(TEST_SUB_ID, readWifiInfo.getSubscriptionId());
+        assertEquals(2, readWifiInfo.getInformationElements().size());
+        assertEquals(informationElements.get(0).id,
+                readWifiInfo.getInformationElements().get(0).id);
+        assertEquals(informationElements.get(0).idExt,
+                readWifiInfo.getInformationElements().get(0).idExt);
+        assertArrayEquals(informationElements.get(0).bytes,
+                readWifiInfo.getInformationElements().get(0).bytes);
+        assertEquals(informationElements.get(1).id,
+                readWifiInfo.getInformationElements().get(1).id);
+        assertEquals(informationElements.get(1).idExt,
+                readWifiInfo.getInformationElements().get(1).idExt);
+        assertArrayEquals(informationElements.get(1).bytes,
+                readWifiInfo.getInformationElements().get(1).bytes);
     }
 
     /**
@@ -125,8 +138,6 @@
      */
     @Test
     public void testWifiInfoParcelWriteReadWithoutLocationSensitiveInfo() throws Exception {
-        assumeTrue(SdkLevel.isAtLeastS());
-
         WifiInfo writeWifiInfo = new WifiInfo();
         writeWifiInfo.txSuccess = TEST_TX_SUCCESS;
         writeWifiInfo.txRetries = TEST_TX_RETRIES;
@@ -147,6 +158,7 @@
         writeWifiInfo.setMaxSupportedTxLinkSpeedMbps(TEST_MAX_SUPPORTED_TX_LINK_SPEED_MBPS);
         writeWifiInfo.setMaxSupportedRxLinkSpeedMbps(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS);
         writeWifiInfo.setSubscriptionId(TEST_SUB_ID);
+        writeWifiInfo.setInformationElements(generateIes());
 
         // Make a copy which allows parcelling of location sensitive data.
         WifiInfo writeWifiInfoWithoutLocationSensitiveInfo = writeWifiInfo.makeCopy(false);
@@ -180,6 +192,45 @@
         assertEquals(TEST_MAX_SUPPORTED_RX_LINK_SPEED_MBPS,
                 readWifiInfo.getMaxSupportedRxLinkSpeedMbps());
         assertEquals(TEST_SUB_ID, readWifiInfo.getSubscriptionId());
+        assertNull(readWifiInfo.getInformationElements());
+    }
+
+    /**
+     *  Verify parcel write/read with WifiInfo.
+     */
+    @Test
+    public void testWifiInfoParcelWriteReadWithNullInfoElements() throws Exception {
+        WifiInfo writeWifiInfo = new WifiInfo();
+        writeWifiInfo.setInformationElements(null);
+
+        // Make a copy which allows parcelling of location sensitive data.
+        WifiInfo writeWifiInfoWithoutLocationSensitiveInfo = writeWifiInfo.makeCopy(true);
+
+        Parcel parcel = Parcel.obtain();
+        writeWifiInfoWithoutLocationSensitiveInfo.writeToParcel(parcel, 0);
+        // Rewind the pointer to the head of the parcel.
+        parcel.setDataPosition(0);
+        WifiInfo readWifiInfo = WifiInfo.CREATOR.createFromParcel(parcel);
+        assertNull(readWifiInfo.getInformationElements());
+    }
+
+    /**
+     *  Verify parcel write/read with WifiInfo.
+     */
+    @Test
+    public void testWifiInfoParcelWriteReadWithEmptyInfoElements() throws Exception {
+        WifiInfo writeWifiInfo = new WifiInfo();
+        writeWifiInfo.setInformationElements(new ArrayList<>());
+
+        // Make a copy which allows parcelling of location sensitive data.
+        WifiInfo writeWifiInfoWithoutLocationSensitiveInfo = writeWifiInfo.makeCopy(true);
+
+        Parcel parcel = Parcel.obtain();
+        writeWifiInfoWithoutLocationSensitiveInfo.writeToParcel(parcel, 0);
+        // Rewind the pointer to the head of the parcel.
+        parcel.setDataPosition(0);
+        WifiInfo readWifiInfo = WifiInfo.CREATOR.createFromParcel(parcel);
+        assertTrue(readWifiInfo.getInformationElements().isEmpty());
     }
 
     @Test
@@ -295,33 +346,37 @@
 
         WifiInfo info1 = builder.build();
         WifiInfo info2 = builder.build();
-        if (SdkLevel.isAtLeastS()) {
-            assertEquals(info1, info2);
-        } else {
-            assertNotEquals(info1, info2);
-        }
+        assertEquals(info1, info2);
 
         info1.setSubscriptionId(TEST_SUB_ID);
-        // Same behavior pre-S & post-S.
         assertNotEquals(info1, info2);
 
         info2.setSubscriptionId(TEST_SUB_ID);
-        if (SdkLevel.isAtLeastS()) {
-            assertEquals(info1, info2);
-        } else {
-            assertNotEquals(info1, info2);
-        }
+        assertEquals(info1, info2);
 
         info1.setSSID(WifiSsid.createFromHex(null));
-        // Same behavior pre-S & post-S.
         assertNotEquals(info1, info2);
 
         info2.setSSID(WifiSsid.createFromHex(null));
-        if (SdkLevel.isAtLeastS()) {
-            assertEquals(info1, info2);
-        } else {
-            assertNotEquals(info1, info2);
-        }
+        assertEquals(info1, info2);
+    }
+
+    @Test
+    public void testWifiInfoEqualsWithInfoElements() throws Exception {
+        WifiInfo.Builder builder = new WifiInfo.Builder()
+                .setSsid(TEST_SSID.getBytes(StandardCharsets.UTF_8))
+                .setBssid(TEST_BSSID)
+                .setRssi(TEST_RSSI)
+                .setNetworkId(TEST_NETWORK_ID);
+
+        WifiInfo info1 = builder.build();
+        WifiInfo info2 = builder.build();
+        assertEquals(info1, info2);
+
+        info1.setInformationElements(generateIes());
+        info2.setInformationElements(generateIes());
+
+        assertEquals(info1, info2);
     }
 
     @Test
@@ -334,32 +389,36 @@
 
         WifiInfo info1 = builder.build();
         WifiInfo info2 = builder.build();
-        if (SdkLevel.isAtLeastS()) {
-            assertEquals(info1.hashCode(), info2.hashCode());
-        } else {
-            assertNotEquals(info1.hashCode(), info2.hashCode());
-        }
+        assertEquals(info1.hashCode(), info2.hashCode());
 
         info1.setSubscriptionId(TEST_SUB_ID);
-        // Same behavior pre-S & post-S.
         assertNotEquals(info1.hashCode(), info2.hashCode());
 
         info2.setSubscriptionId(TEST_SUB_ID);
-        if (SdkLevel.isAtLeastS()) {
-            assertEquals(info1.hashCode(), info2.hashCode());
-        } else {
-            assertNotEquals(info1.hashCode(), info2.hashCode());
-        }
+        assertEquals(info1.hashCode(), info2.hashCode());
 
         info1.setSSID(WifiSsid.createFromHex(null));
-        // Same behavior pre-S & post-S.
         assertNotEquals(info1.hashCode(), info2.hashCode());
 
         info2.setSSID(WifiSsid.createFromHex(null));
-        if (SdkLevel.isAtLeastS()) {
-            assertEquals(info1.hashCode(), info2.hashCode());
-        } else {
-            assertNotEquals(info1.hashCode(), info2.hashCode());
-        }
+        assertEquals(info1.hashCode(), info2.hashCode());
+    }
+
+    private static List<ScanResult.InformationElement> generateIes() {
+        List<ScanResult.InformationElement> informationElements = new ArrayList<>();
+        ScanResult.InformationElement informationElement = new ScanResult.InformationElement();
+        informationElement.id = ScanResult.InformationElement.EID_HT_OPERATION;
+        informationElement.idExt = 0;
+        informationElement.bytes = new byte[]{0x11, 0x22, 0x33};
+        informationElements.add(informationElement);
+
+        informationElement = new ScanResult.InformationElement();
+        informationElement.id = ScanResult.InformationElement.EID_EXTENSION_PRESENT;
+        informationElement.idExt = ScanResult.InformationElement.EID_EXT_HE_OPERATION;
+        informationElement.bytes = new byte[]{0x44, 0x55, 0x66};
+        informationElements.add(informationElement);
+
+        return informationElements;
+
     }
 }