Merge "add flag for emergency calling notif changes" into main
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index 1f31d5a..1cb6aae 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -65,6 +65,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.satellite.INtnSignalStrengthCallback;
+import android.telephony.satellite.ISatelliteCapabilitiesCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteStateCallback;
@@ -159,6 +160,7 @@
     private static final int CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING = 35;
     private static final int EVENT_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING_DONE = 36;
     private static final int EVENT_SERVICE_STATE_CHANGED = 37;
+    private static final int EVENT_SATELLITE_CAPABILITIES_CHANGED = 38;
 
     @NonNull private static SatelliteController sInstance;
     @NonNull private final Context mContext;
@@ -210,6 +212,8 @@
     private final AtomicBoolean mRegisteredForSatelliteModemStateChangedWithSatelliteService =
             new AtomicBoolean(false);
     private final AtomicBoolean mRegisteredForNtnSignalStrengthChanged = new AtomicBoolean(false);
+    private final AtomicBoolean mRegisteredForSatelliteCapabilitiesChanged =
+            new AtomicBoolean(false);
     /**
      * Map key: subId, value: callback to get error code of the provision request.
      */
@@ -227,6 +231,12 @@
      */
     private final ConcurrentHashMap<IBinder, INtnSignalStrengthCallback>
             mNtnSignalStrengthChangedListeners = new ConcurrentHashMap<>();
+    /**
+     * Map key: binder of the callback, value: callback to receive satellite capabilities changed
+     * events.
+     */
+    private final ConcurrentHashMap<IBinder, ISatelliteCapabilitiesCallback>
+            mSatelliteCapabilitiesChangedListeners = new ConcurrentHashMap<>();
     private final Object mIsSatelliteSupportedLock = new Object();
     @GuardedBy("mIsSatelliteSupportedLock")
     private Boolean mIsSatelliteSupported = null;
@@ -1207,6 +1217,16 @@
                 break;
             }
 
+            case EVENT_SATELLITE_CAPABILITIES_CHANGED: {
+                ar = (AsyncResult) msg.obj;
+                if (ar.result == null) {
+                    loge("EVENT_SATELLITE_CAPABILITIES_CHANGED: result is null");
+                } else {
+                    handleEventSatelliteCapabilitiesChanged((SatelliteCapabilities) ar.result);
+                }
+                break;
+            }
+
             default:
                 Log.w(TAG, "SatelliteControllerHandler: unexpected message code: " +
                         msg.what);
@@ -1972,7 +1992,7 @@
     /**
      * Registers for NTN signal strength changed from satellite modem.
      *
-     * @param subId The subId of the subscription to request for.
+     * @param subId The id of the subscription to request for.
      * @param callback The callback to handle the non-terrestrial network signal strength changed
      * event.
      *
@@ -1993,7 +2013,8 @@
      * Unregisters for NTN signal strength changed from satellite modem.
      * If callback was not registered before, the request will be ignored.
      *
-     * @param subId The subId of the subscription to unregister for provision state changed.
+     * @param subId The id of the subscription to unregister for listening NTN signal strength
+     * changed event.
      * @param callback The callback that was passed to
      * {@link #registerForNtnSignalStrengthChanged(int, INtnSignalStrengthCallback)}
      */
