Merge "(ImsService API changes for Better IMS Threading) Add new parameter for ImsConfigStub()"
diff --git a/src/java/com/android/ims/ImsCall.java b/src/java/com/android/ims/ImsCall.java
index 7af0b71..a5813d8 100755
--- a/src/java/com/android/ims/ImsCall.java
+++ b/src/java/com/android/ims/ImsCall.java
@@ -1123,7 +1123,7 @@
              mSession = session;
 
              try {
-                 mSession.setListener(createCallSessionListener());
+                 mSession.setListener(createCallSessionListener(), mContext.getMainExecutor());
              } catch (Throwable t) {
                  loge("attachSession :: ", t);
                  throwImsException(t, 0);
@@ -1147,7 +1147,7 @@
             mSession = session;
 
             try {
-                session.setListener(createCallSessionListener());
+                session.setListener(createCallSessionListener(), mContext.getMainExecutor());
                 session.start(callee, mCallProfile);
             } catch (Throwable t) {
                 loge("start(1) :: ", t);
@@ -1173,7 +1173,7 @@
             mIsConferenceHost = true;
 
             try {
-                session.setListener(createCallSessionListener());
+                session.setListener(createCallSessionListener(), mContext.getMainExecutor());
                 session.start(participants, mCallProfile);
             } catch (Throwable t) {
                 loge("start(n) :: ", t);
@@ -2097,9 +2097,9 @@
 
     private void setTransientSessionAsPrimary(ImsCallSession transientSession) {
         synchronized (ImsCall.this) {
-            mSession.setListener(null);
+            mSession.setListener(null, null);
             mSession = transientSession;
-            mSession.setListener(createCallSessionListener());
+            mSession.setListener(createCallSessionListener(), mContext.getMainExecutor());
         }
     }
 
@@ -2214,7 +2214,7 @@
 
                 // Clear the listener for this transient session, we'll create a new listener
                 // when it is attached to the final ImsCall that it should live on.
-                transientConferenceSession.setListener(null);
+                transientConferenceSession.setListener(null, null);
 
                 // Determine which call the transient session should be moved to.  If the current
                 // call session is still alive and the merge peer's session is not, we have a
@@ -2409,7 +2409,7 @@
 
             // Try to clean up the transient session if it exists.
             if (mTransientConferenceSession != null) {
-                mTransientConferenceSession.setListener(null);
+                mTransientConferenceSession.setListener(null, null);
                 mTransientConferenceSession = null;
             }
 
diff --git a/src/java/com/android/ims/ImsEcbmStateListener.java b/src/java/com/android/ims/ImsEcbmStateListener.java
index ea662ee..814ba5f 100644
--- a/src/java/com/android/ims/ImsEcbmStateListener.java
+++ b/src/java/com/android/ims/ImsEcbmStateListener.java
@@ -29,6 +29,8 @@
 
 package com.android.ims;
 
+import java.util.concurrent.Executor;
+
 /**
  * Listener for receiving notifications about changes to the IMS connection.
  * It provides a state of IMS registration between UE and IMS network, the service
@@ -36,18 +38,42 @@
  *
  * @hide
  */
-public class ImsEcbmStateListener {
+public abstract class ImsEcbmStateListener {
+    protected Executor mListenerExecutor = Runnable::run;
+    /**
+     * constructor.
+     *
+     * @param executor the executor that will execute callbacks.
+     */
+    public ImsEcbmStateListener(Executor executor) {
+        if (executor != null)
+            mListenerExecutor = executor;
+    }
     /**
      * Called when the device enters Emergency Callback Mode
      */
-    public void onECBMEntered() {
-        // no-op
+    public final void onECBMEntered() {
+        onECBMEntered(mListenerExecutor);
     }
 
     /**
+     * Called when the device enters Emergency Callback Mode
+     *
+     * @param executor the executor that will execute callbacks.
+     */
+    public abstract void onECBMEntered(Executor executor);
+
+    /**
      * Called when the device exits Emergency Callback Mode
      */
-    public void onECBMExited() {
-        // no-op
+    public final void onECBMExited() {
+        onECBMExited(mListenerExecutor);
     }
+
+    /**
+     * Called when the device exits Emergency Callback Mode
+     *
+     * @param executor the executor that will execute callbacks.
+     */
+    public abstract void onECBMExited(Executor executor);
 }
diff --git a/src/java/com/android/ims/ImsExternalCallStateListener.java b/src/java/com/android/ims/ImsExternalCallStateListener.java
index aae4c9b..880710a 100644
--- a/src/java/com/android/ims/ImsExternalCallStateListener.java
+++ b/src/java/com/android/ims/ImsExternalCallStateListener.java
@@ -19,6 +19,7 @@
 import android.telephony.ims.ImsExternalCallState;
 
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * Listener for receiving notifications about {@link ImsExternalCallState} information received
@@ -26,13 +27,32 @@
  *
  * @hide
  */
-public class ImsExternalCallStateListener {
+public abstract class ImsExternalCallStateListener {
+    protected Executor mListenerExecutor = Runnable::run;
+    /**
+     * constructor.
+     *
+     * @param executor the executor that will execute callbacks.
+     */
+    public ImsExternalCallStateListener(Executor executor) {
+        if (executor != null)
+            mListenerExecutor = executor;
+    }
     /**
      * Notifies client when Dialog Event Package update is received
      *
      * @param externalCallState the external call state.
      */
-    public void onImsExternalCallStateUpdate(List<ImsExternalCallState> externalCallState) {
-        // no-op
+    public final void onImsExternalCallStateUpdate(List<ImsExternalCallState> externalCallState) {
+        onImsExternalCallStateUpdate(externalCallState, mListenerExecutor);
     }
+    /**
+     * Notifies client when Dialog Event Package update is received
+     *
+     * @param externalCallState the external call state.
+     *
+     * @param executor the executor that will execute callbacks.
+     */
+    public abstract void onImsExternalCallStateUpdate(
+        List<ImsExternalCallState> externalCallState, Executor executor);
 }
diff --git a/src/java/com/android/ims/ImsUt.java b/src/java/com/android/ims/ImsUt.java
index 61a1111..d02ffaa 100644
--- a/src/java/com/android/ims/ImsUt.java
+++ b/src/java/com/android/ims/ImsUt.java
@@ -33,9 +33,11 @@
 import com.android.ims.internal.IImsUtListener;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.telephony.Rlog;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 /**
  * Provides APIs for the supplementary service settings using IMS (Ut interface).
@@ -85,9 +87,13 @@
     private HashMap<Integer, Message> mPendingCmds =
             new HashMap<Integer, Message>();
     private Registrant mSsIndicationRegistrant;
+    private Executor mExecutor = Runnable::run;
 
-    public ImsUt(IImsUt iUt) {
+    public ImsUt(IImsUt iUt, Executor executor) {
         miUt = iUt;
+        if (executor != null) {
+            mExecutor = executor;
+        }
 
         if (miUt != null) {
             try {
@@ -664,22 +670,26 @@
          */
         @Override
         public void utConfigurationUpdated(IImsUt ut, int id) {
-            Integer key = Integer.valueOf(id);
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                Integer key = Integer.valueOf(id);
 
-            synchronized(mLockObj) {
-                sendSuccessReport(mPendingCmds.get(key));
-                mPendingCmds.remove(key);
-            }
+                synchronized(mLockObj) {
+                    sendSuccessReport(mPendingCmds.get(key));
+                    mPendingCmds.remove(key);
+                }
+            }, mExecutor);
         }
 
         @Override
         public void utConfigurationUpdateFailed(IImsUt ut, int id, ImsReasonInfo error) {
-            Integer key = Integer.valueOf(id);
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                Integer key = Integer.valueOf(id);
 
-            synchronized(mLockObj) {
-                sendFailureReport(mPendingCmds.get(key), error);
-                mPendingCmds.remove(key);
-            }
+                synchronized(mLockObj) {
+                    sendFailureReport(mPendingCmds.get(key), error);
+                    mPendingCmds.remove(key);
+                }
+            }, mExecutor);
         }
 
         /**
@@ -710,20 +720,24 @@
          */
         @Override
         public void lineIdentificationSupplementaryServiceResponse(int id, ImsSsInfo config) {
-            synchronized(mLockObj) {
-                sendSuccessReport(mPendingCmds.get(id), config);
-                mPendingCmds.remove(id);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                synchronized(mLockObj) {
+                    sendSuccessReport(mPendingCmds.get(id), config);
+                    mPendingCmds.remove(id);
+                }
+            }, mExecutor);
         }
 
         @Override
         public void utConfigurationQueryFailed(IImsUt ut, int id, ImsReasonInfo error) {
-            Integer key = Integer.valueOf(id);
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                Integer key = Integer.valueOf(id);
 
-            synchronized(mLockObj) {
-                sendFailureReport(mPendingCmds.get(key), error);
-                mPendingCmds.remove(key);
-            }
+                synchronized(mLockObj) {
+                    sendFailureReport(mPendingCmds.get(key), error);
+                    mPendingCmds.remove(key);
+                }
+            }, mExecutor);
         }
 
         /**
@@ -732,12 +746,14 @@
         @Override
         public void utConfigurationCallBarringQueried(IImsUt ut,
                 int id, ImsSsInfo[] cbInfo) {
-            Integer key = Integer.valueOf(id);
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                Integer key = Integer.valueOf(id);
 
-            synchronized(mLockObj) {
-                sendSuccessReport(mPendingCmds.get(key), cbInfo);
-                mPendingCmds.remove(key);
-            }
+                synchronized(mLockObj) {
+                    sendSuccessReport(mPendingCmds.get(key), cbInfo);
+                    mPendingCmds.remove(key);
+                }
+            }, mExecutor);
         }
 
         /**
@@ -746,12 +762,14 @@
         @Override
         public void utConfigurationCallForwardQueried(IImsUt ut,
                 int id, ImsCallForwardInfo[] cfInfo) {
-            Integer key = Integer.valueOf(id);
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                Integer key = Integer.valueOf(id);
 
-            synchronized(mLockObj) {
-                sendSuccessReport(mPendingCmds.get(key), cfInfo);
-                mPendingCmds.remove(key);
-            }
+                synchronized(mLockObj) {
+                    sendSuccessReport(mPendingCmds.get(key), cfInfo);
+                    mPendingCmds.remove(key);
+                }
+            }, mExecutor);
         }
 
         /**
@@ -760,12 +778,14 @@
         @Override
         public void utConfigurationCallWaitingQueried(IImsUt ut,
                 int id, ImsSsInfo[] cwInfo) {
-            Integer key = Integer.valueOf(id);
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                Integer key = Integer.valueOf(id);
 
-            synchronized(mLockObj) {
-                sendSuccessReport(mPendingCmds.get(key), cwInfo);
-                mPendingCmds.remove(key);
-            }
+                synchronized(mLockObj) {
+                    sendSuccessReport(mPendingCmds.get(key), cwInfo);
+                    mPendingCmds.remove(key);
+                }
+            }, mExecutor);
         }
 
         /**
@@ -773,9 +793,11 @@
          */
         @Override
         public void onSupplementaryServiceIndication(ImsSsData ssData) {
-            if (mSsIndicationRegistrant != null) {
-                mSsIndicationRegistrant.notifyResult(ssData);
-            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mSsIndicationRegistrant != null) {
+                    mSsIndicationRegistrant.notifyResult(ssData);
+                }
+            }, mExecutor);
         }
     }
 }
