Merge "Add unit test for testFilterEmergencyNumbersByCategories"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 186ab50..c363811 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -219,6 +219,7 @@
     <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />
     <uses-permission android:name="android.permission.NETWORK_STATS_PROVIDER" />
     <uses-permission android:name="android.permission.MANAGE_SUBSCRIPTION_PLANS"/>
+    <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"/>
 
     <!-- Needed to listen to changes in projection state. -->
     <uses-permission android:name="android.permission.READ_PROJECTION_STATE"/>
@@ -240,6 +241,10 @@
                 android:readPermission="android.permission.READ_CONTACTS"
                 android:writePermission="android.permission.WRITE_CONTACTS" />
 
+        <provider android:name="com.android.ims.rcs.uce.eab.EabProvider"
+                android:authorities="eab"
+                android:exported="false"/>
+
         <!-- Dialer UI that only allows emergency calls -->
         <activity android:name="EmergencyDialer"
             android:label="@string/emergencyDialerIconLabel"
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index d63bc17..9082025 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -5373,6 +5373,36 @@
     }
 
     /**
+     * Clears any carrier ImsService overrides for the slot index specified that were previously
+     * set with {@link #setBoundImsServiceOverride(int, boolean, int[], String)}.
+     *
+     * This should only be used for testing.
+     *
+     * @param slotIndex the slot ID that the ImsService should bind for.
+     * @return true if clearing the carrier ImsService override succeeded or false if it did not.
+     */
+    @Override
+    public boolean clearCarrierImsServiceOverride(int slotIndex) {
+        int[] subIds = SubscriptionManager.getSubId(slotIndex);
+        TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+                "clearCarrierImsServiceOverride");
+        TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mApp,
+                (subIds != null ? subIds[0] : SubscriptionManager.INVALID_SUBSCRIPTION_ID),
+                "clearCarrierImsServiceOverride");
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            if (mImsResolver == null) {
+                // may happen if the device does not support IMS.
+                return false;
+            }
+            return mImsResolver.clearCarrierImsServiceConfiguration(slotIndex);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
      * Return the package name of the currently bound ImsService.
      *
      * @param slotId The slot that the ImsService is associated with.
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index c6a2f52..33d0721 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -64,8 +64,9 @@
     private static final String DATA_ENABLE = "enable";
     private static final String DATA_DISABLE = "disable";
 
-    private static final String IMS_SET_CARRIER_SERVICE = "set-ims-service";
-    private static final String IMS_GET_CARRIER_SERVICE = "get-ims-service";
+    private static final String IMS_SET_IMS_SERVICE = "set-ims-service";
+    private static final String IMS_GET_IMS_SERVICE = "get-ims-service";
+    private static final String IMS_CLEAR_SERVICE_OVERRIDE = "clear-ims-service-override";
     private static final String IMS_ENABLE = "enable";
     private static final String IMS_DISABLE = "disable";
     // Used to disable or enable processing of conference event package data from the network.
@@ -210,6 +211,11 @@
         pw.println("      -d: The ImsService defined as the device default ImsService.");
         pw.println("      -f: The feature type that the query will be requested for. If none is");
         pw.println("          specified, the returned package name will correspond to MMTEL.");
+        pw.println("  ims clear-ims-service-override [-s SLOT_ID]");
+        pw.println("    Clear all carrier ImsService overrides. This does not work for device ");
+        pw.println("    configuration overrides. Options are:");
+        pw.println("      -s: The SIM slot ID for the registered ImsService. If no option");
+        pw.println("          is specified, it will choose the default voice SIM slot.");
         pw.println("  ims enable [-s SLOT_ID]");
         pw.println("    enables IMS for the SIM slot specified, or for the default voice SIM slot");
         pw.println("    if none is specified.");
