Merge "Adds API for notifying Cellular Simultaneous Calling" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d26662d..95fa903 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -14554,6 +14554,7 @@
     field public static final int EVENT_SERVICE_STATE_CHANGED = 1; // 0x1
     field public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9; // 0x9
     field public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED = 41; // 0x29
     field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SRVCC_STATE_CHANGED = 16; // 0x10
     field public static final int EVENT_USER_MOBILE_DATA_STATE_CHANGED = 20; // 0x14
     field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_VOICE_ACTIVATION_STATE_CHANGED = 18; // 0x12
@@ -14600,6 +14601,10 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onRadioPowerStateChanged(int);
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") public static interface TelephonyCallback.SimultaneousCellularCallingSupportListener {
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSimultaneousCellularCallingSubscriptionsChanged(@NonNull java.util.Set<java.lang.Integer>);
+  }
+
   public static interface TelephonyCallback.SrvccStateListener {
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSrvccStateChanged(int);
   }
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index cf1156d..fb57921 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -1681,6 +1681,10 @@
                 @EmergencyCallbackModeStopReason int reason) {
             // not support. Can't override. Use TelephonyCallback.
         }
+
+        public final void onSimultaneousCallingStateChanged(int[] subIds) {
+            // not supported on the deprecated interface - Use TelephonyCallback instead
+        }
     }
 
     private void log(String s) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 19bcf28..dc6a035 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -33,15 +34,19 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IPhoneStateListener;
+import com.android.internal.telephony.flags.Flags;
 
 import dalvik.system.VMRuntime;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
 
 /**
  * A callback class for monitoring changes in specific telephony states
@@ -627,6 +632,18 @@
     public static final int EVENT_EMERGENCY_CALLBACK_MODE_CHANGED = 40;
 
     /**
+     * Event for listening to changes in simultaneous cellular calling subscriptions.
+     *
+     * @see SimultaneousCellularCallingSupportListener
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @SystemApi
+    public static final int EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED = 41;
+
+    /**
      * @hide
      */
     @IntDef(prefix = {"EVENT_"}, value = {
@@ -669,7 +686,8 @@
             EVENT_LINK_CAPACITY_ESTIMATE_CHANGED,
             EVENT_TRIGGER_NOTIFY_ANBR,
             EVENT_MEDIA_QUALITY_STATUS_CHANGED,
-            EVENT_EMERGENCY_CALLBACK_MODE_CHANGED
+            EVENT_EMERGENCY_CALLBACK_MODE_CHANGED,
+            EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface TelephonyEvent {
@@ -1373,6 +1391,44 @@
     }
 
     /**
+     * Interface for listening to changes in the simultaneous cellular calling state for active
+     * cellular subscriptions.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+    @SystemApi
+    public interface SimultaneousCellularCallingSupportListener {
+        /**
+         * Notify the Listener that the subscriptions available for simultaneous <b>cellular</b>
+         * calling have changed.
+         * <p>
+         * If we have an ongoing <b>cellular</b> call on one subscription in this Set, a
+         * simultaneous incoming or outgoing <b>cellular</b> call is possible on any of the
+         * subscriptions in this Set. On a traditional Dual Sim Dual Standby device, simultaneous
+         * calling is not possible between subscriptions, where on a Dual Sim Dual Active device,
+         * simultaneous calling may be possible between subscriptions in certain network conditions.
+         * <p>
+         * Note: This listener only tracks the capability of the modem to perform simultaneous
+         * cellular calls and does not track the simultaneous calling state of scenarios based on
+         * multiple IMS registration over multiple transports (WiFi/Internet calling).
+         * <p>
+         * Note: This listener fires for all changes to cellular calling subscriptions independent
+         * of which subscription it is registered on.
+         *
+         * @param simultaneousCallingSubscriptionIds The Set of subscription IDs that support
+         * simultaneous calling. If there is an ongoing call on a subscription in this Set, then a
+         * simultaneous incoming or outgoing call is only possible for other subscriptions in this
+         * Set. If there is an ongoing call on a subscription that is not in this Set, then
+         * simultaneous calling is not possible at the current time.
+         *
+         */
+        @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+        void onSimultaneousCellularCallingSubscriptionsChanged(
+                @NonNull Set<Integer> simultaneousCallingSubscriptionIds);
+    }
+
+    /**
      * Interface for call attributes listener.
      *
      * @hide
@@ -1976,6 +2032,17 @@
                                     allowedNetworkType)));
         }
 
+        public void onSimultaneousCallingStateChanged(int[] subIds) {
+            SimultaneousCellularCallingSupportListener listener =
+                    (SimultaneousCellularCallingSupportListener) mTelephonyCallbackWeakRef.get();
+            if (listener == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> listener.onSimultaneousCellularCallingSubscriptionsChanged(
+                                    Arrays.stream(subIds).boxed().collect(Collectors.toSet()))));
+        }
+
         public void onLinkCapacityEstimateChanged(
                 List<LinkCapacityEstimate> linkCapacityEstimateList) {
             LinkCapacityEstimateChangedListener listener =
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 886727e..0de4505 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -994,6 +994,21 @@
         }
     }
 
+    /**
+     * Notify external listeners that the subscriptions supporting simultaneous cellular calling
+     * have changed.
+     * @param subIds The new set of subIds supporting simultaneous cellular calling.
+     */
+    public void notifySimultaneousCellularCallingSubscriptionsChanged(Set<Integer> subIds) {
+        try {
+            sRegistry.notifySimultaneousCellularCallingSubscriptionsChanged(
+                    subIds.stream().mapToInt(i -> i).toArray());
+        } catch (RemoteException ex) {
+            // system server crash
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
     public @NonNull Set<Integer> getEventsFromCallback(
             @NonNull TelephonyCallback telephonyCallback) {
         Set<Integer> eventList = new ArraySet<>();
@@ -1135,7 +1150,11 @@
             eventList.add(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED);
         }
 
-
+        if (telephonyCallback
+                instanceof TelephonyCallback.SimultaneousCellularCallingSupportListener) {
+            eventList.add(
+                    TelephonyCallback.EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED);
+        }
         return eventList;
     }
 
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 03cfd4f..969f95d 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -80,4 +80,5 @@
     void onMediaQualityStatusChanged(in MediaQualityStatus mediaQualityStatus);
     void onCallBackModeStarted(int type);
     void onCallBackModeStopped(int type, int reason);
+    void onSimultaneousCallingStateChanged(in int[] subIds);
 }
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index aab2242..0203ea4 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -104,6 +104,7 @@
     void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType);
     void notifyLinkCapacityEstimateChanged(in int phoneId, in int subId,
             in List<LinkCapacityEstimate> linkCapacityEstimateList);
