[NetworkSuggestion] Allow app to set subId for the suggestion

SubId will be used to identify which SIM/subscription to use for this
suggestion. Same network with different SubIds will be consider
different suggestions.
Also add a system API in WifiConfiguration to help data calculation for
different SIM.

Bug: 169275787
Test: atest android.net.wifi

Change-Id: I093c0661e28cc2df5e97391342101e8ecc331a0f
diff --git a/wifi/api/current.txt b/wifi/api/current.txt
index 6928618..2d48a83 100644
--- a/wifi/api/current.txt
+++ b/wifi/api/current.txt
@@ -492,6 +492,7 @@
     method @IntRange(from=0) public int getPriority();
     method public int getPriorityGroup();
     method @Nullable public String getSsid();
+    method public int getSubscriptionId();
     method public boolean isAppInteractionRequired();
     method public boolean isCredentialSharedWithUser();
     method public boolean isEnhancedOpen();
@@ -520,6 +521,7 @@
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriority(@IntRange(from=0) int);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriorityGroup(int);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSsid(@NonNull String);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSubscriptionId(int);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setUntrusted(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiEnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiPassphrase(@NonNull String);
diff --git a/wifi/api/system-current.txt b/wifi/api/system-current.txt
index bf7003c..98cb849 100644
--- a/wifi/api/system-current.txt
+++ b/wifi/api/system-current.txt
@@ -350,6 +350,7 @@
     field @Deprecated public int numScorerOverrideAndSwitchedNetwork;
     field @Deprecated public boolean requirePmf;
     field @Deprecated public boolean shared;
+    field @Deprecated public int subscriptionId;
     field @Deprecated public boolean useExternalScores;
   }
 
diff --git a/wifi/api/system-lint-baseline.txt b/wifi/api/system-lint-baseline.txt
index 6547ee8..a5f3f7c 100644
--- a/wifi/api/system-lint-baseline.txt
+++ b/wifi/api/system-lint-baseline.txt
@@ -1,6 +1,11 @@
 // Baseline format: 1.0
 MissingGetterMatchingBuilder: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig):
-    android.net.wifi.rtt.RangingRequest does not declare a `getResponders()` method matching method android.net.wifi.rtt.RangingRequest.Builder.addResponder(android.net.wifi.rtt.ResponderConfig)
+
+
 
 MissingNullability: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig):
 
+
+
+MutableBareField: android.net.wifi.WifiConfiguration#subscriptionId:
+    Bare field subscriptionId must be marked final, or moved behind accessors if mutable
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index fc6c59a..5208bcf 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -35,6 +35,8 @@
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
@@ -881,6 +883,14 @@
     public int carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
 
     /**
+     * The subscription ID identifies the SIM card for which this network configuration is valid.
+     * See {@link SubscriptionInfo#getSubscriptionId()}
+     * @hide
+     */
+    @SystemApi
+    public int subscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+    /**
      * @hide
      * Auto-join is allowed by user for this network.
      * Default true.
@@ -2871,6 +2881,7 @@
             requirePmf = source.requirePmf;
             updateIdentifier = source.updateIdentifier;
             carrierId = source.carrierId;
+            subscriptionId = source.subscriptionId;
             mPasspointUniqueId = source.mPasspointUniqueId;
         }
     }
@@ -2946,6 +2957,7 @@
         dest.writeLong(randomizedMacLastModifiedTimeMs);
         dest.writeInt(carrierId);
         dest.writeString(mPasspointUniqueId);
+        dest.writeInt(subscriptionId);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -3022,6 +3034,7 @@
                 config.randomizedMacLastModifiedTimeMs = in.readLong();
                 config.carrierId = in.readInt();
                 config.mPasspointUniqueId = in.readString();
+                config.subscriptionId = in.readInt();
                 return config;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 9162c5f..c1f9005 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -29,6 +29,8 @@
 import android.net.wifi.util.SdkLevelUtil;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
@@ -121,6 +123,12 @@
         private int mCarrierId;
 
         /**
+         * The Subscription ID identifies the SIM card for which this network configuration is
+         * valid.
+         */
+        private int mSubscriptionId;
+
+        /**
          * Whether this network is shared credential with user to allow user manually connect.
          */
         private boolean mIsSharedWithUser;
@@ -185,6 +193,7 @@
             mIsNetworkOemPaid = false;
             mPriorityGroup = 0;
             mIsEnhancedMacRandomizationEnabled = false;