@@ -295,12 +301,15 @@
         }
 
         switch (arg) {
-            case IMS_SET_CARRIER_SERVICE: {
+            case IMS_SET_IMS_SERVICE: {
                 return handleImsSetServiceCommand();
             }
-            case IMS_GET_CARRIER_SERVICE: {
+            case IMS_GET_IMS_SERVICE: {
                 return handleImsGetServiceCommand();
             }
+            case IMS_CLEAR_SERVICE_OVERRIDE: {
+                return handleImsClearCarrierServiceCommand();
+            }
             case IMS_ENABLE: {
                 return handleEnableIms();
             }
@@ -547,6 +556,42 @@
         return 0;
     }
 
+    // ims clear-ims-service-override
+    private int handleImsClearCarrierServiceCommand() {
+        PrintWriter errPw = getErrPrintWriter();
+        int slotId = getDefaultSlot();
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-s": {
+                    try {
+                        slotId = Integer.parseInt(getNextArgRequired());
+                    } catch (NumberFormatException e) {
+                        errPw.println("ims set-ims-service requires an integer as a SLOT_ID.");
+                        return -1;
+                    }
+                    break;
+                }
+            }
+        }
+
+        try {
+            boolean result = mInterface.clearCarrierImsServiceOverride(slotId);
+            if (VDBG) {
+                Log.v(LOG_TAG, "ims clear-ims-service-override -s " + slotId
+                        + ", result=" + result);
+            }
+            getOutPrintWriter().println(result);
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "ims clear-ims-service-override -s " + slotId
+                    + ", error" + e.getMessage());
+            errPw.println("Exception: " + e.getMessage());
+            return -1;
+        }
+        return 0;
+    }
+
     // ims get-ims-service
     private int handleImsGetServiceCommand() {
         PrintWriter errPw = getErrPrintWriter();
diff --git a/src/com/android/services/telephony/rcs/DelegateStateTracker.java b/src/com/android/services/telephony/rcs/DelegateStateTracker.java
index e287813..1d8fa3b 100644
--- a/src/com/android/services/telephony/rcs/DelegateStateTracker.java
+++ b/src/com/android/services/telephony/rcs/DelegateStateTracker.java
@@ -126,14 +126,18 @@
      */
     @Override
     public void onRegistrationStateChanged(DelegateRegistrationState registrationState) {
-        mLastRegState = registrationState;
         if (mRegistrationStateOverride > DelegateRegistrationState.DEREGISTERED_REASON_UNKNOWN) {
             logi("onRegistrationStateChanged: overriding registered state to "
                     + mRegistrationStateOverride);
             registrationState = overrideRegistrationForDelegateChange(mRegistrationStateOverride,
                     registrationState);
         }
-        logi("onRegistrationStateChanged: sending reg state" + registrationState);
+        if (registrationState.equals(mLastRegState)) {
+            logi("onRegistrationStateChanged: skipping notification, state is the same.");
+            return;
+        }
+        mLastRegState = registrationState;
+        logi("onRegistrationStateChanged: sending reg state " + registrationState);
         try {
             mAppStateCallback.onFeatureTagStatusChanged(registrationState, mDelegateDeniedTags);
         } catch (RemoteException e) {
@@ -168,10 +172,6 @@
             int registerOverrideReason, DelegateRegistrationState state) {
         Set<String> registeredFeatures = state.getRegisteredFeatureTags();
         DelegateRegistrationState.Builder overriddenState = new DelegateRegistrationState.Builder();
-        // Override REGISTERED only
-        for (String ft : registeredFeatures) {
-            overriddenState.addDeregisteringFeatureTag(ft, registerOverrideReason);
-        }
         // keep other deregistering/deregistered tags the same.
         for (FeatureTagState dereging : state.getDeregisteringFeatureTags()) {
             overriddenState.addDeregisteringFeatureTag(dereging.getFeatureTag(),
@@ -181,6 +181,10 @@
             overriddenState.addDeregisteredFeatureTag(dereged.getFeatureTag(),
                     dereged.getState());
         }
+        // Override REGISTERED only
+        for (String ft : registeredFeatures) {
+            overriddenState.addDeregisteringFeatureTag(ft, registerOverrideReason);
+        }
         return overriddenState.build();
     }
 
diff --git a/src/com/android/services/telephony/rcs/MessageTransportStateTracker.java b/src/com/android/services/telephony/rcs/MessageTransportStateTracker.java
index 531ed7d..0691ae5 100644
--- a/src/com/android/services/telephony/rcs/MessageTransportStateTracker.java
+++ b/src/com/android/services/telephony/rcs/MessageTransportStateTracker.java
@@ -159,7 +159,7 @@
          * and sent over the network.
          */
         @Override
-        public void sendMessage(SipMessage sipMessage, int configVersion) {
+        public void sendMessage(SipMessage sipMessage, long configVersion) {
             long token = Binder.clearCallingIdentity();
             try {
                 mExecutor.execute(() -> {
diff --git a/src/com/android/services/telephony/rcs/SipDelegateBinderConnection.java b/src/com/android/services/telephony/rcs/SipDelegateBinderConnection.java
index 8ae1936..1a77f2b 100644
--- a/src/com/android/services/telephony/rcs/SipDelegateBinderConnection.java
+++ b/src/com/android/services/telephony/rcs/SipDelegateBinderConnection.java
@@ -17,11 +17,13 @@
 package com.android.services.telephony.rcs;
 
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.telephony.ims.DelegateRegistrationState;
 import android.telephony.ims.DelegateRequest;
 import android.telephony.ims.FeatureTagState;
 import android.telephony.ims.SipDelegateImsConfiguration;
+import android.telephony.ims.SipDelegateManager;
 import android.telephony.ims.aidl.ISipDelegate;
 import android.telephony.ims.aidl.ISipDelegateMessageCallback;
 import android.telephony.ims.aidl.ISipDelegateStateCallback;
@@ -31,7 +33,9 @@
 import android.util.Log;
 
 import java.io.PrintWriter;
+import java.util.Collections;
 import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.BiConsumer;
@@ -43,7 +47,8 @@
  * New instances of this class will be created and destroyed new {@link SipDelegate}s are created
  * and destroyed by the {@link SipDelegateController}.
  */
-public class SipDelegateBinderConnection implements DelegateBinderStateManager {
+public class SipDelegateBinderConnection implements DelegateBinderStateManager,
+        IBinder.DeathRecipient {
     private static final String LOG_TAG = "BinderConn";
 
     protected final int mSubId;
@@ -149,6 +154,7 @@
         try {
             mSipTransport.createSipDelegate(mSubId, mRequestedConfig, mSipDelegateStateCallback,
                     cb);
+            mSipTransport.asBinder().linkToDeath(this, 0);
         } catch (RemoteException e) {
             logw("create called on unreachable SipTransport:" + e);
             return false;
@@ -171,6 +177,11 @@
             logw("destroy called on unreachable SipTransport:" + e);
             mExecutor.execute(() -> notifySipDelegateDestroyed(reason));
         }
+        try {
+            mSipTransport.asBinder().unlinkToDeath(this, 0);
+        } catch (NoSuchElementException e) {
+            logw("unlinkToDeath called on already unlinked binder" + e);
+        }
     }
 
     private void notifySipDelegateCreated(ISipDelegate delegate,
@@ -211,4 +222,15 @@
         Log.w(SipTransportController.LOG_TAG, LOG_TAG + "[" + mSubId + "] " + log);
         mLocalLog.log("[W] " + log);
     }
+
+    @Override
+    public void binderDied() {
+        mExecutor.execute(() -> {
+            logw("binderDied!");
+            // Unblock any pending create/destroy operations.
+            // SipTransportController will handle the overall destruction/teardown.
+            notifySipDelegateCreated(null, Collections.emptyList());
+            notifySipDelegateDestroyed(SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD);
+        });
+    }
 }
diff --git a/src/com/android/services/telephony/rcs/SipDelegateController.java b/src/com/android/services/telephony/rcs/SipDelegateController.java
index ee9d4a7..ed50778 100644
--- a/src/com/android/services/telephony/rcs/SipDelegateController.java
+++ b/src/com/android/services/telephony/rcs/SipDelegateController.java
@@ -27,7 +27,6 @@
 import android.telephony.ims.aidl.ISipTransport;
 import android.telephony.ims.stub.DelegateConnectionStateCallback;
 import android.telephony.ims.stub.SipDelegate;
-import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.Log;
 import android.util.Pair;
@@ -76,7 +75,7 @@
     private final LocalLog mLocalLog = new LocalLog(SipTransportController.LOG_SIZE);
 
     private DelegateBinderStateManager mBinderConnection;
-    private Set<String> mTrackedFeatureTags = new ArraySet<>();
+    private Set<String> mTrackedFeatureTags;
 
     public SipDelegateController(int subId, DelegateRequest initialRequest, String packageName,
             ISipTransport sipTransportImpl, ScheduledExecutorService executorService,
@@ -154,7 +153,11 @@
         // May need to implement special case handling where SipDelegate denies all in supportedSet,
         // however that should be a very rare case. For now, if that happens, just keep the
         // SipDelegate bound.
-        return pendingCreate.thenApplyAsync((resultPair) -> {
+        // use thenApply here because we need this to happen on the same thread that it was called
+        // on in order to ensure ordering of onCreated being called, followed by registration
+        // state changed. If not, this is subject to race conditions where registered is queued
+        // before the async processing of this future.
+        return pendingCreate.thenApply((resultPair) -> {
             if (resultPair == null) {
                 logw("create: resultPair returned null");
                 return false;
@@ -164,7 +167,7 @@
             mMessageTransportStateTracker.openTransport(resultPair.first, resultPair.second);
             mDelegateStateTracker.sipDelegateConnected(resultPair.second);
             return true;
-        }, mExecutorService);
+        });
     }
 
     /**
@@ -196,7 +199,7 @@
             Set<FeatureTagState> deniedSet) {
         logi("Received feature tag set change, old: [" + mTrackedFeatureTags + "], new: "
                 + newSupportedSet + ",denied: [" + deniedSet + "]");
-        if (mTrackedFeatureTags.equals(newSupportedSet)) {
+        if (mTrackedFeatureTags != null && mTrackedFeatureTags.equals(newSupportedSet)) {
             logi("changeSupportedFeatureTags: no change, returning");
             return CompletableFuture.completedFuture(true);
         }
diff --git a/src/com/android/services/telephony/rcs/SipTransportController.java b/src/com/android/services/telephony/rcs/SipTransportController.java
index dd06cc1..5d817ba 100644
--- a/src/com/android/services/telephony/rcs/SipTransportController.java
+++ b/src/com/android/services/telephony/rcs/SipTransportController.java
@@ -673,12 +673,18 @@
 
 
     private void updateRoleCache() {
-        // Only one app can fulfill the SMS role.
-        String newSmsRolePackageName = mRoleManagerAdapter.getRoleHolders(RoleManager.ROLE_SMS)
-                .stream().findFirst().orElse("");
+        String newSmsRolePackageName = "";
+        try {
+            // Only one app can fulfill the SMS role.
+            newSmsRolePackageName = mRoleManagerAdapter.getRoleHolders(RoleManager.ROLE_SMS)
+                    .stream().findFirst().orElse("");
+        } catch (Exception e) {
+            logi("updateRoleCache: exception=" + e);
+        }
 
+        logi("updateRoleCache: new packageName=" + newSmsRolePackageName);
         if (TextUtils.equals(mCachedSmsRolePackageName, newSmsRolePackageName)) {
-            logi("onRoleHoldersChanged, skipping, role did not change");
+            logi("updateRoleCache, skipping, role did not change");
             return;
         }
         mCachedSmsRolePackageName = newSmsRolePackageName;
@@ -793,19 +799,24 @@
             unregisterListeners();
             scheduleDestroyDelegates(SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD);
         } else {
+            logi("onRcsManagerChanged: registering listeners/updating role cache...");
             registerListeners();
             updateRoleCache();
         }
     }
 
     private void registerListeners() {
-        mRoleManagerAdapter.addOnRoleHoldersChangedListenerAsUser(mExecutorService, this,
-                UserHandle.SYSTEM);
+        try {
+            mRoleManagerAdapter.addOnRoleHoldersChangedListenerAsUser(mExecutorService, this,
+                    UserHandle.SYSTEM);
+        } catch (Exception e) {
+            logi("registerListeners: exception=" + e);
+        }
     }
 
     private void unregisterListeners() {
-        mRoleManagerAdapter.removeOnRoleHoldersChangedListenerAsUser(this, UserHandle.SYSTEM);
         mCachedSmsRolePackageName = "";
+        mRoleManagerAdapter.removeOnRoleHoldersChangedListenerAsUser(this, UserHandle.SYSTEM);
     }
 
     /**
diff --git a/tests/src/com/android/services/telephony/rcs/MessageTransportStateTrackerTest.java b/tests/src/com/android/services/telephony/rcs/MessageTransportStateTrackerTest.java
index 7ba4252..5e05085 100644
--- a/tests/src/com/android/services/telephony/rcs/MessageTransportStateTrackerTest.java
+++ b/tests/src/com/android/services/telephony/rcs/MessageTransportStateTrackerTest.java
@@ -18,6 +18,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
@@ -96,7 +97,7 @@
         tracker.getDelegateConnection().sendMessage(TEST_MESSAGE, 1 /*version*/);
         verify(mISipDelegate).sendMessage(TEST_MESSAGE, 1 /*version*/);
 
-        doThrow(new RemoteException()).when(mISipDelegate).sendMessage(any(), anyInt());
+        doThrow(new RemoteException()).when(mISipDelegate).sendMessage(any(), anyLong());
         tracker.getDelegateConnection().sendMessage(TEST_MESSAGE, 1 /*version*/);
         verify(mDelegateMessageCallback).onMessageSendFailure(any(),
                 eq(SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD));
diff --git a/tests/src/com/android/services/telephony/rcs/SipDelegateBinderConnectionTest.java b/tests/src/com/android/services/telephony/rcs/SipDelegateBinderConnectionTest.java
index b95ae90..fa439dc 100644
--- a/tests/src/com/android/services/telephony/rcs/SipDelegateBinderConnectionTest.java
+++ b/tests/src/com/android/services/telephony/rcs/SipDelegateBinderConnectionTest.java
@@ -21,9 +21,11 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.verify;
 
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.telephony.ims.DelegateRegistrationState;
 import android.telephony.ims.DelegateRequest;
@@ -59,6 +61,7 @@
 
     @Mock private ISipDelegate mMockDelegate;
     @Mock private ISipTransport mMockTransport;
+    @Mock private IBinder mTransportBinder;
     @Mock private ISipDelegateMessageCallback mMessageCallback;
     @Mock private DelegateBinderStateManager.StateCallback mMockStateCallback;
     @Mock private BiConsumer<ISipDelegate, Set<FeatureTagState>> mMockCreatedCallback;
@@ -69,6 +72,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
+        doReturn(mTransportBinder).when(mMockTransport).asBinder();
         mStateCallbackList = new ArrayList<>(1);
         mStateCallbackList.add(mMockStateCallback);
     }