diff --git a/src/java/com/android/ims/MmTelFeatureConnection.java b/src/java/com/android/ims/MmTelFeatureConnection.java
index 7201313..e192431 100644
--- a/src/java/com/android/ims/MmTelFeatureConnection.java
+++ b/src/java/com/android/ims/MmTelFeatureConnection.java
@@ -436,7 +436,7 @@
             // This will internally set up a listener on the ImsUtImplBase interface, and there is
             // a limitation that there can only be one. If multiple connections try to create this
             // UT interface, it will throw an IllegalStateException.
-            mUt = (imsUt != null) ? new ImsUt(imsUt) : null;
+            mUt = (imsUt != null) ? new ImsUt(imsUt, mContext.getMainExecutor()) : null;
             return mUt;
         }
     }
diff --git a/src/java/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityListener.java b/src/java/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityListener.java
index 872d35d..d2177ea 100644
--- a/src/java/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityListener.java
+++ b/src/java/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityListener.java
@@ -107,6 +107,7 @@
         private static final int EVENT_REGISTER_IMS_CONTENT_CHANGE = 1;
         private static final int EVENT_UNREGISTER_IMS_CHANGE = 2;
         private static final int EVENT_REQUEST_PUBLISH = 3;
+        private static final int EVENT_IMS_UNREGISTERED = 4;
 
         DeviceCapabilityHandler(Looper looper) {
             super(looper);
@@ -127,6 +128,9 @@
                     int triggerType = msg.arg1;
                     mCallback.requestPublishFromInternal(triggerType);
                     break;
+                case EVENT_IMS_UNREGISTERED:
+                    mCallback.updateImsUnregistered();
+                    break;
             }
         }
 