@@ -2008,6 +2029,44 @@
     }
 
     /**
+     * Registers for satellite capabilities change event from the satellite service.
+     *
+     * @param subId The id of the subscription to request for.
+     * @param callback The callback to handle the satellite capabilities changed event.
+     *
+     * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
+     */
+    @SatelliteManager.SatelliteResult public int registerForSatelliteCapabilitiesChanged(
+            int subId, @NonNull ISatelliteCapabilitiesCallback callback) {
+        if (DBG) logd("registerForSatelliteCapabilitiesChanged()");
+
+        int error = evaluateOemSatelliteRequestAllowed(true);
+        if (error != SATELLITE_RESULT_SUCCESS) return error;
+
+        mSatelliteCapabilitiesChangedListeners.put(callback.asBinder(), callback);
+        return SATELLITE_RESULT_SUCCESS;
+    }
+
+    /**
+     * Unregisters for satellite capabilities change event from the satellite service.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param subId The id of the subscription to unregister for listening satellite capabilities
+     * changed event.
+     * @param callback The callback that was passed to
+     * {@link #registerForSatelliteCapabilitiesChanged(int, ISatelliteCapabilitiesCallback)}
+     */
+    public void unregisterForSatelliteCapabilitiesChanged(
+            int subId, @NonNull ISatelliteCapabilitiesCallback callback) {
+        if (DBG) logd("unregisterForSatelliteCapabilitiesChanged()");
+
+        int error = evaluateOemSatelliteRequestAllowed(true);
+        if (error == SATELLITE_RESULT_SUCCESS) {
+            mSatelliteCapabilitiesChangedListeners.remove(callback.asBinder());
+        }
+    }
+
+    /**
      * This API can be used by only CTS to update satellite vendor service package name.
      *
      * @param servicePackageName The package name of the satellite vendor service.
@@ -2583,6 +2642,7 @@
             registerForPendingDatagramCount();
             registerForSatelliteModemStateChanged();
             registerForNtnSignalStrengthChanged();
+            registerForSatelliteCapabilitiesChanged();
 
             requestIsSatelliteProvisioned(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
                     new ResultReceiver(this) {
@@ -2666,6 +2726,21 @@
         }
     }
 
+    private void registerForSatelliteCapabilitiesChanged() {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("registerForSatelliteCapabilitiesChanged: oemEnabledSatelliteFlag is disabled");
+            return;
+        }
+
+        if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+            if (!mRegisteredForSatelliteCapabilitiesChanged.get()) {
+                mSatelliteModemInterface.registerForSatelliteCapabilitiesChanged(
+                        this, EVENT_SATELLITE_CAPABILITIES_CHANGED, null);
+                mRegisteredForSatelliteCapabilitiesChanged.set(true);
+            }
+        }
+    }
+
     private void handleEventSatelliteProvisionStateChanged(boolean provisioned) {
         logd("handleSatelliteProvisionStateChangedEvent: provisioned=" + provisioned);
 
@@ -2748,6 +2823,31 @@
         });
     }
 
+    private void handleEventSatelliteCapabilitiesChanged(SatelliteCapabilities capabilities) {
+        logd("handleEventSatelliteCapabilitiesChanged()");
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("handleEventSatelliteCapabilitiesChanged: oemEnabledSatelliteFlag is disabled");
+            return;
+        }
+
+        synchronized (mSatelliteCapabilitiesLock) {
+            mSatelliteCapabilities = capabilities;
+        }
+
+        List<ISatelliteCapabilitiesCallback> deadCallersList = new ArrayList<>();
+        mSatelliteCapabilitiesChangedListeners.values().forEach(listener -> {
+            try {
+                listener.onSatelliteCapabilitiesChanged(capabilities);
+            } catch (RemoteException e) {
+                logd("handleEventSatelliteCapabilitiesChanged RemoteException: " + e);
+                deadCallersList.add(listener);
+            }
+        });
+        deadCallersList.forEach(listener -> {
+            mSatelliteCapabilitiesChangedListeners.remove(listener.asBinder());
+        });
+    }
+
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected void setSettingsKeyForSatelliteMode(int val) {
         logd("setSettingsKeyForSatelliteMode val: " + val);
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
index dc6ea13..3d629db 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
@@ -91,6 +91,8 @@
             new RegistrantList();
     @NonNull private final RegistrantList mNtnSignalStrengthChangedRegistrants =
             new RegistrantList();
+    @NonNull private final RegistrantList mSatelliteCapabilitiesChangedRegistrants =
+            new RegistrantList();
 
     @NonNull private final ISatelliteListener mListener = new ISatelliteListener.Stub() {
         @Override
@@ -145,7 +147,14 @@
         public void onNtnSignalStrengthChanged(
                 android.telephony.satellite.stub.NtnSignalStrength ntnSignalStrength) {
             mNtnSignalStrengthChangedRegistrants.notifyResult(
-                    SatelliteServiceUtils.fromModemInterface(ntnSignalStrength));
+                    SatelliteServiceUtils.fromNtnSignalStrength(ntnSignalStrength));
+        }
+
+        @Override
+        public void onSatelliteCapabilitiesChanged(
+                android.telephony.satellite.stub.SatelliteCapabilities satelliteCapabilities) {
+            mSatelliteCapabilitiesChangedRegistrants.notifyResult(
+                    SatelliteServiceUtils.fromSatelliteCapabilities(satelliteCapabilities));
         }
     };
 
@@ -473,6 +482,27 @@
     }
 
     /**
+     * Registers for satellite capabilities changed.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSatelliteCapabilitiesChanged(
+            @NonNull Handler h, int what, @Nullable Object obj) {
+        mSatelliteCapabilitiesChangedRegistrants.add(h, what, obj);
+    }
+
+    /**
+     * Unregisters for satellite capabilities changed.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSatelliteCapabilitiesChanged(@NonNull Handler h) {
+        mSatelliteCapabilitiesChangedRegistrants.remove(h);
+    }
+
+    /**
      * Request to enable or disable the satellite service listening mode.
      * Listening mode allows the satellite service to listen for incoming pages.
      *
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
index b42c7d4..0e6f706 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
@@ -217,7 +217,7 @@
      * @param ntnSignalStrength The non-terrestrial signal strength from the satellite service.
      * @return The converted non-terrestrial signal strength for the framework.
      */