+            mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         }
 
         /**
@@ -353,6 +362,27 @@
         }
 
         /**
+         * Set the subscription ID of the SIM card for which this suggestion is targeted.
+         * The suggestion will only apply to that SIM card.
+         * <p>
+         * The subscription ID must belong to a carrier ID which meets either of the following
+         * conditions:
+         * <li>The carrier ID specified by the cross carrier provider, or</li>
+         * <li>The carrier ID which is used to validate the suggesting carrier-privileged app, see
+         * {@link TelephonyManager#hasCarrierPrivileges()}</li>
+         *
+         * @param subId subscription ID see {@link SubscriptionInfo#getSubscriptionId()}
+         * @return Instance of {@link Builder} to enable chaining of the builder method.
+         */
+        public @NonNull Builder setSubscriptionId(int subId) {
+            if (!SdkLevelUtil.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
+            mSubscriptionId = subId;
+            return this;
+        }
+
+        /**
          * Set the priority group ID, {@link #setPriority(int)} will only impact the network
          * suggestions from the same priority group within the same app.
          *
@@ -360,6 +390,9 @@
          * @return Instance of {@link Builder} to enable chaining of the builder method.
          */
         public @NonNull Builder setPriorityGroup(int priorityGroup) {
+            if (!SdkLevelUtil.isAtLeastS()) {
+                throw new UnsupportedOperationException();
+            }
             mPriorityGroup = priorityGroup;
             return this;
         }
@@ -675,6 +708,7 @@
             wifiConfiguration.macRandomizationSetting = mIsEnhancedMacRandomizationEnabled
                     ? WifiConfiguration.RANDOMIZATION_ENHANCED
                     : WifiConfiguration.RANDOMIZATION_PERSISTENT;
+            wifiConfiguration.subscriptionId = mSubscriptionId;
             return wifiConfiguration;
         }
 
@@ -704,7 +738,9 @@
             wifiConfiguration.meteredOverride = mMeteredOverride;
             wifiConfiguration.trusted = !mIsNetworkUntrusted;
             wifiConfiguration.oemPaid = mIsNetworkOemPaid;
+            wifiConfiguration.subscriptionId = mSubscriptionId;
             mPasspointConfiguration.setCarrierId(mCarrierId);
+            mPasspointConfiguration.setSubscriptionId(mSubscriptionId);
             mPasspointConfiguration.setMeteredOverride(wifiConfiguration.meteredOverride);
             wifiConfiguration.macRandomizationSetting = mIsEnhancedMacRandomizationEnabled
                     ? WifiConfiguration.RANDOMIZATION_ENHANCED