@@ -155,6 +159,14 @@
             message.arg1 = type;
             sendMessageDelayed(message, TRIGGER_PUBLISH_REQUEST_DELAY_MS);
         }
+
+        public void sendImsUnregisteredMessage() {
+            logd("sendImsUnregisteredMessage");
+            // Remove the existing message and resend a new message.
+            removeMessages(EVENT_IMS_UNREGISTERED);
+            Message msg = obtainMessage(EVENT_IMS_UNREGISTERED);
+            sendMessageDelayed(msg, TRIGGER_PUBLISH_REQUEST_DELAY_MS);
+        }
     }
 
     private final int mSubId;
@@ -601,8 +613,11 @@
         mCapabilityInfo.updateImsMmtelUnregistered();
         // When the MMTEL is unregistered, the mmtel associated uri should be cleared.
         handleMmTelSubscriberAssociatedUriChanged(null, false);
-        mHandler.sendTriggeringPublishMessage(
-                PublishController.PUBLISH_TRIGGER_MMTEL_UNREGISTERED);
+
+        // If the RCS is already unregistered, it informs that the IMS is unregistered.
+        if (mCapabilityInfo.isImsRegistered() == false) {
+            mHandler.sendImsUnregisteredMessage();
+        }
     }
 
     /*
@@ -648,10 +663,9 @@
         boolean hasChanged = mCapabilityInfo.updateImsRcsUnregistered();
         // When the RCS is unregistered, the rcs associated uri should be cleared.
         handleRcsSubscriberAssociatedUriChanged(null, false);
-        // Trigger publish if the state has changed.
-        if (hasChanged) {
-            mHandler.sendTriggeringPublishMessage(
-                    PublishController.PUBLISH_TRIGGER_RCS_UNREGISTERED);
+        // If the MMTEL is already unregistered, it informs that the IMS is unregistered.
+        if (mCapabilityInfo.isImsRegistered() == false) {
+            mHandler.sendImsUnregisteredMessage();
         }
     }
 
diff --git a/src/java/com/android/ims/rcs/uce/presence/publish/PublishController.java b/src/java/com/android/ims/rcs/uce/presence/publish/PublishController.java
index 472c6a2..b20f5ce 100644
--- a/src/java/com/android/ims/rcs/uce/presence/publish/PublishController.java
+++ b/src/java/com/android/ims/rcs/uce/presence/publish/PublishController.java
@@ -84,6 +84,9 @@
     /** The Carrier Config for the subscription has Changed **/
     int PUBLISH_TRIGGER_CARRIER_CONFIG_CHANGED = 16;
 