-    @Nullable public static NtnSignalStrength fromModemInterface(
+    @Nullable public static NtnSignalStrength fromNtnSignalStrength(
             android.telephony.satellite.stub.NtnSignalStrength ntnSignalStrength) {
         return new NtnSignalStrength(ntnSignalStrength.signalStrengthLevel);
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
index 0556ef5..add1d52 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
@@ -96,6 +96,7 @@
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.satellite.INtnSignalStrengthCallback;
+import android.telephony.satellite.ISatelliteCapabilitiesCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteStateCallback;
@@ -2396,6 +2397,114 @@
         assertFalse(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
     }
 
+    @Test
+    public void testRegisterForSatelliteCapabilitiesChangedWithFeatureFlagEnabled() {
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+
+        Semaphore semaphore = new Semaphore(0);
+        final SatelliteCapabilities[] satelliteCapabilities = new SatelliteCapabilities[1];
+        ISatelliteCapabilitiesCallback callback =
+                new ISatelliteCapabilitiesCallback.Stub() {
+                    @Override
+                    public void onSatelliteCapabilitiesChanged(SatelliteCapabilities capabilities) {
+                        logd("onSatelliteCapabilitiesChanged: " + capabilities);
+                        try {
+                            satelliteCapabilities[0] = capabilities;
+                            semaphore.release();
+                        } catch (Exception ex) {
+                            loge("onSatelliteCapabilitiesChanged: Got exception in releasing "
+                                    + "semaphore, ex=" + ex);
+                        }
+                    }
+                };
+
+        int errorCode = mSatelliteControllerUT.registerForSatelliteCapabilitiesChanged(SUB_ID,
+                callback);
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, errorCode);
+
+        setUpResponseForRequestIsSatelliteSupported(false,
+                SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        errorCode = mSatelliteControllerUT.registerForSatelliteCapabilitiesChanged(SUB_ID,
+                callback);
+        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, errorCode);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteProvisioned(true,
+                SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        errorCode = mSatelliteControllerUT.registerForSatelliteCapabilitiesChanged(SUB_ID,
+                callback);
+        assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
+        SatelliteCapabilities expectedCapabilities = mSatelliteCapabilities;
+        sendSatelliteCapabilitiesChangedEvent(expectedCapabilities, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteCapabilitiesChanged"));
+        assertTrue(expectedCapabilities.equals(satelliteCapabilities[0]));
+
+        expectedCapabilities = mEmptySatelliteCapabilities;
+        sendSatelliteCapabilitiesChangedEvent(expectedCapabilities, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteCapabilitiesChanged"));
+        assertTrue(expectedCapabilities.equals(satelliteCapabilities[0]));
+
+        mSatelliteControllerUT.unregisterForSatelliteCapabilitiesChanged(SUB_ID, callback);
+        expectedCapabilities = mSatelliteCapabilities;
+        sendSatelliteCapabilitiesChangedEvent(expectedCapabilities, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 0, "testRegisterForSatelliteCapabilitiesChanged"));
+    }
+
+    @Test
+    public void testRegisterForSatelliteCapabilitiesChangedWithFeatureFlagDisabled() {
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
+
+        Semaphore semaphore = new Semaphore(0);
+        final SatelliteCapabilities[] satelliteCapabilities = new SatelliteCapabilities[1];
+        ISatelliteCapabilitiesCallback callback =
+                new ISatelliteCapabilitiesCallback.Stub() {
+                    @Override
+                    public void onSatelliteCapabilitiesChanged(SatelliteCapabilities capabilities) {
+                        logd("onSatelliteCapabilitiesChanged: " + capabilities);
+                        try {
+                            satelliteCapabilities[0] = capabilities;
+                            semaphore.release();
+                        } catch (Exception ex) {
+                            loge("onSatelliteCapabilitiesChanged: Got exception in releasing "
+                                    + "semaphore, ex=" + ex);
+                        }
+                    }
+                };
+
+        int errorCode = mSatelliteControllerUT.registerForSatelliteCapabilitiesChanged(SUB_ID,
+                callback);
+        assertEquals(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, errorCode);
+
+        setUpResponseForRequestIsSatelliteSupported(false,
+                SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_NOT_SUPPORTED);
+        errorCode = mSatelliteControllerUT.registerForSatelliteCapabilitiesChanged(SUB_ID,
+                callback);
+        assertEquals(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, errorCode);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_NOT_SUPPORTED);
+        errorCode = mSatelliteControllerUT.registerForSatelliteCapabilitiesChanged(SUB_ID,
+                callback);
+        assertEquals(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, errorCode);
+
+        SatelliteCapabilities expectedCapabilities = mSatelliteCapabilities;
+        sendSatelliteCapabilitiesChangedEvent(expectedCapabilities, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 0, "testRegisterForSatelliteCapabilitiesChanged"));
+    }
+
     private void resetSatelliteControllerUTEnabledState() {
         logd("resetSatelliteControllerUTEnabledState");
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
@@ -2936,7 +3045,14 @@
 
     private void sendServiceStateChangedEvent() {
         mSatelliteControllerUT.obtainMessage(37 /* EVENT_SERVICE_STATE_CHANGED */).sendToTarget();
+    }
 
+    private void sendSatelliteCapabilitiesChangedEvent(SatelliteCapabilities capabilities,
+            Throwable exception) {
+        Message msg = mSatelliteControllerUT.obtainMessage(
+                38 /* EVENT_SATELLITE_CAPABILITIES_CHANGED */);
+        msg.obj = new AsyncResult(null, capabilities, exception);
+        msg.sendToTarget();
     }
 
     private void setRadioPower(boolean on) {