@@ -946,7 +982,8 @@
     @Override
     public int hashCode() {
         return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID,
-                wifiConfiguration.allowedKeyManagement, wifiConfiguration.getKey());
+                wifiConfiguration.allowedKeyManagement, wifiConfiguration.getKey(),
+                wifiConfiguration.subscriptionId, wifiConfiguration.carrierId);
     }
 
     /**
@@ -970,7 +1007,9 @@
                 && Objects.equals(this.wifiConfiguration.allowedKeyManagement,
                 lhs.wifiConfiguration.allowedKeyManagement)
                 && TextUtils.equals(this.wifiConfiguration.getKey(),
-                lhs.wifiConfiguration.getKey());
+                lhs.wifiConfiguration.getKey())
+                && this.wifiConfiguration.carrierId == lhs.wifiConfiguration.carrierId
+                && this.wifiConfiguration.subscriptionId == lhs.wifiConfiguration.subscriptionId;
     }
 
     @Override
@@ -1123,6 +1162,19 @@
      * @see Builder#setPriorityGroup(int)
      */
     public int getPriorityGroup() {
+        if (!SdkLevelUtil.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
         return priorityGroup;
     }
+
+    /**
+     * @see Builder#setSubscriptionId(int)
+     */
+    public int getSubscriptionId() {
+        if (!SdkLevelUtil.isAtLeastS()) {
+            throw new UnsupportedOperationException();
+        }
+        return wifiConfiguration.subscriptionId;
+    }
 }
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 61a6e16..357c5bc 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -30,6 +30,8 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
@@ -412,6 +414,12 @@
     private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
 
     /**
+     * The subscription ID identifies the SIM card who provides this network configuration.
+     * See {@link SubscriptionInfo#getSubscriptionId()}
+     */
+    private int mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+    /**
      * Set the carrier ID associated with current configuration.
      * @param carrierId {@code mCarrierId}
      * @hide
@@ -430,6 +438,24 @@
     }
 
     /**
+     * Set the subscription ID associated with current configuration.
+     * @param subscriptionId {@code mSubscriptionId}
+     * @hide
+     */
+    public void setSubscriptionId(int subscriptionId) {
+        this.mSubscriptionId = subscriptionId;
+    }
+
+    /**
+     * Get the carrier ID associated with current configuration.
+     * @return {@code mSubscriptionId}
+     * @hide
+     */
+    public int getSubscriptionId() {
+        return mSubscriptionId;
+    }
+
+    /**
      * The auto-join configuration specifies whether or not the Passpoint Configuration is
      * considered for auto-connection. If true then yes, if false then it isn't considered as part
      * of auto-connection - but can still be manually connected to.
@@ -604,6 +630,7 @@
         mServiceFriendlyNames = source.mServiceFriendlyNames;
         mAaaServerTrustedNames = source.mAaaServerTrustedNames;
         mCarrierId = source.mCarrierId;
+        mSubscriptionId = source.mSubscriptionId;
         mIsAutojoinEnabled = source.mIsAutojoinEnabled;
         mIsMacRandomizationEnabled = source.mIsMacRandomizationEnabled;
         mIsEnhancedMacRandomizationEnabled = source.mIsEnhancedMacRandomizationEnabled;
@@ -641,6 +668,7 @@
         dest.writeBoolean(mIsMacRandomizationEnabled);
         dest.writeBoolean(mIsEnhancedMacRandomizationEnabled);
         dest.writeInt(mMeteredOverride);
+        dest.writeInt(mSubscriptionId);
     }
 
     @Override
@@ -671,6 +699,7 @@
                 && mUsageLimitDataLimit == that.mUsageLimitDataLimit
                 && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes
                 && mCarrierId == that.mCarrierId
+                && mSubscriptionId == that.mSubscriptionId
                 && mIsAutojoinEnabled == that.mIsAutojoinEnabled
                 && mIsMacRandomizationEnabled == that.mIsMacRandomizationEnabled
                 && mIsEnhancedMacRandomizationEnabled == that.mIsEnhancedMacRandomizationEnabled
@@ -686,7 +715,7 @@
                 mSubscriptionExpirationTimeMillis, mUsageLimitUsageTimePeriodInMinutes,
                 mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes,
                 mServiceFriendlyNames, mCarrierId, mIsAutojoinEnabled, mIsMacRandomizationEnabled,
-                mIsEnhancedMacRandomizationEnabled, mMeteredOverride);
+                mIsEnhancedMacRandomizationEnabled, mMeteredOverride, mSubscriptionId);
     }
 
     @Override
@@ -740,6 +769,7 @@
             builder.append("ServiceFriendlyNames: ").append(mServiceFriendlyNames);
         }
         builder.append("CarrierId:" + mCarrierId);
+        builder.append("SubscriptionId:" + mSubscriptionId);
         builder.append("IsAutojoinEnabled:" + mIsAutojoinEnabled);
         builder.append("mIsMacRandomizationEnabled:" + mIsMacRandomizationEnabled);
         builder.append("mIsEnhancedMacRandomizationEnabled:" + mIsEnhancedMacRandomizationEnabled);
@@ -853,6 +883,7 @@
                 config.mIsMacRandomizationEnabled = in.readBoolean();
                 config.mIsEnhancedMacRandomizationEnabled = in.readBoolean();
                 config.mMeteredOverride = in.readInt();
+                config.mSubscriptionId = in.readInt();
                 return config;
             }
 
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 8af7500..54f0c0f 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -73,6 +73,8 @@
         config.fromWifiNetworkSuggestion = true;
         config.setRandomizedMacAddress(MacAddressUtils.createRandomUnicastAddress());
         MacAddress macBeforeParcel = config.getRandomizedMacAddress();
+        config.subscriptionId = 1;
+        config.carrierId = 1189;
         Parcel parcelW = Parcel.obtain();
         config.writeToParcel(parcelW, 0);
         byte[] bytes = parcelW.marshall();
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 6e08ca4..3f9ce5b 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -764,6 +764,32 @@
     }
 
     /**
+     * Verify that the builder creates the appropriate SIM credential suggestion with SubId, also
+     * verify {@link WifiNetworkSuggestion#equals(Object)} consider suggestion with different SubId
+     * as different suggestions.
+     */
+    @Test
+    public void testSimCredentialNetworkWithSubId() {
+        assumeTrue(SdkLevelUtil.isAtLeastS());
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+        enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
+        WifiNetworkSuggestion suggestion1 = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2EnterpriseConfig(enterpriseConfig)
+                .setSubscriptionId(1)
+                .build();
+        assertEquals(1, suggestion1.getSubscriptionId());
+        WifiNetworkSuggestion suggestion2 = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID)
+                .setWpa2EnterpriseConfig(enterpriseConfig)
+                .setSubscriptionId(2)
+                .build();
+        assertEquals(2, suggestion2.getSubscriptionId());
+        assertNotEquals(suggestion1, suggestion2);
+    }
+
+    /**
      * Check that parcel marshalling/unmarshalling works
      */
     @Test
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
index 8d55acb..5830a1e 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
@@ -30,6 +30,7 @@
 
 public class PasspointTestUtils {
     private static final int CERTIFICATE_FINGERPRINT_BYTES = 32;
+    private static final int TEST_SUB_ID = 1;
 
     /**
      * Utility function for creating a {@link android.net.wifi.hotspot2.pps.HomeSP}.
@@ -156,6 +157,7 @@
         friendlyNames.put("en", "ServiceName1");
         friendlyNames.put("kr", "ServiceName2");
         config.setServiceFriendlyNames(friendlyNames);
+        config.setSubscriptionId(TEST_SUB_ID);
         return config;
     }