+    /** MMTEL and RCS are unregistered. **/
+    int PUBLISH_TRIGGER_MMTEL_RCS_UNREGISTERED = 17;
+
     @IntDef(value = {
             PUBLISH_TRIGGER_SERVICE,
             PUBLISH_TRIGGER_RETRY,
@@ -100,7 +103,8 @@
             PUBLISH_TRIGGER_RCS_URI_CHANGE,
             PUBLISH_TRIGGER_PROVISIONING_CHANGE,
             PUBLISH_TRIGGER_OVERRIDE_CAPS,
-            PUBLISH_TRIGGER_CARRIER_CONFIG_CHANGED
+            PUBLISH_TRIGGER_CARRIER_CONFIG_CHANGED,
+            PUBLISH_TRIGGER_MMTEL_RCS_UNREGISTERED
     }, prefix="PUBLISH_TRIGGER_")
     @Retention(RetentionPolicy.SOURCE)
     @interface PublishTriggerType {}
@@ -154,6 +158,11 @@
          * Sent the publish request to ImsService.
          */
         void notifyPendingPublishRequest();
+
+        /**
+         * Update the Ims unregistered. This api will be called if the IMS unregistered.
+         */
+        void updateImsUnregistered();
     }
 
     /**
diff --git a/src/java/com/android/ims/rcs/uce/presence/publish/PublishControllerImpl.java b/src/java/com/android/ims/rcs/uce/presence/publish/PublishControllerImpl.java
index 0aa9684..034d9ac 100644
--- a/src/java/com/android/ims/rcs/uce/presence/publish/PublishControllerImpl.java
+++ b/src/java/com/android/ims/rcs/uce/presence/publish/PublishControllerImpl.java
@@ -434,6 +434,12 @@
                     logd("notifyPendingPublishRequest");
                     mPublishHandler.sendPublishSentMessage();
                 }
+
+                @Override
+                public void updateImsUnregistered() {
+                    logd("updateImsUnregistered");
+                    mPublishHandler.sendImsUnregisteredMessage();
+                }
             };
 
     /**
@@ -461,6 +467,7 @@
         private static final int MSG_UNPUBLISHED = 13;
         private static final int MSG_PUBLISH_SENT = 14;
         private static final int MSG_PUBLISH_UPDATED = 15;
+        private static final int MSG_IMS_UNREGISTERED = 16;
 
         private final WeakReference<PublishControllerImpl> mPublishControllerRef;
 
@@ -569,6 +576,12 @@
                             reasonHeaderCause, reasonHeaderText);
                     break;
                 }
+
+                case MSG_IMS_UNREGISTERED:
+                    publishCtrl.handleUnpublishedMessage(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED,
+                            Instant.now());
+                    break;
+
                 default:
                     publishCtrl.logd("invalid message: " + message.what);
                     break;
@@ -812,6 +825,15 @@
             removeMessages(MSG_RESET_DEVICE_STATE);
         }
 
+        public void sendImsUnregisteredMessage() {
+            PublishControllerImpl publishCtrl = mPublishControllerRef.get();
+            if (publishCtrl == null) return;
+            if (publishCtrl.mIsDestroyedFlag) return;
+            Message message = obtainMessage();
+            message.what = MSG_IMS_UNREGISTERED;
+            sendMessage(message);
+        }
+
         private static Map<Integer, String> EVENT_DESCRIPTION = new HashMap<>();
         static {
             EVENT_DESCRIPTION.put(MSG_RCS_CONNECTED, "RCS_CONNECTED");
@@ -829,6 +851,7 @@
             EVENT_DESCRIPTION.put(MSG_UNPUBLISHED, "MSG_UNPUBLISHED");
             EVENT_DESCRIPTION.put(MSG_PUBLISH_SENT, "MSG_PUBLISH_SENT");
             EVENT_DESCRIPTION.put(MSG_PUBLISH_UPDATED, "MSG_PUBLISH_UPDATED");
+            EVENT_DESCRIPTION.put(MSG_IMS_UNREGISTERED, "MSG_IMS_UNREGISTERED");
         }
     }
 
diff --git a/src/java/com/android/ims/rcs/uce/presence/publish/PublishRequestResponse.java b/src/java/com/android/ims/rcs/uce/presence/publish/PublishRequestResponse.java
index 9391419..b651cfb 100644
--- a/src/java/com/android/ims/rcs/uce/presence/publish/PublishRequestResponse.java
+++ b/src/java/com/android/ims/rcs/uce/presence/publish/PublishRequestResponse.java
@@ -199,6 +199,13 @@
     }
 
     private void onNetworkResponse(int sipCode, String reason) {
+        // When we send a request to PUBLISH and there is no change to the UCE capabilities, we
+        // expected onCommandError() with COMMAND_CODE_NO_CHANGE.
+        // But some of the vendor will instead send SIP code 999.
+        if (sipCode == 999) {
+            onCommandError(RcsCapabilityExchangeImplBase.COMMAND_CODE_NO_CHANGE);
+            return;
+        }
         mResponseTimestamp = Instant.now();
         mNetworkRespSipCode = Optional.of(sipCode);
         mReasonPhrase = Optional.ofNullable(reason);
@@ -214,6 +221,13 @@
 
     private void onNetworkResponse(int sipCode, String reasonPhrase, int reasonHeaderCause,
             String reasonHeaderText) {
+        // When we send a request to PUBLISH and there is no change to the UCE capabilities, we
+        // expected onCommandError() with COMMAND_CODE_NO_CHANGE.
+        // But some of the vendor will instead send SIP code 999.
+        if (sipCode == 999) {
+            onCommandError(RcsCapabilityExchangeImplBase.COMMAND_CODE_NO_CHANGE);
+            return;
+        }
         mResponseTimestamp = Instant.now();
         mNetworkRespSipCode = Optional.of(sipCode);
         mReasonPhrase = Optional.ofNullable(reasonPhrase);
diff --git a/tests/src/com/android/ims/ImsUtTest.java b/tests/src/com/android/ims/ImsUtTest.java
index 634b4d9..71722ce 100644
--- a/tests/src/com/android/ims/ImsUtTest.java
+++ b/tests/src/com/android/ims/ImsUtTest.java
@@ -97,7 +97,7 @@
     public void testClirConversionCompat() throws Exception {
         ArgumentCaptor<ImsUt.IImsUtListenerProxy> captor =
                 ArgumentCaptor.forClass(ImsUt.IImsUtListenerProxy.class);
-        ImsUt mImsUt = new ImsUt(mImsUtBinder);
+        ImsUt mImsUt = new ImsUt(mImsUtBinder, Runnable::run);
         verify(mImsUtBinder).setListener(captor.capture());
         ImsUt.IImsUtListenerProxy proxy = captor.getValue();
         assertNotNull(proxy);
@@ -125,7 +125,7 @@
     public void testClipConversionCompat() throws Exception {
         ArgumentCaptor<ImsUt.IImsUtListenerProxy> captor =
                 ArgumentCaptor.forClass(ImsUt.IImsUtListenerProxy.class);
-        ImsUt mImsUt = new ImsUt(mImsUtBinder);
+        ImsUt mImsUt = new ImsUt(mImsUtBinder, Runnable::run);
         verify(mImsUtBinder).setListener(captor.capture());
         ImsUt.IImsUtListenerProxy proxy = captor.getValue();
         assertNotNull(proxy);
diff --git a/tests/src/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityListenerTest.java b/tests/src/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityListenerTest.java
index 1959dd0..2d170ab 100644
--- a/tests/src/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityListenerTest.java
+++ b/tests/src/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityListenerTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.refEq;
 import static org.mockito.Mockito.verify;
 
@@ -176,24 +177,6 @@
 
     @Test
     @SmallTest
-    public void testMmTelUnregistration() throws Exception {
-        DeviceCapabilityListener deviceCapListener = createDeviceCapabilityListener();
-        deviceCapListener.setImsCallbackRegistered(true);
-        RegistrationCallback registrationCallback = deviceCapListener.mMmtelRegistrationCallback;
-
-        ImsReasonInfo info = new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED, -1, "");
-        registrationCallback.onUnregistered(info);
-
-        Handler handler = deviceCapListener.getHandler();
-        waitForHandlerActionDelayed(handler, HANDLER_WAIT_TIMEOUT_MS, HANDLER_SENT_DELAY_MS);
-
-        verify(mDeviceCapability).updateImsMmtelUnregistered();
-        verify(mCallback).requestPublishFromInternal(
-                PublishController.PUBLISH_TRIGGER_MMTEL_UNREGISTERED);
-    }
-
-    @Test
-    @SmallTest
     public void testRcsRegistration() throws Exception {
         DeviceCapabilityListener deviceCapListener = createDeviceCapabilityListener();
         deviceCapListener.setImsCallbackRegistered(true);
@@ -226,28 +209,6 @@
 
     @Test
     @SmallTest
-    public void testRcsUnregistration() throws Exception {
-        DeviceCapabilityListener deviceCapListener = createDeviceCapabilityListener();
-        deviceCapListener.setImsCallbackRegistered(true);
-        RegistrationCallback registrationCallback = deviceCapListener.mRcsRegistrationCallback;
-        // Notify DeviceCapabilityListener that unregistered has caused a change and requires
-        // publish.
-        doReturn(true).when(mDeviceCapability).updateImsRcsUnregistered();
-
-        ImsReasonInfo info = new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED, -1, "");
-        registrationCallback.onUnregistered(info);
-
-        Handler handler = deviceCapListener.getHandler();
-        waitForHandlerActionDelayed(handler, HANDLER_WAIT_TIMEOUT_MS, HANDLER_SENT_DELAY_MS);
-
-        verify(mDeviceCapability).updateImsRcsUnregistered();
-        verify(mCallback).requestPublishFromInternal(
-                PublishController.PUBLISH_TRIGGER_RCS_UNREGISTERED);
-        verify(mUceStatsWriter).setStoreCompleteImsRegistrationFeatureTagStats(anyInt());
-    }
-
-    @Test
-    @SmallTest
     public void testMmtelCapabilityChange() throws Exception {
         DeviceCapabilityListener deviceCapListener = createDeviceCapabilityListener();
         ImsMmTelManager.CapabilityCallback callback = deviceCapListener.mMmtelCapabilityCallback;
@@ -263,6 +224,48 @@
                 PublishController.PUBLISH_TRIGGER_MMTEL_CAPABILITY_CHANGE);
     }
 
+    @Test
+    @SmallTest
+    public void testImsUnregistration() throws Exception {
+        DeviceCapabilityListener deviceCapListener = createDeviceCapabilityListener();
+        deviceCapListener.setImsCallbackRegistered(true);
+
+        // set the Ims is registered
+        doReturn(true).when(mDeviceCapability).isImsRegistered();
+        // MMTEL unregistered
+        RegistrationCallback mmtelRegiCallback = deviceCapListener.mMmtelRegistrationCallback;
+
+        ImsReasonInfo info = new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED, -1, "");
+        mmtelRegiCallback.onUnregistered(info);
+
+        Handler handler = deviceCapListener.getHandler();
+        waitForHandlerActionDelayed(handler, HANDLER_WAIT_TIMEOUT_MS, HANDLER_SENT_DELAY_MS);
+
+        verify(mDeviceCapability).updateImsMmtelUnregistered();
+
+        // Do not send internal publish trigger
+        verify(mCallback, never()).requestPublishFromInternal(anyInt());
+        // Only MMTEL unregistered. Verify do not send ImsUnregistered.
+        verify(mCallback, never()).updateImsUnregistered();
+
+        // set the Ims Unregistered
+        doReturn(false).when(mDeviceCapability).isImsRegistered();
+        // RCS unregistered
+        RegistrationCallback rcsRegiCallback = deviceCapListener.mRcsRegistrationCallback;
+        doReturn(true).when(mDeviceCapability).updateImsRcsUnregistered();
+
+        rcsRegiCallback.onUnregistered(info);
+
+        waitForHandlerActionDelayed(handler, HANDLER_WAIT_TIMEOUT_MS, HANDLER_SENT_DELAY_MS);
+
+        verify(mDeviceCapability).updateImsRcsUnregistered();
+        // Do not send internal publish trigger
+        verify(mCallback, never()).requestPublishFromInternal(anyInt());
+        verify(mUceStatsWriter).setStoreCompleteImsRegistrationFeatureTagStats(anyInt());
+
+        verify(mCallback).updateImsUnregistered();
+    }
+
     private DeviceCapabilityListener createDeviceCapabilityListener() {
         DeviceCapabilityListener deviceCapListener = new DeviceCapabilityListener(mContext,
                 mSubId, mDeviceCapability, mCallback, mUceStatsWriter);
diff --git a/tests/src/com/android/ims/rcs/uce/presence/publish/PublishControllerImplTest.java b/tests/src/com/android/ims/rcs/uce/presence/publish/PublishControllerImplTest.java
index 1a7a261..a7e0bbb 100644
--- a/tests/src/com/android/ims/rcs/uce/presence/publish/PublishControllerImplTest.java
+++ b/tests/src/com/android/ims/rcs/uce/presence/publish/PublishControllerImplTest.java
@@ -201,6 +201,25 @@
 
     @Test
     @SmallTest
+    public void testImsUnregistered() throws Exception {
+        PublishControllerImpl publishController = createPublishController();
+        //To initialize the public state to publish_ok.
+        publishController.setCapabilityType(RcsImsCapabilities.CAPABILITY_TYPE_OPTIONS_UCE);
+
+        // Trigger a ims unregistered
+        PublishControllerCallback callback = publishController.getPublishControllerCallback();
+        callback.updateImsUnregistered();
+
+        Handler handler = publishController.getPublishHandler();
+        waitForHandlerAction(handler, 1000);
+        int publishState = publishController.getUcePublishState(false);
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED, publishState);
+        verify(mPublishProcessor).resetState();
+        verify(mUceStatsWriter).setUnPublish(eq(mSubId));
+    }
+
+    @Test
+    @SmallTest
     public void testPublishUpdated() throws Exception {
         PublishControllerImpl publishController = createPublishController();
         int responseCode = 200;