+    void notifySimultaneousCellularCallingSubscriptionsChanged(in int[] subIds);
 
     void addCarrierPrivilegesCallback(
             int phoneId, ICarrierPrivilegesCallback callback, String pkg, String featureId);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index eb6fdd7..f921b0b 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -418,6 +418,8 @@
             LinkCapacityEstimate.INVALID, LinkCapacityEstimate.INVALID)));
     private List<List<LinkCapacityEstimate>> mLinkCapacityEstimateLists;
 
+    private int[] mSimultaneousCellularCallingSubIds = {};
+
     private int[] mECBMReason;
     private boolean[] mECBMStarted;
     private int[] mSCBMReason;
@@ -564,7 +566,9 @@
                 || events.contains(TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED)
                 || events.contains(TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED)
                 || events.contains(TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED)
-                || events.contains(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED);
+                || events.contains(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED)
+                || events.contains(TelephonyCallback
+                        .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED);
     }
 
     private static final int MSG_USER_SWITCHED = 1;
@@ -1427,6 +1431,15 @@
                         remove(r.binder);
                     }
                 }
+                if (events.contains(TelephonyCallback
+                        .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED)) {
+                    try {
+                        r.callback.onSimultaneousCallingStateChanged(
+                                mSimultaneousCellularCallingSubIds);
+                    } catch (RemoteException ex) {
+                        remove(r.binder);
+                    }
+                }
                 if (events.contains(
                         TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED)) {
                     try {
@@ -3092,6 +3105,43 @@
         }
     }
 
+    /**
+     * Notify the listeners that simultaneous cellular calling subscriptions have changed
+     * @param subIds The set of subIds that support simultaneous cellular calling
+     */
+    public void notifySimultaneousCellularCallingSubscriptionsChanged(int[] subIds) {
+        if (!checkNotifyPermission("notifySimultaneousCellularCallingSubscriptionsChanged()")) {
+            return;
+        }
+
+        if (VDBG) {
+            StringBuilder b = new StringBuilder();
+            b.append("notifySimultaneousCellularCallingSubscriptionsChanged: ");
+            b.append("subIds = {");
+            for (int i : subIds) {
+                b.append(" ");
+                b.append(i);
+            }
+            b.append("}");
+            log(b.toString());
+        }
+
+        synchronized (mRecords) {
+            mSimultaneousCellularCallingSubIds = subIds;
+            for (Record r : mRecords) {
+                if (r.matchTelephonyCallbackEvent(TelephonyCallback
+                        .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED)) {
+                    try {
+                        r.callback.onSimultaneousCallingStateChanged(subIds);
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
     @Override
     public void addCarrierPrivilegesCallback(
             int phoneId,
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 4c37f7d..b84ff29 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims;
 
+import android.annotation.FlaggedApi;
 import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -46,6 +47,7 @@
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.lang.annotation.Retention;
@@ -152,12 +154,36 @@
     public static final long CAPABILITY_TERMINAL_BASED_CALL_WAITING = 1 << 2;
 
     /**
+     * This ImsService supports the capability to manage calls on multiple subscriptions at the same
+     * time.
+     * <p>
+     * When set, this ImsService supports managing calls on multiple subscriptions at the same time
+     * for all WLAN network configurations. Telephony will allow new outgoing/incoming IMS calls to
+     * be set up on other subscriptions while there is an ongoing call. The ImsService must also
+     * support managing calls on WWAN + WWAN configurations whenever the modem also reports
+     * simultaneous calling availability, which can be listened to using the
+     * {@link android.telephony.TelephonyCallback.SimultaneousCellularCallingSupportListener} API.
+     * Telephony will only allow additional ongoing/incoming IMS calls on another subscription to be
+     * set up on WWAN + WWAN configurations when the modem reports that simultaneous cellular
+     * calling is allowed at the current time on both subscriptions where there are ongoing calls.
+     * <p>
+     * When unset (default), this ImsService can not support calls on multiple subscriptions at the
+     * same time for any WLAN or WWAN configurations, so pending outgoing call placed on another
+     * cellular subscription while there is an ongoing call will be cancelled by Telephony.
+     * Similarly, any incoming call notification on another cellular subscription while there is an
+     * ongoing call will be rejected.
+     * @hide TODO: move this to system API when we have a backing implementation + CTS testing
+     */
+    @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+    public static final long CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING = 1 << 3;
+
+    /**
      * Used for internal correctness checks of capabilities set by the ImsService implementation and
      * tracks the index of the largest defined flag in the capabilities long.
      * @hide
      */
     public static final long CAPABILITY_MAX_INDEX =
-            Long.numberOfTrailingZeros(CAPABILITY_TERMINAL_BASED_CALL_WAITING);
+            Long.numberOfTrailingZeros(CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING);
 
     /**
      * @hide