Support CarrierService#notifyCarrierNetworkChange with subscription

Current APIs does not include subId or phoneId to locate which
carrier network reported the change. A patch based on carrier
privilege were deployed in Q to temprarily fix the issue.

However, multiple subscriptions which all have the carrier
privileges will all get notified, even only one of them
has the real change.

To throughly resolve the issue, the subscription is introduced
in the API to distinguish which network actually has the change.

Bug: 131697791
Test: atest TelephonyRegistryManagerTest CarrierServiceTest
Change-Id: Ie7ee25f53da605c15e1a9228482cc8c4e7406d2a
diff --git a/core/api/current.txt b/core/api/current.txt
index c3b5b94..6aeed38e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -38281,7 +38281,8 @@
 
   public abstract class CarrierService extends android.app.Service {
     ctor public CarrierService();
-    method public final void notifyCarrierNetworkChange(boolean);
+    method @Deprecated public final void notifyCarrierNetworkChange(boolean);
+    method public final void notifyCarrierNetworkChange(int, boolean);
     method @CallSuper public android.os.IBinder onBind(android.content.Intent);
     method public abstract android.os.PersistableBundle onLoadConfig(android.service.carrier.CarrierIdentifier);
     field public static final String CARRIER_SERVICE_INTERFACE = "android.service.carrier.CarrierService";
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 859fd80..e7f8920 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -285,6 +285,8 @@
      * UI. There is no timeout associated with showing this UX, so a carrier app must be sure to
      * call with active set to false sometime after calling with it set to {@code true}.
      * <p>
+     * This will apply to all subscriptions the carrier app has carrier privileges on.
+     * <p>
      * Requires Permission: calling app has carrier privileges.
      *
      * @param active Whether the carrier network change is or shortly will be
@@ -300,6 +302,31 @@
     }
 
     /**
+     * Informs the system of an intentional upcoming carrier network change by a carrier app on the
+     * given {@code subscriptionId}. This call only used to allow the system to provide alternative
+     * UI while telephony is performing an action that may result in intentional, temporary network
+     * lack of connectivity.
+     * <p>
+     * Based on the active parameter passed in, this method will either show or hide the
+     * alternative UI. There is no timeout associated with showing this UX, so a carrier app must be
+     * sure to call with active set to false sometime after calling with it set to {@code true}.
+     * <p>
+     * Requires Permission: calling app has carrier privileges.
+     *
+     * @param subscriptionId the subscription of the carrier network.
+     * @param active whether the carrier network change is or shortly will be active. Set this value
+     *              to true to begin showing alternative UI and false to stop.
+     * @see TelephonyManager#hasCarrierPrivileges
+     */
+    public void notifyCarrierNetworkChange(int subscriptionId, boolean active) {
+        try {
+            sRegistry.notifyCarrierNetworkChangeWithSubId(subscriptionId, active);
+        } catch (RemoteException ex) {
+            // system server crash
+        }
+    }
+
+    /**
      * Notify call state changed on certain subscription.
      *
      * @param slotIndex for which call state changed. Can be derived from subId except when subId is
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 6ba0279..15d4246 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -77,6 +77,7 @@
     void notifySubscriptionInfoChanged();
     void notifyOpportunisticSubscriptionInfoChanged();
     void notifyCarrierNetworkChange(in boolean active);
+    void notifyCarrierNetworkChangeWithSubId(in int subId, in boolean active);
     void notifyUserMobileDataStateChangedForPhoneId(in int phoneId, in int subId, in boolean state);
     void notifyDisplayInfoChanged(int slotIndex, int subId, in TelephonyDisplayInfo telephonyDisplayInfo);
     void notifyPhoneCapabilityChanged(in PhoneCapability capability);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 91cd2f6..d7c1cfb 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -366,7 +366,7 @@
 
     private List<BarringInfo> mBarringInfo = null;
 
-    private boolean mCarrierNetworkChangeState = false;
+    private boolean[] mCarrierNetworkChangeState = null;
 
     private PhoneCapability mPhoneCapability = null;
 
@@ -675,6 +675,7 @@
         mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
         mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
         mTelephonyDisplayInfos = copyOf(mTelephonyDisplayInfos, mNumPhones);
+        mCarrierNetworkChangeState = copyOf(mCarrierNetworkChangeState, mNumPhones);
         mIsDataEnabled= copyOf(mIsDataEnabled, mNumPhones);
         mDataEnabledReason = copyOf(mDataEnabledReason, mNumPhones);
         mAllowedNetworkTypeReason = copyOf(mAllowedNetworkTypeReason, mNumPhones);
@@ -720,6 +721,7 @@
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mPreciseDataConnectionStates.add(new ArrayMap<>());
             mBarringInfo.add(i, new BarringInfo());
+            mCarrierNetworkChangeState[i] = false;
             mTelephonyDisplayInfos[i] = null;
             mIsDataEnabled[i] = false;
             mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER;
@@ -784,6 +786,7 @@
         mOutgoingCallEmergencyNumber = new EmergencyNumber[numPhones];
         mOutgoingSmsEmergencyNumber = new EmergencyNumber[numPhones];
         mBarringInfo = new ArrayList<>();
+        mCarrierNetworkChangeState = new boolean[numPhones];
         mTelephonyDisplayInfos = new TelephonyDisplayInfo[numPhones];
         mPhysicalChannelConfigs = new ArrayList<>();
         mAllowedNetworkTypeReason = new int[numPhones];
@@ -820,6 +823,7 @@
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mPreciseDataConnectionStates.add(new ArrayMap<>());
             mBarringInfo.add(i, new BarringInfo());
+            mCarrierNetworkChangeState[i] = false;
             mTelephonyDisplayInfos[i] = null;
             mIsDataEnabled[i] = false;
             mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER;
@@ -1230,7 +1234,7 @@
                 }
                 if (events.contains(TelephonyCallback.EVENT_CARRIER_NETWORK_CHANGED)) {
                     try {
-                        r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState);
+                        r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState[r.phoneId]);
                     } catch (RemoteException ex) {
                         remove(r.binder);
                     }
@@ -1724,23 +1728,37 @@
             throw new SecurityException("notifyCarrierNetworkChange without carrier privilege");
         }
 
-        synchronized (mRecords) {
-            mCarrierNetworkChangeState = active;
-            for (int subId : subIds) {
-                int phoneId = getPhoneIdFromSubId(subId);
+        for (int subId : subIds) {
+            notifyCarrierNetworkChangeWithPermission(subId, active);
+        }
+    }
 
-                if (VDBG) {
-                    log("notifyCarrierNetworkChange: active=" + active + "subId: " + subId);
-                }
-                for (Record r : mRecords) {
-                    if (r.matchTelephonyCallbackEvent(
-                            TelephonyCallback.EVENT_CARRIER_NETWORK_CHANGED)
-                            && idMatch(r, subId, phoneId)) {
-                        try {
-                            r.callback.onCarrierNetworkChange(active);
-                        } catch (RemoteException ex) {
-                            mRemoveList.add(r.binder);
-                        }
+    @Override
+    public void notifyCarrierNetworkChangeWithSubId(int subId, boolean active) {
+        if (!TelephonyPermissions.checkCarrierPrivilegeForSubId(mContext, subId)) {
+            throw new SecurityException(
+                    "notifyCarrierNetworkChange without carrier privilege on subId " + subId);
+        }
+
+        notifyCarrierNetworkChangeWithPermission(subId, active);
+    }
+
+    private void notifyCarrierNetworkChangeWithPermission(int subId, boolean active) {
+        synchronized (mRecords) {
+            int phoneId = getPhoneIdFromSubId(subId);
+            mCarrierNetworkChangeState[phoneId] = active;
+
+            if (VDBG) {
+                log("notifyCarrierNetworkChange: active=" + active + "subId: " + subId);
+            }
+            for (Record r : mRecords) {
+                if (r.matchTelephonyCallbackEvent(
+                        TelephonyCallback.EVENT_CARRIER_NETWORK_CHANGED)
+                        && idMatch(r, subId, phoneId)) {
+                    try {
+                        r.callback.onCarrierNetworkChange(active);
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
                     }
                 }
             }
@@ -2788,6 +2806,7 @@
                 pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
                 pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]);
                 pw.println("mBarringInfo=" + mBarringInfo.get(i));
+                pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState[i]);
                 pw.println("mTelephonyDisplayInfo=" + mTelephonyDisplayInfos[i]);
                 pw.println("mIsDataEnabled=" + mIsDataEnabled);
                 pw.println("mDataEnabledReason=" + mDataEnabledReason);
@@ -2797,7 +2816,7 @@
                 pw.println("mLinkCapacityEstimateList=" + mLinkCapacityEstimateLists.get(i));
                 pw.decreaseIndent();
             }
-            pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState);
+
             pw.println("mPhoneCapability=" + mPhoneCapability);
             pw.println("mActiveDataSubId=" + mActiveDataSubId);
             pw.println("mRadioPowerState=" + mRadioPowerState);
diff --git a/telephony/java/android/service/carrier/CarrierService.java b/telephony/java/android/service/carrier/CarrierService.java
index d06ec11..ca27998 100644
--- a/telephony/java/android/service/carrier/CarrierService.java
+++ b/telephony/java/android/service/carrier/CarrierService.java
@@ -115,7 +115,12 @@
      *               active. Set this value to true to begin showing
      *               alternative UI and false to stop.
      * @see android.telephony.TelephonyManager#hasCarrierPrivileges
+     * @deprecated use {@link #notifyCarrierNetworkChange(int, boolean)} instead.
+     *             With no parameter to specify the subscription, this API will
+     *             apply to all subscriptions that the carrier app has carrier
+     *             privileges on.
      */
+    @Deprecated
     public final void notifyCarrierNetworkChange(boolean active) {
         TelephonyRegistryManager telephonyRegistryMgr =
             (TelephonyRegistryManager) this.getSystemService(
@@ -126,6 +131,31 @@
     }
 
     /**
+     * Informs the system of an intentional upcoming carrier network change by a carrier app on the
+     * given {@code subscriptionId}. This call is optional and is only used to allow the system to
+     * provide alternative UI while telephony is performing an action that may result in
+     * intentional, temporary network lack of connectivity.
+     *
+     * <p>Based on the active parameter passed in, this method will either show or hide the
+     * alternative UI. There is no timeout associated with showing this UX, so a carrier app must
+     * be sure to call with active set to false sometime after calling with it set to true.
+     *
+     * <p>Requires Permission: calling app has carrier privileges.
+     *
+     * @param subscriptionId the subscription of the carrier network that trigger the change.
+     * @param active whether the carrier network change is or shortly will be active. Set this
+     *               value to true to begin showing alternative UI and false to stop.
+     * @see android.telephony.TelephonyManager#hasCarrierPrivileges
+     */
+    public final void notifyCarrierNetworkChange(int subscriptionId, boolean active) {
+        TelephonyRegistryManager telephonyRegistryMgr = this.getSystemService(
+                TelephonyRegistryManager.class);
+        if (telephonyRegistryMgr != null) {
+            telephonyRegistryMgr.notifyCarrierNetworkChange(subscriptionId, active);
+        }
+    }
+
+    /**
      * If overriding this method, call through to the super method for any unknown actions.
      * {@inheritDoc}
      */