Merge Android 14

Bug: 298295554
Merged-In: I885e1dfab6bbe15ec70347ccfc8c3ffe2349f16f
Change-Id: I99f24e06b0b55d0f72eb8868f001cd3712f4a573
diff --git a/src/java/com/android/ims/ImsCall.java b/src/java/com/android/ims/ImsCall.java
index a2e07a5..5fae98e 100755
--- a/src/java/com/android/ims/ImsCall.java
+++ b/src/java/com/android/ims/ImsCall.java
@@ -537,6 +537,22 @@
         public void onCallSessionRtpHeaderExtensionsReceived(ImsCall imsCall,
                 @NonNull Set<RtpHeaderExtension> rtpHeaderExtensionData) {
         }
+
+        /**
+        * Access Network Bitrate Recommendation Query (ANBRQ), see 3GPP TS 26.114.
+        * This API triggers radio to send ANBRQ message to the access network to query the
+        * desired bitrate.
+        *
+        * @param imsCall The ImsCall the data was received on.
+        * @param mediaType MediaType is used to identify media stream such as audio or video.
+        * @param direction Direction of this packet stream (e.g. uplink or downlink).
+        * @param bitsPerSecond This value is the bitrate requested by the other party UE through
+        *        RTP CMR, RTCPAPP or TMMBR, and ImsStack converts this value to the MAC bitrate
+        *        (defined in TS36.321, range: 0 ~ 8000 kbit/s).
+        */
+        public void onCallSessionSendAnbrQuery(ImsCall imsCall, int mediaType, int direction,
+                int bitsPerSecond) {
+        }
     }
 
     // List of update operation for IMS call control
@@ -886,11 +902,25 @@
     }
 
     /**
-     * Gets the specified property of this call.
+     * Deliver the bitrate for the indicated media type, direction and bitrate to the upper layer.
      *
-     * @param name key to get the extra call information defined in {@link ImsCallProfile}
-     * @return the extra call information as string
+     * @param mediaType MediaType is used to identify media stream such as audio or video.
+     * @param direction Direction of this packet stream (e.g. uplink or downlink).
+     * @param bitsPerSecond This value is the bitrate received from the NW through the Recommended
+     *        bitrate MAC Control Element message and ImsStack converts this value from MAC bitrate
+     *        to audio/video codec bitrate (defined in TS26.114).
+     * @hide
      */
+    public void callSessionNotifyAnbr(int mediaType, int direction, int bitsPerSecond) {
+        synchronized(mLockObj) {
+            if (mSession != null) {
+                mSession.callSessionNotifyAnbr(mediaType, direction, bitsPerSecond);
+            } else {
+                logi("callSessionNotifyAnbr : session - null");
+            }
+        }
+    }
+
     public String getCallExtra(String name) throws ImsException {
         // Lookup the cache
 
@@ -3495,6 +3525,25 @@
                 }
             }
         }
+
+        @Override
+        public void callSessionSendAnbrQuery(int mediaType, int direction, int bitsPerSecond) {
+            ImsCall.Listener listener;
+
+            logi("callSessionSendAnbrQuery in ImsCall");
+            synchronized (ImsCall.this) {
+                listener = mListener;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallSessionSendAnbrQuery(ImsCall.this, mediaType,
+                            direction, bitsPerSecond);
+                } catch (Throwable t) {
+                    loge("callSessionSendAnbrQuery:: ", t);
+                }
+            }
+        }
     }
 
     /**
diff --git a/src/java/com/android/ims/ImsManager.java b/src/java/com/android/ims/ImsManager.java
index c41426d..b5a1168 100644
--- a/src/java/com/android/ims/ImsManager.java
+++ b/src/java/com/android/ims/ImsManager.java
@@ -49,9 +49,12 @@
 import android.telephony.ims.ImsMmTelManager;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsService;
+import android.telephony.ims.MediaQualityStatus;
+import android.telephony.ims.MediaThreshold;
 import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.RtpHeaderExtensionType;
+import android.telephony.ims.SrvccCall;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsConfig;
 import android.telephony.ims.aidl.IImsConfigCallback;
@@ -60,6 +63,7 @@
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.aidl.IImsSmsListener;
 import android.telephony.ims.aidl.ISipTransport;
+import android.telephony.ims.aidl.ISrvccStartedCallback;
 import android.telephony.ims.feature.CapabilityChangeRequest;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
@@ -257,7 +261,7 @@
     @VisibleForTesting
     public interface SubscriptionManagerProxy {
         boolean isValidSubscriptionId(int subId);
-        int[] getSubscriptionIds(int slotIndex);
+        int getSubscriptionId(int slotIndex);
         int getDefaultVoicePhoneId();
         int getIntegerSubscriptionProperty(int subId, String propKey, int defValue);
         void setSubscriptionProperty(int subId, String propKey, String propValue);
@@ -292,8 +296,8 @@
         }
 
         @Override
-        public int[] getSubscriptionIds(int slotIndex) {
-            return getSubscriptionManager().getSubscriptionIds(slotIndex);
+        public int getSubscriptionId(int slotIndex) {
+            return SubscriptionManager.getSubscriptionId(slotIndex);
         }
 
         @Override
@@ -1433,12 +1437,7 @@
     }
 
     private int getSubId() {
-        int[] subIds = mSubscriptionManagerProxy.getSubscriptionIds(mPhoneId);
-        int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-        if (subIds != null && subIds.length >= 1) {
-            subId = subIds[0];
-        }
-        return subId;
+        return mSubscriptionManagerProxy.getSubscriptionId(mPhoneId);
     }
 
     private void setWfcModeInternal(int wfcMode) {
@@ -2689,11 +2688,139 @@
         }
     }
 
+    /**
+     * Notifies the change of user setting.
+     *
+     * @param enabled indicates whether the user setting for call waiting is enabled or not.
+     */
+    public void setTerminalBasedCallWaitingStatus(boolean enabled) throws ImsException {
+        MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
+        try {
+            c.setTerminalBasedCallWaitingStatus(enabled);
+        } catch (ServiceSpecificException se) {
+            if (se.errorCode
+                    == android.telephony.ims.ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
+                throw new ImsException("setTerminalBasedCallWaitingStatus()", se,
+                        ImsReasonInfo.CODE_LOCAL_IMS_NOT_SUPPORTED_ON_DEVICE);
+            } else {
+                throw new ImsException("setTerminalBasedCallWaitingStatus()", se,
+                        ImsReasonInfo.CODE_LOCAL_INTERNAL_ERROR);
+            }
+        } catch (RemoteException e) {
+            throw new ImsException("setTerminalBasedCallWaitingStatus()", e,
+                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+        }
+    }
+
+    /**
+     * Returns whether all of the capabilities specified are capable or not.
+     */
+    public boolean isCapable(@ImsService.ImsServiceCapability long capabilities)
+            throws ImsException {
+        MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
+        try {
+            return c.isCapable(capabilities);
+        } catch (RemoteException e) {
+            throw new ImsException("isCapable()", e,
+                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+        }
+    }
+
+    /**
+     * Notifies SRVCC started.
+     * @param cb The callback to receive the list of {@link SrvccCall}.
+     */
+    public void notifySrvccStarted(ISrvccStartedCallback cb)
+            throws ImsException {
+        MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
+        try {
+            c.notifySrvccStarted(cb);
+        } catch (RemoteException e) {
+            throw new ImsException("notifySrvccStarted", e,
+                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+        }
+    }
+
+    /**
+     * Notifies SRVCC is completed, IMS service will hang up all calls.
+     */
+    public void notifySrvccCompleted() throws ImsException {
+        MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
+        try {
+            c.notifySrvccCompleted();
+        } catch (RemoteException e) {
+            throw new ImsException("notifySrvccCompleted", e,
+                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+        }
+    }
+
+    /**
+     * Notifies SRVCC failed. IMS service will recover and continue calls over IMS.
+     */
+    public void notifySrvccFailed() throws ImsException {
+        MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
+        try {
+            c.notifySrvccFailed();
+        } catch (RemoteException e) {
+            throw new ImsException("notifySrvccFailed", e,
+                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+        }
+    }
+
+    /**
+     * Notifies SRVCC is canceled. IMS service will recover and continue calls over IMS.
+     */
+    public void notifySrvccCanceled() throws ImsException {
+        MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
+        try {
+            c.notifySrvccCanceled();
+        } catch (RemoteException e) {
+            throw new ImsException("notifySrvccCanceled", e,
+                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+        }
+    }
+
+    /**
+     * Notifies that radio triggered IMS deregistration.
+     * @param reason the reason why the deregistration is triggered.
+     */
+    public void triggerDeregistration(@ImsRegistrationImplBase.ImsDeregistrationReason int reason)
+            throws ImsException {
+        MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
+        try {
+            c.triggerDeregistration(reason);
+        } catch (RemoteException e) {
+            throw new ImsException("triggerDeregistration", e,
+                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+        }
+    }
+
     public int getImsServiceState() throws ImsException {
         MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
         return c.getFeatureState();
     }
 
+    public void setMediaThreshold(@MediaQualityStatus.MediaSessionType int sessionType,
+            MediaThreshold threshold) throws ImsException {
+        MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
+        try {
+            c.setMediaThreshold(sessionType, threshold);
+        } catch (RemoteException e) {
+            loge("setMediaThreshold Failed.");
+        }
+    }
+
+    public MediaQualityStatus queryMediaQualityStatus (
+            @MediaQualityStatus.MediaSessionType int sessionType) throws ImsException {
+        MmTelFeatureConnection c = getOrThrowExceptionIfServiceUnavailable();
+        try {
+            return c.queryMediaQualityStatus(sessionType);
+        } catch (RemoteException e) {
+            loge("queryMediaQualityStatus Failed.");
+            return null;
+        }
+    }
+
     @Override
     public void updateFeatureState(int state) {
         mMmTelConnectionRef.get().updateFeatureState(state);
@@ -2975,6 +3102,15 @@
         }
     }
 
+    public void onMemoryAvailable(int token) throws ImsException {
+        try {
+            mMmTelConnectionRef.get().onMemoryAvailable(token);
+        } catch (RemoteException e) {
+            throw new ImsException("onMemoryAvailable()", e,
+                ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+        }
+    }
+
     public void acknowledgeSms(int token, int messageRef, int result) throws ImsException {
         try {
             mMmTelConnectionRef.get().acknowledgeSms(token, messageRef, result);
@@ -2984,6 +3120,15 @@
         }
     }
 
+    public void acknowledgeSms(int token, int messageRef, int result, byte[] pdu) throws ImsException {
+        try {
+            mMmTelConnectionRef.get().acknowledgeSms(token, messageRef, result, pdu);
+        } catch (RemoteException e) {
+            throw new ImsException("acknowledgeSms()", e,
+                    ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+        }
+    }
+
     public void acknowledgeSmsReport(int token, int messageRef, int result) throws  ImsException{
         try {
             mMmTelConnectionRef.get().acknowledgeSmsReport(token, messageRef, result);
diff --git a/src/java/com/android/ims/MmTelFeatureConnection.java b/src/java/com/android/ims/MmTelFeatureConnection.java
index 3170d41..c09fd94 100644
--- a/src/java/com/android/ims/MmTelFeatureConnection.java
+++ b/src/java/com/android/ims/MmTelFeatureConnection.java
@@ -22,8 +22,11 @@
 import android.os.IInterface;
 import android.os.Message;
 import android.os.RemoteException;
+import android.telephony.SubscriptionManager;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsService;
+import android.telephony.ims.MediaQualityStatus;
+import android.telephony.ims.MediaThreshold;
 import android.telephony.ims.RtpHeaderExtensionType;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsConfig;
@@ -33,11 +36,14 @@
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.aidl.IImsSmsListener;
 import android.telephony.ims.aidl.ISipTransport;
+import android.telephony.ims.aidl.ISrvccStartedCallback;
 import android.telephony.ims.feature.CapabilityChangeRequest;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsEcbmImplBase;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.telephony.ims.stub.ImsSmsImplBase;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.ims.internal.IImsCallSession;
 import com.android.ims.internal.IImsEcbm;
@@ -45,7 +51,7 @@
 import com.android.ims.internal.IImsUt;
 
 import java.util.ArrayList;
-import java.util.Optional;
+import java.util.HashMap;
 import java.util.Set;
 
 /**
@@ -100,7 +106,6 @@
     }
 
     private class CapabilityCallbackManager extends ImsCallbackAdapterManager<IImsCapabilityCallback> {
-
         public CapabilityCallbackManager(Context context, Object lock) {
             super(context, lock, mSlotId, mSubId);
         }
@@ -376,6 +381,22 @@
         mProvisioningCallbackManager.removeCallback(callback);
     }
 
+    public void setMediaThreshold(@MediaQualityStatus.MediaSessionType int sessionType,
+            MediaThreshold threshold) throws RemoteException {
+        synchronized (mLock) {
+            checkServiceIsReady();
+            getServiceInterface(mBinder).setMediaQualityThreshold(sessionType, threshold);
+        }
+    }
+
+    public MediaQualityStatus queryMediaQualityStatus(
+            @MediaQualityStatus.MediaSessionType int sessionType) throws RemoteException {
+        synchronized (mLock) {
+            checkServiceIsReady();
+            return getServiceInterface(mBinder).queryMediaQualityStatus(sessionType);
+        }
+    }
+
     public void changeEnabledCapabilities(CapabilityChangeRequest request,
             IImsCapabilityCallback callback) throws RemoteException {
         synchronized (mLock) {
@@ -504,6 +525,13 @@
         }
     }
 
+    public void onMemoryAvailable(int token) throws RemoteException {
+        synchronized (mLock) {
+            checkServiceIsReady();
+            getServiceInterface(mBinder).onMemoryAvailable(token);
+        }
+    }
+
     public void acknowledgeSms(int token, int messageRef,
             @ImsSmsImplBase.SendStatusResult int result) throws RemoteException {
         synchronized (mLock) {
@@ -512,6 +540,14 @@
         }
     }
 
+    public void acknowledgeSms(int token, int messageRef,
+            @ImsSmsImplBase.SendStatusResult int result, byte[] pdu) throws RemoteException {
+        synchronized (mLock) {
+            checkServiceIsReady();
+            getServiceInterface(mBinder).acknowledgeSmsWithPdu(token, messageRef, result, pdu);
+        }
+    }
+
     public void acknowledgeSmsReport(int token, int messageRef,
             @ImsSmsImplBase.StatusReportResult int result) throws RemoteException {
         synchronized (mLock) {
@@ -541,6 +577,45 @@
         }
     }
 
+    public void notifySrvccStarted(ISrvccStartedCallback cb)
+            throws RemoteException {
+        synchronized (mLock) {
+            checkServiceIsReady();
+            getServiceInterface(mBinder).notifySrvccStarted(cb);
+        }
+    }
+
+    public void notifySrvccCompleted() throws RemoteException {
+        synchronized (mLock) {
+            checkServiceIsReady();
+            getServiceInterface(mBinder).notifySrvccCompleted();
+        }
+    }
+
+    public void notifySrvccFailed() throws RemoteException {
+        synchronized (mLock) {
+            checkServiceIsReady();
+            getServiceInterface(mBinder).notifySrvccFailed();
+        }
+    }
+
+    public void notifySrvccCanceled() throws RemoteException {
+        synchronized (mLock) {
+            checkServiceIsReady();
+            getServiceInterface(mBinder).notifySrvccCanceled();
+        }
+    }
+
+    public void triggerDeregistration(@ImsRegistrationImplBase.ImsDeregistrationReason int reason)
+            throws RemoteException {
+        IImsRegistration registration = getRegistration();
+        if (registration != null) {
+            registration.triggerDeregistration(reason);
+        } else {
+            Log.e(TAG + " [" + mSlotId + "]", "triggerDeregistration IImsRegistration is null");
+        }
+    }
+
     public @MmTelFeature.ProcessCallResult int shouldProcessCall(boolean isEmergency,
             String[] numbers) throws RemoteException {
         if (isEmergency && !isEmergencyMmTelAvailable()) {
@@ -576,6 +651,19 @@
         }
     }
 
+    /**
+     * Notifies the MmTelFeature of the enablement status of terminal based call waiting
+     *
+     * @param enabled indicates whether the user setting for call waiting is enabled or not.
+     */
+    public void setTerminalBasedCallWaitingStatus(boolean enabled)
+            throws RemoteException {
+        synchronized (mLock) {
+            checkServiceIsReady();
+            getServiceInterface(mBinder).setTerminalBasedCallWaitingStatus(enabled);
+        }
+    }
+
     private IImsMmTelFeature getServiceInterface(IBinder b) {
         return IImsMmTelFeature.Stub.asInterface(b);
     }
diff --git a/src/java/com/android/ims/RcsFeatureManager.java b/src/java/com/android/ims/RcsFeatureManager.java
index e034a68..e3f50c3 100644
--- a/src/java/com/android/ims/RcsFeatureManager.java
+++ b/src/java/com/android/ims/RcsFeatureManager.java
@@ -16,6 +16,8 @@
 
 package com.android.ims;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.net.Uri;
 import android.os.IBinder;
@@ -30,6 +32,7 @@
 import android.telephony.ims.ImsService;
 import android.telephony.ims.RcsUceAdapter.StackPublishTriggerType;
 import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.SipDetails;
 import android.telephony.ims.aidl.ICapabilityExchangeEventListener;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsConfig;
@@ -44,7 +47,6 @@
 import android.telephony.ims.aidl.ISubscribeResponseCallback;
 import android.telephony.ims.feature.CapabilityChangeRequest;
 import android.telephony.ims.feature.ImsFeature;
-import android.telephony.ims.feature.RcsFeature;
 import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
@@ -96,8 +98,7 @@
          * This method must be called to notify the framework of SUCCESS (200 OK) and FAILURE (300+)
          * codes in order to keep the AOSP stack up to date.
          */
-        void onPublishUpdated(int reasonCode, String reasonPhrase,
-                int reasonHeaderCause, String reasonHeaderText);
+        void onPublishUpdated(SipDetails details);
 
         /**
          * Receive a capabilities request from the remote client.
@@ -123,10 +124,9 @@
                 }
 
                 @Override
-                public void onPublishUpdated(int reasonCode, String reasonPhrase,
-                        int reasonHeaderCause, String reasonHeaderText) {
-                    mCapabilityEventCallback.forEach(callback -> callback.onPublishUpdated(
-                            reasonCode, reasonPhrase, reasonHeaderCause, reasonHeaderText));
+                public void onPublishUpdated(@NonNull SipDetails details) {
+                    mCapabilityEventCallback.forEach(
+                            callback ->callback.onPublishUpdated(details));
                 }
 
                 @Override
@@ -604,38 +604,15 @@
         mRcsFeatureConnection.updateFeatureCapabilities(capabilities);
     }
 
-    /**
-     * Testing interface used to mock SubscriptionManager in testing
-     * @hide
-     */
-    @VisibleForTesting
-    public interface SubscriptionManagerProxy {
-        /**
-         * Mock-able interface for {@link SubscriptionManager#getSubId(int)} used for testing.
-         */
-        int getSubId(int slotId);
-    }
-
     public IImsConfig getConfig() {
         return mRcsFeatureConnection.getConfig();
     }
 
-    private static SubscriptionManagerProxy sSubscriptionManagerProxy
-            = slotId -> {
-                int[] subIds = SubscriptionManager.getSubId(slotId);
-                if (subIds != null) {
-                    return subIds[0];
-                }
-                return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-            };
-
     /**
-     * Testing function used to mock SubscriptionManager in testing
-     * @hide
+     * @return the subscription ID associated with this ImsService connection.
      */
-    @VisibleForTesting
-    public static void setSubscriptionManager(SubscriptionManagerProxy proxy) {
-        sSubscriptionManagerProxy = proxy;
+    public int getSubId() {
+        return mRcsFeatureConnection.getSubId();
     }
 
     private void log(String s) {
diff --git a/src/java/com/android/ims/rcs/uce/UceController.java b/src/java/com/android/ims/rcs/uce/UceController.java
index 6fb27b0..aeab061 100644
--- a/src/java/com/android/ims/rcs/uce/UceController.java
+++ b/src/java/com/android/ims/rcs/uce/UceController.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.net.Uri;
 import android.os.HandlerThread;
@@ -28,6 +29,7 @@
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.RcsUceAdapter.PublishState;
 import android.telephony.ims.RcsUceAdapter.StackPublishTriggerType;
+import android.telephony.ims.SipDetails;
 import android.telephony.ims.aidl.IOptionsRequestCallback;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
 import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
@@ -212,7 +214,7 @@
     private static class CachedCapabilityEvent {
         private Optional<Integer> mRequestPublishCapabilitiesEvent;
         private Optional<Boolean> mUnpublishEvent;
-        private Optional<SomeArgs> mPublishUpdatedEvent;
+        private Optional<SipDetails> mPublishUpdatedEvent;
         private Optional<SomeArgs> mRemoteCapabilityRequestEvent;
 
         public CachedCapabilityEvent() {
@@ -239,14 +241,8 @@
         /**
          * Cache the publish update event triggered by the ImsService.
          */
-        public synchronized void setOnPublishUpdatedEvent(int reasonCode, String reasonPhrase,
-                int reasonHeaderCause, String reasonHeaderText) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = reasonCode;
-            args.arg2 = reasonPhrase;
-            args.arg3 = reasonHeaderCause;
-            args.arg4 = reasonHeaderText;
-            mPublishUpdatedEvent = Optional.of(args);
+        public synchronized void setOnPublishUpdatedEvent(SipDetails details) {
+            mPublishUpdatedEvent = Optional.of(details);
         }
 
         /**
@@ -272,7 +268,7 @@
         }
 
         /** @Return the cached pubilsh update event */
-        public synchronized Optional<SomeArgs> getPublishUpdatedEvent() {
+        public synchronized Optional<SipDetails> getPublishUpdatedEvent() {
             return mPublishUpdatedEvent;
         }
 
@@ -285,7 +281,6 @@
         public synchronized void clear() {
             mRequestPublishCapabilitiesEvent = Optional.empty();
             mUnpublishEvent = Optional.empty();
-            mPublishUpdatedEvent.ifPresent(args -> args.recycle());
             mPublishUpdatedEvent = Optional.empty();
             mRemoteCapabilityRequestEvent.ifPresent(args -> args.recycle());
             mRemoteCapabilityRequestEvent = Optional.empty();
@@ -489,14 +484,9 @@
         Optional<Boolean> unpublishEvent = mCachedCapabilityEvent.getUnpublishEvent();
         unpublishEvent.ifPresent(unpublish -> onUnpublish());
 
-        Optional<SomeArgs> publishUpdatedEvent = mCachedCapabilityEvent.getPublishUpdatedEvent();
-        publishUpdatedEvent.ifPresent(args -> {
-            int reasonCode = (Integer) args.arg1;
-            String reasonPhrase = (String) args.arg2;
-            int reasonHeaderCause = (Integer) args.arg3;
-            String reasonHeaderText = (String) args.arg4;
-            onPublishUpdated(reasonCode, reasonPhrase, reasonHeaderCause, reasonHeaderText);
-        });
+        Optional<SipDetails> publishUpdatedEvent = mCachedCapabilityEvent.getPublishUpdatedEvent();
+        publishUpdatedEvent.ifPresent(details ->
+                onPublishUpdated(details));
 
         Optional<SomeArgs> remoteRequest = mCachedCapabilityEvent.getRemoteCapabilityRequestEvent();
         remoteRequest.ifPresent(args -> {
@@ -606,15 +596,12 @@
                 }
 
                 @Override
-                public void onPublishUpdated(int reasonCode, String reasonPhrase,
-                        int reasonHeaderCause, String reasonHeaderText) {
+                public void onPublishUpdated(@NonNull SipDetails details) {
                     if (isRcsConnecting()) {
-                        mCachedCapabilityEvent.setOnPublishUpdatedEvent(reasonCode, reasonPhrase,
-                                reasonHeaderCause, reasonHeaderText);
+                        mCachedCapabilityEvent.setOnPublishUpdatedEvent(details);
                         return;
                     }
-                    UceController.this.onPublishUpdated(reasonCode, reasonPhrase,
-                            reasonHeaderCause, reasonHeaderText);
+                    UceController.this.onPublishUpdated(details);
                 }
 
                 @Override
@@ -648,14 +635,14 @@
         if (uriList == null || uriList.isEmpty() || c == null) {
             logw("requestCapabilities: parameter is empty");
             if (c != null) {
-                c.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L);
+                c.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L, null);
             }
             return;
         }
 
         if (isUnavailable()) {
             logw("requestCapabilities: controller is unavailable");
-            c.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L);
+            c.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L, null);
             return;
         }
 
@@ -668,7 +655,7 @@
             long retryAfterMillis = deviceStateResult.getRequestRetryAfterMillis();
             logw("requestCapabilities: The device is disallowed, deviceState= " + deviceState +
                     ", errorCode=" + errorCode + ", retryAfterMillis=" + retryAfterMillis);
-            c.onError(errorCode, retryAfterMillis);
+            c.onError(errorCode, retryAfterMillis, null);
             return;
         }
 
@@ -687,14 +674,14 @@
         if (uri == null || c == null) {
             logw("requestAvailability: parameter is empty");
             if (c != null) {
-                c.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L);
+                c.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L, null);
             }
             return;
         }
 
         if (isUnavailable()) {
             logw("requestAvailability: controller is unavailable");
-            c.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L);
+            c.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L, null);
             return;
         }
 
@@ -707,7 +694,7 @@
             long retryAfterMillis = deviceStateResult.getRequestRetryAfterMillis();
             logw("requestAvailability: The device is disallowed, deviceState= " + deviceState +
                     ", errorCode=" + errorCode + ", retryAfterMillis=" + retryAfterMillis);
-            c.onError(errorCode, retryAfterMillis);
+            c.onError(errorCode, retryAfterMillis, null);
             return;
         }
 
@@ -740,11 +727,9 @@
      * This method is triggered by the ImsService to notify framework that the device's
      * publish status has been changed.
      */
-    public void onPublishUpdated(int reasonCode, String reasonPhrase,
-            int reasonHeaderCause, String reasonHeaderText) {
+    public void onPublishUpdated(@NonNull SipDetails details) {
         logi("onPublishUpdated");
-        mPublishController.onPublishUpdated(reasonCode, reasonPhrase,
-                reasonHeaderCause, reasonHeaderText);
+        mPublishController.onPublishUpdated(details);
     }
 
     /**
diff --git a/src/java/com/android/ims/rcs/uce/eab/EabBulkCapabilityUpdater.java b/src/java/com/android/ims/rcs/uce/eab/EabBulkCapabilityUpdater.java
index b4406fd..738a4fc 100644
--- a/src/java/com/android/ims/rcs/uce/eab/EabBulkCapabilityUpdater.java
+++ b/src/java/com/android/ims/rcs/uce/eab/EabBulkCapabilityUpdater.java
@@ -37,6 +37,7 @@
 import android.telephony.ims.ImsManager;
 import android.telephony.ims.ImsRcsManager;
 import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.SipDetails;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
 import android.util.Log;
 
@@ -142,12 +143,12 @@
         }
 
         @Override
-        public void onComplete() {
+        public void onComplete(SipDetails details) {
             Log.d(TAG, "onComplete");
         }
 
         @Override
-        public void onError(int errorCode, long retryAfterMilliseconds) {
+        public void onError(int errorCode, long retryAfterMilliseconds, SipDetails details) {
             Log.d(TAG, "Refresh capabilities failed. Error code: " + errorCode
                     + ", retryAfterMilliseconds: " + retryAfterMilliseconds);
             if (retryAfterMilliseconds != 0) {
diff --git a/src/java/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityInfo.java b/src/java/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityInfo.java
index c363668..4929148 100644
--- a/src/java/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityInfo.java
+++ b/src/java/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityInfo.java
@@ -39,10 +39,13 @@
 
 import com.android.ims.rcs.uce.util.FeatureTags;
 import com.android.ims.rcs.uce.util.UceUtils;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -104,6 +107,11 @@
     private boolean mMobileData;
     private boolean mVtSetting;
 
+    // The service description associated with the last publication update.
+    private final Set<ServiceDescription> mLastSuccessfulCapabilities = new ArraySet<>();
+    // The service description to temporarily store the presence capability being sent.
+    private Set<ServiceDescription> mPendingPublishCapabilities;
+
     public DeviceCapabilityInfo(int subId, String[] capToRegistrationMap) {
         mSubId = subId;
         mServiceCapRegTracker = PublishServiceDescTracker.fromCarrierConfig(capToRegistrationMap);
@@ -125,6 +133,8 @@
         mMmTelCapabilities = new MmTelCapabilities();
         mMmtelAssociatedUris = Collections.EMPTY_LIST;
         mRcsAssociatedUris = Collections.EMPTY_LIST;
+        mLastSuccessfulCapabilities.clear();
+        mPendingPublishCapabilities = null;
     }
 
     /**
@@ -166,7 +176,6 @@
 
     /**
      * Update the status that IMS MMTEL is unregistered.
-     * @return Mmtel registered status before change
      */
     public synchronized boolean updateImsMmtelUnregistered() {
         logi("IMS MMTEL unregistered: original state=" + mMmtelRegistered);
@@ -176,6 +185,8 @@
             changed = true;
         }
         mMmtelNetworkRegType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+        mLastSuccessfulCapabilities.clear();
+        mPendingPublishCapabilities = null;
         return changed;
     }
 
@@ -248,6 +259,8 @@
         mLastRegistrationFeatureTags = Collections.emptySet();
         updateRegistration(mLastRegistrationFeatureTags);
         mRcsNetworkRegType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+        mLastSuccessfulCapabilities.clear();
+        mPendingPublishCapabilities = null;
         return changed;
     }
 
@@ -446,6 +459,67 @@
         return mPresenceCapable;
     }
 
+    // Get the device's capabilities with the PRESENCE mechanism.
+    public RcsContactUceCapability getChangedPresenceCapability(Context context) {
+        if (context == null) {
+            return null;
+        }
+        Set<ServiceDescription> capableFromReg =
+                mServiceCapRegTracker.copyRegistrationCapabilities();
+        if (isPresenceCapabilityChanged(capableFromReg)) {
+            RcsContactUceCapability rcsContactUceCapability = getPresenceCapabilities(context);
+            if (rcsContactUceCapability != null) {
+                mPendingPublishCapabilities = mServiceCapRegTracker.copyRegistrationCapabilities();
+            }
+            return rcsContactUceCapability;
+        }
+        return null;
+    }
+
+    public void setPresencePublishResult(boolean isSuccess) {
+        if (isSuccess) {
+            mLastSuccessfulCapabilities.clear();
+            if (mPendingPublishCapabilities != null) {
+                mLastSuccessfulCapabilities.addAll(mPendingPublishCapabilities);
+            }
+        }
+        mPendingPublishCapabilities = null;
+    }
+
+    public void resetPresenceCapability() {
+        mLastSuccessfulCapabilities.clear();
+        mPendingPublishCapabilities = null;
+    }
+
+    public List<RcsContactPresenceTuple> getLastSuccessfulPresenceTuplesWithoutContactUri() {
+        List<RcsContactPresenceTuple> presenceTuples = new ArrayList<>();
+        if (mLastSuccessfulCapabilities.isEmpty()) {
+            return presenceTuples;
+        }
+
+        for (ServiceDescription capability : mLastSuccessfulCapabilities) {
+            presenceTuples.add(capability.getTupleBuilder().build());
+        }
+        return presenceTuples;
+    }
+
+    @VisibleForTesting
+    public void addLastSuccessfulServiceDescription(ServiceDescription capability) {
+        mLastSuccessfulCapabilities.add(capability);
+    }
+
+    @VisibleForTesting
+    public boolean isPresenceCapabilityChanged(Set<ServiceDescription> capableFromReg) {
+        if (mLastSuccessfulCapabilities.isEmpty()) {
+            return true;
+        }
+
+        if (capableFromReg.equals(mLastSuccessfulCapabilities)) {
+            return false;
+        }
+        return true;
+    }
+
     private boolean isVolteAvailable(int networkRegType, MmTelCapabilities capabilities) {
         return (networkRegType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                 && capabilities.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
@@ -478,7 +552,12 @@
             @CapabilityMechanism int mechanism, Context context) {
         switch (mechanism) {
             case RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE:
-                return getPresenceCapabilities(context);
+                RcsContactUceCapability rcsContactUceCapability = getPresenceCapabilities(context);
+                if (rcsContactUceCapability != null) {
+                    mPendingPublishCapabilities =
+                            mServiceCapRegTracker.copyRegistrationCapabilities();
+                }
+                return rcsContactUceCapability;
             case RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS:
                 return getOptionsCapabilities(context);
             default:
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 91904a6..bc37205 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
@@ -276,7 +276,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
         mContext.registerReceiver(mReceiver, filter, android.Manifest.permission.MODIFY_PHONE_STATE,
-                null);
+                null, Context.RECEIVER_EXPORTED);
 
         ContentResolver resolver = mContext.getContentResolver();
         if (resolver != null) {
@@ -587,9 +587,9 @@
      * This method is called when the MMTEL is registered.
      */
     private void handleImsMmtelRegistered(int imsTransportType) {
+        // update capability, but not trigger PUBLISH message.
+        // PUBLISH message will be sent when the Capability status changed callback is called.
         mCapabilityInfo.updateImsMmtelRegistered(imsTransportType);
-        mHandler.sendTriggeringPublishMessage(
-                PublishController.PUBLISH_TRIGGER_MMTEL_REGISTERED);
     }
 
     /*
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 a3d31b9..732a558 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
@@ -18,9 +18,11 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.RcsContactUceCapability.CapabilityMechanism;
 import android.telephony.ims.RcsUceAdapter.PublishState;
+import android.telephony.ims.SipDetails;
 import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
 
 import com.android.ims.rcs.uce.ControllerBase;
@@ -138,7 +140,8 @@
         /**
          * Update the publish request result.
          */
-        void updatePublishRequestResult(int publishState, Instant updatedTimestamp, String pidfXml);
+        void updatePublishRequestResult(int publishState, Instant updatedTimestamp, String pidfXml,
+                SipDetails details);
 
         /**
          * Update the value of the publish throttle.
@@ -208,9 +211,7 @@
     /**
      * Notify that the device's publish status have been changed.
      */
-    void onPublishUpdated(int reasonCode, String reasonPhrase,
-            int reasonHeaderCause, String reasonHeaderText);
-
+    void onPublishUpdated(@NonNull SipDetails details);
     /**
      * Retrieve the device's capabilities.
      */
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 35a5d55..101aa81 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
@@ -16,7 +16,10 @@
 
 package com.android.ims.rcs.uce.presence.publish;
 
+import static android.telephony.ims.RcsUceAdapter.PUBLISH_STATE_OK;
+
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.os.Build;
 import android.os.Handler;
@@ -27,10 +30,13 @@
 import android.os.RemoteException;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ims.ImsException;
+import android.telephony.ims.PublishAttributes;
+import android.telephony.ims.RcsContactPresenceTuple;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.RcsContactUceCapability.CapabilityMechanism;
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.RcsUceAdapter.PublishState;
+import android.telephony.ims.SipDetails;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
 import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities;
@@ -53,6 +59,7 @@
 import java.lang.ref.WeakReference;
 import java.time.Instant;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -198,7 +205,7 @@
         if (capabilityType == RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE) {
             return RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED;
         } else if (capabilityType == RcsImsCapabilities.CAPABILITY_TYPE_OPTIONS_UCE) {
-            return RcsUceAdapter.PUBLISH_STATE_OK;
+            return PUBLISH_STATE_OK;
         } else {
             return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
         }
@@ -363,11 +370,9 @@
      * Notify that the device's publish status have been changed.
      */
     @Override
-    public void onPublishUpdated(int reasonCode, String reasonPhrase,
-            int reasonHeaderCause, String reasonHeaderText) {
+    public void onPublishUpdated(@NonNull SipDetails details) {
         if (mIsDestroyedFlag) return;
-        mPublishHandler.sendPublishUpdatedMessage(reasonCode, reasonPhrase, reasonHeaderCause,
-                reasonHeaderText);
+        mPublishHandler.sendPublishUpdatedMessage(details);
     }
 
     @Override
@@ -412,9 +417,10 @@
 
                 @Override
                 public void updatePublishRequestResult(@PublishState int state,
-                        Instant updatedTime, String pidfXml) {
+                        Instant updatedTime, String pidfXml, SipDetails details) {
                     logd("updatePublishRequestResult: " + state + ", time=" + updatedTime);
-                    mPublishHandler.sendPublishStateChangedMessage(state, updatedTime, pidfXml);
+                    mPublishHandler.sendPublishStateChangedMessage(state, updatedTime, pidfXml,
+                            details);
                 }
 
                 @Override
@@ -514,9 +520,10 @@
                     int newPublishState = (Integer) args.arg1;
                     Instant updatedTimestamp = (Instant) args.arg2;
                     String pidfXml = (String) args.arg3;
+                    SipDetails details = (SipDetails) args.arg4;
                     args.recycle();
                     publishCtrl.handlePublishStateChangedMessage(newPublishState, updatedTimestamp,
-                            pidfXml);
+                            pidfXml, details);
                     break;
                 }
                 case MSG_NOTIFY_CURRENT_PUBLISH_STATE:
@@ -566,14 +573,8 @@
                     break;
 
                 case MSG_PUBLISH_UPDATED: {
-                    SomeArgs args = (SomeArgs) message.obj;
-                    int reasonCode = (Integer) args.arg1;
-                    String reasonPhrase = (String) args.arg2;
-                    int reasonHeaderCause = (Integer) args.arg3;
-                    String reasonHeaderText = (String) args.arg4;
-                    args.recycle();
-                    publishCtrl.handlePublishUpdatedMessage(reasonCode, reasonPhrase,
-                            reasonHeaderCause, reasonHeaderText);
+                    SipDetails details = (SipDetails) message.obj;
+                    publishCtrl.handlePublishUpdatedMessage(details);
                     break;
                 }
 
@@ -654,7 +655,7 @@
          * Send the message to notify the publish state is changed.
          */
         public void sendPublishStateChangedMessage(@PublishState int publishState,
-                @NonNull Instant updatedTimestamp, String pidfXml) {
+                @NonNull Instant updatedTimestamp, String pidfXml, SipDetails details) {
             PublishControllerImpl publishCtrl = mPublishControllerRef.get();
             if (publishCtrl == null) return;
             if (publishCtrl.mIsDestroyedFlag) return;
@@ -663,6 +664,7 @@
             args.arg1 = publishState;
             args.arg2 = updatedTimestamp;
             args.arg3 = pidfXml;
+            args.arg4 = details;
             Message message = obtainMessage();
             message.what = MSG_PUBLISH_STATE_CHANGED;
             message.obj = args;
@@ -689,20 +691,14 @@
         /**
          * Send the message to notify the publish state is changed.
          */
-        public void sendPublishUpdatedMessage(int reasonCode, String reasonPhrase,
-                int reasonHeaderCause, String reasonHeaderText) {
+        public void sendPublishUpdatedMessage(@NonNull SipDetails details) {
             PublishControllerImpl publishCtrl = mPublishControllerRef.get();
             if (publishCtrl == null) return;
             if (publishCtrl.mIsDestroyedFlag) return;
 
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = reasonCode;
-            args.arg2 = reasonPhrase;
-            args.arg3 = reasonHeaderCause;
-            args.arg4 = reasonHeaderText;
             Message message = obtainMessage();
             message.what = MSG_PUBLISH_UPDATED;
-            message.obj = args;
+            message.obj = details;
             sendMessage(message);
         }
 
@@ -924,7 +920,7 @@
         // PUBLISH is enabled.
         if (isPresencePublishEnabled()) {
             handlePublishStateChangedMessage(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED,
-                    Instant.now(), null /*pidfXml*/);
+                    Instant.now(), null /*pidfXml*/, null /*SipDetails*/);
         }
     }
 
@@ -1011,7 +1007,8 @@
             // Update the publish state directly. Because this method is called in the
             // handler thread already, the process of updating publish state does not need to be
             // sent to the looper again.
-            handlePublishStateChangedMessage(updatedPublishState, Instant.now(), null /*pidfxml*/);
+            handlePublishStateChangedMessage(updatedPublishState, Instant.now(), null /*pidfxml*/,
+                    null /*SipDetails*/);
         }
     }
 
@@ -1040,7 +1037,7 @@
      * from original state.
      */
     private void handlePublishStateChangedMessage(@PublishState int newPublishState,
-            Instant updatedTimestamp, String pidfXml) {
+            Instant updatedTimestamp, String pidfXml, SipDetails details) {
         synchronized (mPublishStateLock) {
             if (mIsDestroyedFlag) return;
             // Check if the time of the given publish state is not earlier than existing time.
@@ -1067,7 +1064,7 @@
         logd("Notify publish state changed: " + mCurrentPublishState);
         mPublishStateCallbacks.broadcast(c -> {
             try {
-                c.onPublishStateChanged(mCurrentPublishState);
+                c.onPublishUpdated(getPublishAttributes(mCurrentPublishState, details));
             } catch (RemoteException e) {
                 logw("Notify publish state changed error: " + e);
             }
@@ -1075,11 +1072,25 @@
         logd("Notify publish state changed: completed");
     }
 
+    private PublishAttributes getPublishAttributes(@PublishState int mCurrentPublishState,
+            SipDetails details) {
+        List<RcsContactPresenceTuple> tuples = null;
+        if (mCurrentPublishState == PUBLISH_STATE_OK) {
+            tuples = mDeviceCapabilityInfo.getLastSuccessfulPresenceTuplesWithoutContactUri();
+        }
+        if (tuples != null && !tuples.isEmpty()) {
+            return new PublishAttributes.Builder(mCurrentPublishState).setSipDetails(details)
+                    .setPresenceTuples(tuples).build();
+        }
+        return new PublishAttributes.Builder(mCurrentPublishState).setSipDetails(details).build();
+    }
+
     private void handleNotifyCurrentPublishStateMessage(IRcsUcePublishStateCallback callback,
             boolean supportPublishingState) {
         if (mIsDestroyedFlag || callback == null) return;
         try {
-            callback.onPublishStateChanged(getUcePublishState(supportPublishingState));
+            int publishState = getUcePublishState(supportPublishingState);
+            callback.onPublishUpdated(getPublishAttributes(publishState, null /*SipDetails*/));
         } catch (RemoteException e) {
             logw("handleCurrentPublishStateUpdateMessage exception: " + e);
         }
@@ -1146,7 +1157,8 @@
             Instant updatedTimestamp) {
         if (mIsDestroyedFlag) return;
         mPublishProcessor.resetState();
-        handlePublishStateChangedMessage(newPublishState, updatedTimestamp, null);
+        handlePublishStateChangedMessage(newPublishState, updatedTimestamp,
+                null /*pidfXml*/, null /*SipDetails*/);
     }
 
     private void handlePublishSentMessage() {
@@ -1170,20 +1182,22 @@
                     mCurrentPublishState = RcsUceAdapter.PUBLISH_STATE_PUBLISHING;
                     if (isSupportPublishingState) {
                         if (callback != null) {
-                            callback.onPublishStateChanged(mCurrentPublishState);
+                            callback.onPublishUpdated(getPublishAttributes(mCurrentPublishState,
+                                    null /*SipDetails*/));
                         }
                     } else {
                         // If it is currently PUBLISH_STATE_OK, the state must not be changed to
                         // PUBLISH_STATE_NOT_PUBLISHED.
                         // And in the case of the current PUBLISH_STATE_NOT_PUBLISHED, it is
                         // necessary to avoid reporting the duplicate state.
-                        if (tempPublishState != RcsUceAdapter.PUBLISH_STATE_OK
+                        if (tempPublishState != PUBLISH_STATE_OK
                                 && tempPublishState != RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED) {
                             // set the state to PUBLISH_STATE_NOT_PUBLISHED so that
                             // getUcePublishState is consistent with the callback
                             mLastPublishState = RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED;
                             if (callback != null) {
-                                callback.onPublishStateChanged(mLastPublishState);
+                                callback.onPublishUpdated(getPublishAttributes(mLastPublishState,
+                                        null /*SipDetails*/));
                             }
                         }
                     }
@@ -1194,11 +1208,10 @@
         }
     }
 
-    private void handlePublishUpdatedMessage(int reasonCode, String reasonPhrase,
-            int reasonHeaderCause, String reasonHeaderText) {
+    private void handlePublishUpdatedMessage(@NonNull SipDetails details) {
         if (mIsDestroyedFlag) return;
         PublishRequestResponse updatedPublish = new PublishRequestResponse(getLastPidfXml(),
-                reasonCode, reasonPhrase, reasonHeaderCause, reasonHeaderText);
+                details);
         mPublishProcessor.publishUpdated(updatedPublish);
     }
 
diff --git a/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessor.java b/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessor.java
index d87eea9..c239dd5 100644
--- a/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessor.java
+++ b/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessor.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.os.RemoteException;
 import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.SipDetails;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase;
 import android.text.TextUtils;
@@ -112,6 +113,8 @@
         logi("onRcsDisconnected");
         mRcsFeatureManager = null;
         mProcessorState.onRcsDisconnected();
+        // reset the publish capabilities.
+        mDeviceCapabilities.resetPresenceCapability();
     }
 
     /**
@@ -152,10 +155,15 @@
         }
 
         // Get the latest device's capabilities.
-        RcsContactUceCapability deviceCapability =
-                mDeviceCapabilities.getDeviceCapabilities(CAPABILITY_MECHANISM_PRESENCE, mContext);
+        RcsContactUceCapability deviceCapability;
+        if (triggerType == PublishController.PUBLISH_TRIGGER_SERVICE) {
+            deviceCapability = mDeviceCapabilities.getDeviceCapabilities(
+                    CAPABILITY_MECHANISM_PRESENCE, mContext);
+        } else {
+            deviceCapability = mDeviceCapabilities.getChangedPresenceCapability(mContext);
+        }
         if (deviceCapability == null) {
-            logw("doPublishInternal: device capability is null");
+            logi("doPublishInternal: device capability hasn't changed or is null");
             return false;
         }
 
@@ -349,6 +357,8 @@
         // Increase the retry count
         mProcessorState.increaseRetryCount();
 
+        // reset the last capabilities because of the request is failed
+        mDeviceCapabilities.setPresencePublishResult(false);
         // Reset the pending flag because it is going to resend a request.
         clearPendingRequest();
 
@@ -373,15 +383,21 @@
         Instant responseTime = response.getResponseTimestamp();
 
         // Record the time when the request is successful and reset the retry count.
+        boolean publishSuccess = false;
         if (response.isRequestSuccess()) {
             mProcessorState.setLastPublishedTime(responseTime);
             mProcessorState.resetRetryCount();
+            publishSuccess = true;
         }
+        // set the last capabilities according to the result of request.
+        mDeviceCapabilities.setPresencePublishResult(publishSuccess);
 
         // Update the publish state after the request has finished.
         int publishState = response.getPublishState();
         String pidfXml = response.getPidfXml();
-        mPublishCtrlCallback.updatePublishRequestResult(publishState, responseTime, pidfXml);
+        SipDetails details = response.getSipDetails().orElse(null);
+        mPublishCtrlCallback.updatePublishRequestResult(publishState, responseTime, pidfXml,
+                details);
 
         // Refresh the device state with the publish request result.
         response.getResponseSipCode().ifPresent(sipCode -> {
@@ -492,6 +508,8 @@
      */
     public void resetState() {
         mProcessorState.resetState();
+        // reset the publish capabilities.
+        mDeviceCapabilities.resetPresenceCapability();
     }
 
     /**
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 47aa37c..aa1b27c 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
@@ -16,8 +16,10 @@
 
 package com.android.ims.rcs.uce.presence.publish;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.telephony.ims.RcsUceAdapter;
+import android.telephony.ims.SipDetails;
 import android.telephony.ims.aidl.IPublishResponseCallback;
 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase;
 import android.text.TextUtils;
@@ -47,7 +49,7 @@
     private Optional<String> mReasonPhrase;
     private Optional<Integer> mReasonHeaderCause;
     private Optional<String> mReasonHeaderText;
-
+    private Optional<SipDetails> mSipDetails;
     // The timestamp when receive the response from the network.
     private Instant mResponseTimestamp;
 
@@ -61,29 +63,29 @@
         mReasonPhrase = Optional.empty();
         mReasonHeaderCause = Optional.empty();
         mReasonHeaderText = Optional.empty();
+        mSipDetails = Optional.empty();
     }
 
-    public PublishRequestResponse(String pidfXml, int sipCode, String reasonPhrase,
-            int reasonHeaderCause, String reasonHeaderText) {
+    public PublishRequestResponse(String pidfXml, @Nullable SipDetails details) {
         mTaskId = 0L;
         mPublishCtrlCallback = null;
         mCmdErrorCode = Optional.empty();
 
         mPidfXml = pidfXml;
         mResponseTimestamp = Instant.now();
-        mNetworkRespSipCode = Optional.of(sipCode);
-        mReasonPhrase = Optional.ofNullable(reasonPhrase);
-        if (reasonHeaderCause != 0) {
-            mReasonHeaderCause = Optional.of(reasonHeaderCause);
+        mNetworkRespSipCode = Optional.of(details.getResponseCode());
+        mReasonPhrase = Optional.ofNullable(details.getResponsePhrase());
+        if (details.getReasonHeaderCause() != 0) {
+            mReasonHeaderCause = Optional.of(details.getReasonHeaderCause());
         } else {
             mReasonHeaderCause = Optional.empty();
         }
-        if (TextUtils.isEmpty(reasonHeaderText)) {
+        if (TextUtils.isEmpty(details.getReasonHeaderText())) {
             mReasonHeaderText = Optional.empty();
         } else {
-            mReasonHeaderText = Optional.ofNullable(reasonHeaderText);
+            mReasonHeaderText = Optional.ofNullable(details.getReasonHeaderText());
         }
-
+        mSipDetails = Optional.ofNullable(details);
     }
 
     // The result callback of the publish capability request.
@@ -94,15 +96,8 @@
         }
 
         @Override
-        public void onNetworkResponse(int code, String reason) {
-            PublishRequestResponse.this.onNetworkResponse(code, reason);
-        }
-
-        @Override
-        public void onNetworkRespHeader(int code, String reasonPhrase, int reasonHeaderCause,
-                String reasonHeaderText) {
-            PublishRequestResponse.this.onNetworkResponse(code, reasonPhrase, reasonHeaderCause,
-                    reasonHeaderText);
+        public void onNetworkResponse(@NonNull SipDetails details) {
+            PublishRequestResponse.this.onNetworkResponse(details);
         }
     };
 
@@ -150,6 +145,12 @@
     }
 
     /**
+     * Retrieve the sip information which received from the network.
+     */
+    public Optional<SipDetails> getSipDetails() {
+        return mSipDetails;
+    }
+    /**
      * Retrieve the SIP code from the network response. It will get the value from the Reason
      * Header first. If the ReasonHeader is not present, it will get the value from the Network
      * response instead.
@@ -198,49 +199,34 @@
         }
     }
 
-    private void onNetworkResponse(int sipCode, String reason) {
+    private void onNetworkResponse(@NonNull SipDetails details) {
         // 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) {
+        if (details.getResponseCode() == 999) {
             onCommandError(RcsCapabilityExchangeImplBase.COMMAND_CODE_NO_CHANGE);
             return;
         }
         mResponseTimestamp = Instant.now();
-        mNetworkRespSipCode = Optional.of(sipCode);
-        mReasonPhrase = Optional.ofNullable(reason);
+        mNetworkRespSipCode = Optional.of(details.getResponseCode());
+        mReasonPhrase = Optional.ofNullable(details.getResponsePhrase());
+        if (details.getReasonHeaderCause() != 0) {
+            mReasonHeaderCause = Optional.of(details.getReasonHeaderCause());
+        }
+        if (TextUtils.isEmpty(details.getReasonHeaderText())) {
+            mReasonHeaderText = Optional.empty();
+        } else {
+            mReasonHeaderText = Optional.ofNullable(details.getReasonHeaderText());
+        }
+        mSipDetails = Optional.ofNullable(details);
         updateRetryFlagByNetworkResponse();
 
         PublishControllerCallback ctrlCallback = mPublishCtrlCallback;
         if (ctrlCallback != null) {
             ctrlCallback.onRequestNetworkResp(this);
         } else {
-            Log.d(LOG_TAG, "onNetworkResponse: already destroyed. sip code=" + sipCode);
-        }
-    }
-
-    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);
-        mReasonHeaderCause = Optional.of(reasonHeaderCause);
-        mReasonHeaderText = Optional.ofNullable(reasonHeaderText);
-        updateRetryFlagByNetworkResponse();
-
-        PublishControllerCallback ctrlCallback = mPublishCtrlCallback;
-        if (ctrlCallback != null) {
-            ctrlCallback.onRequestNetworkResp(this);
-        } else {
-            Log.d(LOG_TAG, "onNetworkResponse: already destroyed. sipCode=" + sipCode +
-                    ", reasonHeader=" + reasonHeaderCause);
+            Log.d(LOG_TAG, "onNetworkResponse: already destroyed. sip code="
+                    + details.getResponseCode());
         }
     }
 
diff --git a/src/java/com/android/ims/rcs/uce/request/CapabilityRequestResponse.java b/src/java/com/android/ims/rcs/uce/request/CapabilityRequestResponse.java
index 97371b8..6da3130 100644
--- a/src/java/com/android/ims/rcs/uce/request/CapabilityRequestResponse.java
+++ b/src/java/com/android/ims/rcs/uce/request/CapabilityRequestResponse.java
@@ -16,11 +16,14 @@
 
 package com.android.ims.rcs.uce.request;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.net.Uri;
 import android.telephony.ims.RcsContactTerminatedReason;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.RcsUceAdapter.ErrorCode;
+import android.telephony.ims.SipDetails;
 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase;
 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase.CommandCode;
 import android.text.TextUtils;
@@ -85,6 +88,9 @@
     // The collection to record whether the request contacts have received the capabilities updated.
     private Map<Uri, Boolean> mContactCapsReceived;
 
+    // The SIP detail information of the network response.
+    private Optional<SipDetails> mSipDetails;
+
     public CapabilityRequestResponse() {
         mRequestInternalError = Optional.empty();
         mCommandError = Optional.empty();
@@ -99,6 +105,7 @@
         mUpdatedCapabilityList = new ArrayList<>();
         mRemoteCaps = new HashSet<>();
         mContactCapsReceived = new HashMap<>();
+        mSipDetails = Optional.empty();
     }
 
     /**
@@ -169,12 +176,20 @@
     /**
      * Set the network response of this request which is sent by the network.
      */
-    public synchronized void setNetworkResponseCode(int sipCode, String reasonPhrase,
-            int reasonHeaderCause, String reasonHeaderText) {
-        mNetworkRespSipCode = Optional.of(sipCode);
-        mReasonPhrase = Optional.ofNullable(reasonPhrase);
-        mReasonHeaderCause = Optional.of(reasonHeaderCause);
-        mReasonHeaderText = Optional.ofNullable(reasonHeaderText);
+    public synchronized void setSipDetails(@NonNull SipDetails details) {
+        setNetworkResponseCode(details.getResponseCode(), details.getResponsePhrase());
+        if (details.getReasonHeaderCause() != 0) {
+            mReasonHeaderCause = Optional.of(details.getReasonHeaderCause());
+        } else {
+            mReasonHeaderCause = Optional.empty();
+        }
+        if (TextUtils.isEmpty(details.getReasonHeaderText())) {
+            mReasonHeaderText = Optional.empty();
+        } else {
+            mReasonHeaderText = Optional.ofNullable(details.getReasonHeaderText());
+        }
+
+        mSipDetails = Optional.ofNullable(details);
     }
 
     // Get the sip code of the network response.
@@ -239,6 +254,13 @@
     }
 
     /**
+     * Retrieve the SIP information which received from the network.
+     */
+    public Optional<SipDetails> getSipDetails() {
+        return mSipDetails;
+    }
+
+    /**
      * Add the capabilities which are retrieved from the cache.
      */
     public synchronized void addCachedCapabilities(List<RcsContactUceCapability> capabilityList) {
diff --git a/src/java/com/android/ims/rcs/uce/request/OptionsRequestCoordinator.java b/src/java/com/android/ims/rcs/uce/request/OptionsRequestCoordinator.java
index a266093..b2db178 100644
--- a/src/java/com/android/ims/rcs/uce/request/OptionsRequestCoordinator.java
+++ b/src/java/com/android/ims/rcs/uce/request/OptionsRequestCoordinator.java
@@ -315,7 +315,7 @@
     private void triggerCompletedCallback() {
         try {
             logd("triggerCompletedCallback");
-            mCapabilitiesCallback.onComplete();
+            mCapabilitiesCallback.onComplete(null);
         } catch (RemoteException e) {
             logw("triggerCompletedCallback exception: " + e);
         } finally {
@@ -329,7 +329,7 @@
     private void triggerErrorCallback(int errorCode, long retryAfterMillis) {
         try {
             logd("triggerErrorCallback: errorCode=" + errorCode + ", retry=" + retryAfterMillis);
-            mCapabilitiesCallback.onError(errorCode, retryAfterMillis);
+            mCapabilitiesCallback.onError(errorCode, retryAfterMillis, null);
         } catch (RemoteException e) {
             logw("triggerErrorCallback exception: " + e);
         } finally {
diff --git a/src/java/com/android/ims/rcs/uce/request/SubscribeRequest.java b/src/java/com/android/ims/rcs/uce/request/SubscribeRequest.java
index bee7177..a306eb4 100644
--- a/src/java/com/android/ims/rcs/uce/request/SubscribeRequest.java
+++ b/src/java/com/android/ims/rcs/uce/request/SubscribeRequest.java
@@ -17,11 +17,13 @@
 package com.android.ims.rcs.uce.request;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.telephony.ims.RcsContactTerminatedReason;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.RcsUceAdapter;
+import android.telephony.ims.SipDetails;
 import android.telephony.ims.aidl.ISubscribeResponseCallback;
 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase.CommandCode;
 
@@ -53,14 +55,8 @@
                     SubscribeRequest.this.onCommandError(code);
                 }
                 @Override
-                public void onNetworkResponse(int code, String reason) {
-                    SubscribeRequest.this.onNetworkResponse(code, reason);
-                }
-                @Override
-                public void onNetworkRespHeader(int code, String reasonPhrase,
-                        int reasonHeaderCause, String reasonHeaderText) {
-                    SubscribeRequest.this.onNetworkResponse(code, reasonPhrase, reasonHeaderCause,
-                            reasonHeaderText);
+                public void onNetworkResponse(@NonNull SipDetails details) {
+                    SubscribeRequest.this.onNetworkResponse(details);
                 }
                 @Override
                 public void onNotifyCapabilitiesUpdate(List<String> pidfXmls) {
@@ -135,28 +131,14 @@
     }
 
     // Receive the network response callback which is triggered by ISubscribeResponseCallback.
-    private void onNetworkResponse(int sipCode, String reason) {
-        logd("onNetworkResponse: code=" + sipCode + ", reason=" + reason);
-        if (mIsFinished) {
-            logw("onNetworkResponse: request is already finished");
-            return;
-        }
-        mRequestResponse.setNetworkResponseCode(sipCode, reason);
-        mRequestManagerCallback.notifyNetworkResponse(mCoordinatorId, mTaskId);
-    }
+    private void onNetworkResponse(@NonNull SipDetails details) {
+        logd("onNetworkResponse: sip details=" + details.toString());
 
-    // Receive the network response callback which is triggered by ISubscribeResponseCallback.
-    private void onNetworkResponse(int sipCode, String reasonPhrase,
-        int reasonHeaderCause, String reasonHeaderText) {
-        logd("onNetworkResponse: code=" + sipCode + ", reasonPhrase=" + reasonPhrase +
-                ", reasonHeaderCause=" + reasonHeaderCause +
-                ", reasonHeaderText=" + reasonHeaderText);
         if (mIsFinished) {
             logw("onNetworkResponse: request is already finished");
             return;
         }
-        mRequestResponse.setNetworkResponseCode(sipCode, reasonPhrase, reasonHeaderCause,
-                reasonHeaderText);
+        mRequestResponse.setSipDetails(details);
         mRequestManagerCallback.notifyNetworkResponse(mCoordinatorId, mTaskId);
     }
 
diff --git a/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java b/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java
index f44686a..26143f9 100644
--- a/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java
+++ b/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java
@@ -18,10 +18,12 @@
 
 import static android.telephony.ims.stub.RcsCapabilityExchangeImplBase.COMMAND_CODE_GENERIC_FAILURE;
 
+import android.annotation.Nullable;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.RcsUceAdapter;
+import android.telephony.ims.SipDetails;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
 
 import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult;
@@ -111,21 +113,25 @@
     private static final RequestResultCreator sNetworkRespErrorCreator = (taskId, response,
             requestMgrCallback) -> {
         DeviceStateResult deviceState = requestMgrCallback.getDeviceState();
+        SipDetails details = response.getSipDetails().orElse(null);
         if (deviceState.isRequestForbidden()) {
             int errorCode = deviceState.getErrorCode().orElse(RcsUceAdapter.ERROR_FORBIDDEN);
             long retryAfter = deviceState.getRequestRetryAfterMillis();
-            return RequestResult.createFailedResult(taskId, errorCode, retryAfter);
+            return RequestResult.createFailedResult(taskId, errorCode, retryAfter, details);
         } else {
             int errorCode = CapabilityRequestResponse.getCapabilityErrorFromSipCode(response);
             long retryAfter = response.getRetryAfterMillis();
-            return RequestResult.createFailedResult(taskId, errorCode, retryAfter);
+            return RequestResult.createFailedResult(taskId, errorCode, retryAfter, details);
         }
     };
 
     // The RequestResult creator of the network response is not 200 OK, however, we can to treat
     // it as a successful result and finish the request
     private static final RequestResultCreator sNetworkRespSuccessfulCreator = (taskId, response,
-            requestMgrCallback) -> RequestResult.createSuccessResult(taskId);
+            requestMgrCallback) -> {
+        SipDetails detail = response.getSipDetails().orElse(null);
+        return RequestResult.createSuccessResult(taskId, detail);
+    };
 
     // The RequestResult creator of the request terminated.
     private static final RequestResultCreator sTerminatedCreator = (taskId, response,
@@ -134,6 +140,7 @@
         TerminatedResult terminatedResult = SubscriptionTerminatedHelper.getAnalysisResult(
                 response.getTerminatedReason(), response.getRetryAfterMillis(),
                 response.haveAllRequestCapsUpdatedBeenReceived());
+        SipDetails details = response.getSipDetails().orElse(null);
         if (terminatedResult.getErrorCode().isPresent()) {
             // If the terminated error code is present, it means that the request is failed.
             int errorCode = terminatedResult.getErrorCode().get();
@@ -143,9 +150,9 @@
             // If the network response is failed or the retryAfter is not 0, this request is failed.
             long retryAfterMillis = response.getRetryAfterMillis();
             int errorCode = CapabilityRequestResponse.getCapabilityErrorFromSipCode(response);
-            return RequestResult.createFailedResult(taskId, errorCode, retryAfterMillis);
+            return RequestResult.createFailedResult(taskId, errorCode, retryAfterMillis, details);
         } else {
-            return RequestResult.createSuccessResult(taskId);
+            return RequestResult.createSuccessResult(taskId, details);
         }
     };
 
@@ -297,7 +304,9 @@
             // Trigger capabilities updated callback if there is any.
             List<RcsContactUceCapability> updatedCapList = response.getUpdatedContactCapability();
             if (!updatedCapList.isEmpty()) {
-                mRequestManagerCallback.saveCapabilities(updatedCapList);
+                if (response.isNotFound()) {
+                    mRequestManagerCallback.saveCapabilities(updatedCapList);
+                }
                 triggerCapabilitiesReceivedCallback(updatedCapList);
                 response.removeUpdatedCapabilities(updatedCapList);
             }
@@ -538,14 +547,23 @@
                         .max(Comparator.comparingLong(result ->
                                 result.getRetryMillis().orElse(-1L)));
 
+            Optional<RequestResult> optDebugInfoResult = mFinishedRequests.values().stream()
+                    .filter(result -> !result.getSipDetails().isEmpty())
+                    .findFirst();
+
+            SipDetails details = null;
+            if (optDebugInfoResult.isPresent()) {
+                RequestResult result = optDebugInfoResult.get();
+                details = result.getSipDetails().orElse(null);
+            }
             // Trigger the callback
             if (optRequestResult.isPresent()) {
                 RequestResult result = optRequestResult.get();
                 int errorCode = result.getErrorCode().orElse(DEFAULT_ERROR_CODE);
                 long retryAfter = result.getRetryMillis().orElse(0L);
-                triggerErrorCallback(errorCode, retryAfter);
+                triggerErrorCallback(errorCode, retryAfter, details);
             } else {
-                triggerCompletedCallback();
+                triggerCompletedCallback(details);
             }
 
             // Notify UceRequestManager to remove this instance from the collection.
@@ -572,10 +590,10 @@
     /**
      * Trigger the onComplete callback to notify the request is completed.
      */
-    private void triggerCompletedCallback() {
+    private void triggerCompletedCallback(@Nullable SipDetails details) {
         try {
             logd("triggerCompletedCallback");
-            mCapabilitiesCallback.onComplete();
+            mCapabilitiesCallback.onComplete(details);
         } catch (RemoteException e) {
             logw("triggerCompletedCallback exception: " + e);
         } finally {
@@ -586,10 +604,11 @@
     /**
      * Trigger the onError callback to notify the request is failed.
      */
-    private void triggerErrorCallback(int errorCode, long retryAfterMillis) {
+    private void triggerErrorCallback(int errorCode, long retryAfterMillis,
+            @Nullable SipDetails details) {
         try {
             logd("triggerErrorCallback: errorCode=" + errorCode + ", retry=" + retryAfterMillis);
-            mCapabilitiesCallback.onError(errorCode, retryAfterMillis);
+            mCapabilitiesCallback.onError(errorCode, retryAfterMillis, details);
         } catch (RemoteException e) {
             logw("triggerErrorCallback exception: " + e);
         } finally {
diff --git a/src/java/com/android/ims/rcs/uce/request/UceRequestCoordinator.java b/src/java/com/android/ims/rcs/uce/request/UceRequestCoordinator.java
index eea4fbe..5d3a35d 100644
--- a/src/java/com/android/ims/rcs/uce/request/UceRequestCoordinator.java
+++ b/src/java/com/android/ims/rcs/uce/request/UceRequestCoordinator.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.telephony.ims.RcsUceAdapter;
+import android.telephony.ims.SipDetails;
 import android.util.Log;
 
 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
@@ -135,6 +136,15 @@
         }
 
         /**
+         * Create a RequestResult that successfully completes the request.
+         * @param taskId the task id of the UceRequest
+         * @param details The SIP information related to this request.
+         */
+        public static RequestResult createSuccessResult(long taskId, @Nullable SipDetails details) {
+            return new RequestResult(taskId, details);
+        }
+
+        /**
          * Create a RequestResult for the failed request.
          * @param taskId the task id of the UceRequest
          * @param errorCode the error code of the failed request
@@ -144,29 +154,66 @@
             return new RequestResult(taskId, errorCode, retry);
         }
 
+        /**
+         * Create a RequestResult for the failed request.
+         * @param taskId the task id of the UceRequest
+         * @param errorCode the error code of the failed request
+         * @param retry When the request can be retried.
+         * @param details The SIP information related to this request.
+         */
+        public static RequestResult createFailedResult(long taskId, int errorCode, long retry,
+                @Nullable SipDetails details) {
+            return new RequestResult(taskId, errorCode, retry, details);
+        }
+
         private final Long mTaskId;
         private final Boolean mIsSuccess;
         private final Optional<Integer> mErrorCode;
         private final Optional<Long> mRetryMillis;
+        private final Optional<SipDetails> mSipDetails;
 
         /**
          * The private constructor for the successful request.
          */
         private RequestResult(long taskId) {
+            this(taskId, null);
+        }
+
+        /**
+         * The private constructor for the successful request.
+         */
+        private RequestResult(long taskId, SipDetails details) {
             mTaskId = taskId;
             mIsSuccess = true;
             mErrorCode = Optional.empty();
             mRetryMillis = Optional.empty();
+            if (details == null) {
+                mSipDetails = Optional.empty();
+            } else {
+                mSipDetails = Optional.ofNullable(details);
+            }
         }
 
         /**
          * The private constructor for the failed request.
          */
         private RequestResult(long taskId, int errorCode, long retryMillis) {
+            this(taskId, errorCode, retryMillis, null);
+        }
+
+        /**
+         * The private constructor for the failed request.
+         */
+        private RequestResult(long taskId, int errorCode, long retryMillis, SipDetails details) {
             mTaskId = taskId;
             mIsSuccess = false;
             mErrorCode = Optional.of(errorCode);
             mRetryMillis = Optional.of(retryMillis);
+            if (details == null) {
+                mSipDetails = Optional.empty();
+            } else {
+                mSipDetails = Optional.ofNullable(details);
+            }
         }
 
         public long getTaskId() {
@@ -184,6 +231,10 @@
         public Optional<Long> getRetryMillis() {
             return mRetryMillis;
         }
+
+        public Optional<SipDetails> getSipDetails() {
+            return mSipDetails;
+        }
     }
 
     // The default capability error code.
diff --git a/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java b/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java
index 85908f0..8ff39c1 100644
--- a/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java
+++ b/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java
@@ -478,7 +478,7 @@
     public void sendCapabilityRequest(List<Uri> uriList, boolean skipFromCache,
             IRcsUceControllerCallback callback) throws RemoteException {
         if (mIsDestroyed) {
-            callback.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L);
+            callback.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L, null);
             return;
         }
         sendRequestInternal(UceRequest.REQUEST_TYPE_CAPABILITY, uriList, skipFromCache, callback);
@@ -490,7 +490,7 @@
     public void sendAvailabilityRequest(Uri uri, IRcsUceControllerCallback callback)
             throws RemoteException {
         if (mIsDestroyed) {
-            callback.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L);
+            callback.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L, null);
             return;
         }
         sendRequestInternal(UceRequest.REQUEST_TYPE_AVAILABILITY,
@@ -511,7 +511,7 @@
             }
             if (nonCachedUris.isEmpty()) {
                 logd("sendRequestInternal: shortcut complete, sending success result");
-                callback.onComplete();
+                callback.onComplete(null);
                 return;
             }
         }
@@ -525,7 +525,7 @@
 
         if (requestCoordinator == null) {
             logw("sendRequestInternal: Neither Presence nor OPTIONS are supported");
-            callback.onError(RcsUceAdapter.ERROR_NOT_ENABLED, 0L);
+            callback.onError(RcsUceAdapter.ERROR_NOT_ENABLED, 0L, null);
             return;
         }
 
diff --git a/tests/src/com/android/ims/ContextFixture.java b/tests/src/com/android/ims/ContextFixture.java
index 48cb9ab..21dd634 100644
--- a/tests/src/com/android/ims/ContextFixture.java
+++ b/tests/src/com/android/ims/ContextFixture.java
@@ -138,6 +138,12 @@
         }
 
         @Override
+        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+                String broadcastPermission, Handler scheduler, int flags) {
+            return null;
+        }
+
+        @Override
         public void unregisterReceiver(BroadcastReceiver receiver) {
         }
 
diff --git a/tests/src/com/android/ims/ImsManagerTest.java b/tests/src/com/android/ims/ImsManagerTest.java
index 0653908..ad5051b 100644
--- a/tests/src/com/android/ims/ImsManagerTest.java
+++ b/tests/src/com/android/ims/ImsManagerTest.java
@@ -119,7 +119,7 @@
 
         doReturn(true).when(mSubscriptionManagerProxy).isValidSubscriptionId(anyInt());
         doReturn(mSubId).when(mSubscriptionManagerProxy).getActiveSubscriptionIdList();
-        doReturn(mSubId).when(mSubscriptionManagerProxy).getSubscriptionIds(anyInt());
+        doReturn(mSubId[0]).when(mSubscriptionManagerProxy).getSubscriptionId(anyInt());
         doReturn(mPhoneId).when(mSubscriptionManagerProxy).getDefaultVoicePhoneId();
         doReturn(-1).when(mSubscriptionManagerProxy).getIntegerSubscriptionProperty(anyInt(),
                 anyString(), anyInt());
@@ -1012,6 +1012,14 @@
 
     }
 
+    @Test @SmallTest
+    public void onMemoryAvailableTest() throws Exception{
+        ImsManager imsManager = getImsManagerAndInitProvisionedValues();
+        int token = 1;
+        imsManager.onMemoryAvailable(token);
+        verify(mMmTelFeatureConnection).onMemoryAvailable(eq(token));
+    }
+
     private ImsManager getImsManagerAndInitProvisionedValues() {
         when(mImsConfigImplBaseMock.getConfigInt(anyInt()))
                 .thenAnswer(invocation ->  {
diff --git a/tests/src/com/android/ims/rcs/uce/UceControllerTest.java b/tests/src/com/android/ims/rcs/uce/UceControllerTest.java
index 021a7c1..79702ed 100644
--- a/tests/src/com/android/ims/rcs/uce/UceControllerTest.java
+++ b/tests/src/com/android/ims/rcs/uce/UceControllerTest.java
@@ -147,7 +147,7 @@
         List<Uri> uriList = new ArrayList<>();
         uceController.requestCapabilities(uriList, mCapabilitiesCallback);
 
-        verify(mCapabilitiesCallback).onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L);
+        verify(mCapabilitiesCallback).onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L, null);
         verify(mTaskManager, never()).sendCapabilityRequest(any(), eq(false), any());
     }
 
@@ -164,7 +164,7 @@
         uriList.add(Uri.fromParts("sip", "test", null));
         uceController.requestCapabilities(uriList, mCapabilitiesCallback);
 
-        verify(mCapabilitiesCallback).onError(RcsUceAdapter.ERROR_FORBIDDEN, 0L);
+        verify(mCapabilitiesCallback).onError(RcsUceAdapter.ERROR_FORBIDDEN, 0L, null);
         verify(mTaskManager, never()).sendCapabilityRequest(any(), eq(false), any());
     }
 
@@ -194,7 +194,7 @@
         Uri contact = Uri.fromParts("sip", "test", null);
         uceController.requestAvailability(contact, mCapabilitiesCallback);
 
-        verify(mCapabilitiesCallback).onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L);
+        verify(mCapabilitiesCallback).onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L, null);
         verify(mTaskManager, never()).sendAvailabilityRequest(any(), any());
     }
 
@@ -210,7 +210,7 @@
         Uri contact = Uri.fromParts("sip", "test", null);
         uceController.requestAvailability(contact, mCapabilitiesCallback);
 
-        verify(mCapabilitiesCallback).onError(RcsUceAdapter.ERROR_FORBIDDEN, 0L);
+        verify(mCapabilitiesCallback).onError(RcsUceAdapter.ERROR_FORBIDDEN, 0L, null);
         verify(mTaskManager, never()).sendCapabilityRequest(any(), eq(false), any());
     }
 
diff --git a/tests/src/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityInfoTest.java b/tests/src/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityInfoTest.java
index c977a08..de045de 100644
--- a/tests/src/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityInfoTest.java
+++ b/tests/src/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityInfoTest.java
@@ -16,6 +16,19 @@
 
 package com.android.ims.rcs.uce.presence.publish;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Context;
+import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
+import android.telephony.ims.RcsContactPresenceTuple;
+import android.util.ArraySet;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
@@ -26,6 +39,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.ims.ImsTestBase;
+import com.android.ims.rcs.uce.util.UceUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -33,11 +47,18 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
 @RunWith(AndroidJUnit4.class)
 public class DeviceCapabilityInfoTest extends ImsTestBase {
-
     int mSubId = 1;
 
+    @Mock PublishServiceDescTracker mPublishServiceDescTracker;
+    @Mock Context mMockContext;
+
     String sipNumber = "123456789";
     String telNumber = "987654321";
 
@@ -53,6 +74,22 @@
 
     @Test
     @SmallTest
+    public void testGetPresenceCapabilityForSameDescription() throws Exception {
+        DeviceCapabilityInfo deviceCapInfo = createDeviceCapabilityInfo();
+
+        Set<ServiceDescription> mTestCapability = new ArraySet<>();
+        mTestCapability.add(getChatDescription());
+        mTestCapability.add(getMmtelDescription());
+        mTestCapability.add(getUndefinedDescription());
+
+        deviceCapInfo.addLastSuccessfulServiceDescription(getMmtelDescription());
+        deviceCapInfo.addLastSuccessfulServiceDescription(getChatDescription());
+        deviceCapInfo.addLastSuccessfulServiceDescription(getUndefinedDescription());
+        assertFalse(deviceCapInfo.isPresenceCapabilityChanged(mTestCapability));
+    }
+
+    @Test
+    @SmallTest
     public void testGetImsAssociatedUriWithoutPreferTelUri() throws Exception {
         DeviceCapabilityInfo deviceCapInfo = createDeviceCapabilityInfo();
 
@@ -90,6 +127,23 @@
 
     @Test
     @SmallTest
+    public void testGetPresenceCapabilityForSameSizeOfDescription() throws Exception {
+        DeviceCapabilityInfo deviceCapInfo = createDeviceCapabilityInfo();
+
+        Set<ServiceDescription> mTestCapability = new ArraySet<>();
+        mTestCapability.add(getChatDescription());
+        mTestCapability.add(getMmtelDescription());
+        mTestCapability.add(getUndefinedDescription());
+
+        deviceCapInfo.addLastSuccessfulServiceDescription(getMmtelDescription());
+        deviceCapInfo.addLastSuccessfulServiceDescription(getChatDescription());
+        deviceCapInfo.addLastSuccessfulServiceDescription(getUndefined2Description());
+
+        assertTrue(deviceCapInfo.isPresenceCapabilityChanged(mTestCapability));
+    }
+
+    @Test
+    @SmallTest
     public void testGetImsAssociatedUriWithPreferTelUri() throws Exception {
         DeviceCapabilityInfo deviceCapInfo = createDeviceCapabilityInfo();
 
@@ -138,7 +192,43 @@
         number = numberParts[0];
 
         assertEquals(number, telNumber);
+    }
 
+    @Test
+    @SmallTest
+    public void testGetLastSuccessfulPresenceTuples() throws Exception {
+         DeviceCapabilityInfo deviceCapInfo = createDeviceCapabilityInfo();
+
+        List<RcsContactPresenceTuple> tuples =
+                deviceCapInfo.getLastSuccessfulPresenceTuplesWithoutContactUri();
+        // Verify that the presence tuples are empty when the last capabilities are empty.
+        assertTrue(tuples.isEmpty());
+
+        doReturn(null).when(mMockContext).getSystemService(CarrierConfigManager.class);
+        doReturn(null).when(mMockContext).getSystemService(TelephonyManager.class);
+        ServiceDescription mmtelDescription = getMmtelDescription();
+        deviceCapInfo.addLastSuccessfulServiceDescription(mmtelDescription);
+        ServiceDescription chatDescription = getChatDescription();
+        deviceCapInfo.addLastSuccessfulServiceDescription(chatDescription);
+        tuples = deviceCapInfo.getLastSuccessfulPresenceTuplesWithoutContactUri();
+        assertEquals(2, tuples.size());
+
+        Uri[] uris = new Uri[1];
+        uris[0] = Uri.fromParts(PhoneAccount.SCHEME_SIP, sipNumber, null);
+        deviceCapInfo.updateRcsAssociatedUri(uris);
+        List<RcsContactPresenceTuple> expectedTuples = new ArrayList<>();
+        expectedTuples.add(mmtelDescription.getTupleBuilder().setContactUri(uris[0]).build());
+        expectedTuples.add(chatDescription.getTupleBuilder().setContactUri(uris[0]).build());
+
+        tuples = deviceCapInfo.getLastSuccessfulPresenceTuplesWithoutContactUri();
+        assertTrue(!tuples.isEmpty());
+        assertEquals(expectedTuples.size(), tuples.size());
+        for (int i = 0; i < tuples.size(); i++) {
+            assertEquals(expectedTuples.get(i).getServiceId(), tuples.get(i).getServiceId());
+            assertEquals(expectedTuples.get(i).getServiceVersion(),
+                    tuples.get(i).getServiceVersion());
+            assertNull(tuples.get(i).getContactUri());
+        }
     }
 
     private DeviceCapabilityInfo createDeviceCapabilityInfo() {
@@ -146,4 +236,40 @@
         return deviceCapInfo;
     }
 
+    private ServiceDescription getChatDescription() {
+        ServiceDescription SERVICE_DESCRIPTION_CHAT_SESSION =
+                new ServiceDescription(
+                        RcsContactPresenceTuple.SERVICE_ID_CHAT_V2,
+                        "2.0" /*version*/,
+                        null /*description*/
+                );
+        return SERVICE_DESCRIPTION_CHAT_SESSION;
+    }
+
+    private ServiceDescription getMmtelDescription() {
+        ServiceDescription SERVICE_DESCRIPTION_MMTEL_VOICE = new ServiceDescription(
+                RcsContactPresenceTuple.SERVICE_ID_MMTEL,
+                "1.0" /*version*/,
+                "Voice Service" /*description*/
+        );
+        return SERVICE_DESCRIPTION_MMTEL_VOICE;
+    }
+
+    private ServiceDescription getUndefinedDescription() {
+        ServiceDescription SERVICE_DESCRIPTION_TEST = new ServiceDescription(
+                "test",
+                "1.0" /*version*/,
+                "Test_Service" /*description*/
+        );
+        return SERVICE_DESCRIPTION_TEST;
+    }
+
+    private ServiceDescription getUndefined2Description() {
+        ServiceDescription SERVICE_DESCRIPTION_TEST2 = new ServiceDescription(
+                "test1",
+                "1.0" /*version*/,
+                "Test_Service" /*description*/
+        );
+        return SERVICE_DESCRIPTION_TEST2;
+    }
 }
\ No newline at end of file
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 4b3caeb..1135039 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
@@ -107,7 +107,7 @@
         deviceCapListener.initialize();
 
         verify(mContext).registerReceiver(any(), any(),
-            eq(android.Manifest.permission.MODIFY_PHONE_STATE), any());
+            eq(android.Manifest.permission.MODIFY_PHONE_STATE), any(), anyInt());
         verify(mProvisioningManager).registerProvisioningChangedCallback(any(), any());
     }
 
@@ -154,7 +154,8 @@
         waitForHandlerActionDelayed(handler, HANDLER_WAIT_TIMEOUT_MS, HANDLER_SENT_DELAY_MS);
 
         verify(mDeviceCapability).updateImsMmtelRegistered(1);
-        verify(mCallback).requestPublishFromInternal(
+        // update capability, but not trigger PUBLISH message when MmTel registered.
+        verify(mCallback, never()).requestPublishFromInternal(
                 PublishController.PUBLISH_TRIGGER_MMTEL_REGISTERED);
     }
 
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 a6e3d0b..aa145c7 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
@@ -22,6 +22,8 @@
 import static junit.framework.Assert.assertFalse;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -34,6 +36,7 @@
 import android.os.Looper;
 import android.os.RemoteCallbackList;
 import android.telephony.ims.RcsUceAdapter;
+import android.telephony.ims.SipDetails;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
 import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities;
@@ -137,7 +140,7 @@
         assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED, initState);
 
         publishController.getPublishControllerCallback().updatePublishRequestResult(
-                RcsUceAdapter.PUBLISH_STATE_OK, Instant.now(), null);
+                RcsUceAdapter.PUBLISH_STATE_OK, Instant.now(), null, null);
         Handler handler = publishController.getPublishHandler();
         waitForHandlerAction(handler, 1000);
 
@@ -154,7 +157,7 @@
         assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED, initState);
 
         publishController.getPublishControllerCallback().updatePublishRequestResult(
-                RcsUceAdapter.PUBLISH_STATE_PUBLISHING, Instant.now(), null);
+                RcsUceAdapter.PUBLISH_STATE_PUBLISHING, Instant.now(), null, null);
         Handler handler = publishController.getPublishHandler();
         waitForHandlerAction(handler, 1000);
 
@@ -223,8 +226,10 @@
     public void testPublishUpdated() throws Exception {
         PublishControllerImpl publishController = createPublishController();
         int responseCode = 200;
+        String responsePhrase = "OK";
 
-        publishController.onPublishUpdated(responseCode, "", 0, "");
+        publishController.onPublishUpdated(new SipDetails.Builder(SipDetails.METHOD_PUBLISH)
+                .setSipResponseCode(responseCode, responsePhrase).build());
 
         Handler handler = publishController.getPublishHandler();
         waitForHandlerAction(handler, 1000);
@@ -235,7 +240,57 @@
         verify(mPublishProcessor).publishUpdated(captor.capture());
         PublishRequestResponse response = captor.getValue();
         int expectedCode = response.getNetworkRespSipCode().orElse(-1);
+        String expectedPhrase = response.getReasonPhrase().orElse("");
         assertEquals(responseCode, expectedCode);
+        assertEquals(responsePhrase, expectedPhrase);
+        SipDetails details = response.getSipDetails().orElse(null);
+        assertNotNull(details);
+        assertEquals(responseCode, details.getResponseCode());
+        assertEquals(responsePhrase, details.getResponsePhrase());
+    }
+
+    @Test
+    @SmallTest
+    public void testPublishUpdatedWithSipDetails() throws Exception {
+        PublishControllerImpl publishController = createPublishController();
+        int responseCode = 200;
+        String responsePhrase = "OK";
+        int reasonHdrCause = 7;
+        String reasonHdrText = "reasonHeaderText";
+        int cseq = 10;
+        String callId = "TestCallId";
+
+        publishController.onPublishUpdated(new SipDetails.Builder(SipDetails.METHOD_PUBLISH)
+                .setCSeq(cseq).setSipResponseCode(responseCode, responsePhrase).setCallId(callId)
+                .setSipResponseReasonHeader(reasonHdrCause, reasonHdrText).build());
+
+        Handler handler = publishController.getPublishHandler();
+        waitForHandlerAction(handler, 1000);
+
+        ArgumentCaptor<PublishRequestResponse> captor =
+                ArgumentCaptor.forClass(PublishRequestResponse.class);
+
+        verify(mPublishProcessor).publishUpdated(captor.capture());
+        PublishRequestResponse response = captor.getValue();
+        int expectedCode = response.getNetworkRespSipCode().orElse(-1);
+        String expectedPhrase = response.getReasonPhrase().orElse("");
+        int expectedReasonCause = response.getReasonHeaderCause().orElse(-1);
+        String expectedReasonText = response.getReasonHeaderText().orElse("");
+
+        assertEquals(responseCode, expectedCode);
+        assertEquals(responsePhrase, expectedPhrase);
+        assertEquals(reasonHdrCause, expectedReasonCause);
+        assertEquals(reasonHdrText, expectedReasonText);
+
+        SipDetails details = response.getSipDetails().orElse(null);
+        assertNotNull(details);
+        assertEquals(SipDetails.METHOD_PUBLISH, details.getMethod());
+        assertEquals(cseq, details.getCSeq());
+        assertEquals(responseCode, details.getResponseCode());
+        assertEquals(responsePhrase, details.getResponsePhrase());
+        assertEquals(reasonHdrCause, details.getReasonHeaderCause());
+        assertEquals(reasonHdrText, details.getReasonHeaderText());
+        assertEquals(callId, details.getCallId());
     }
 
     @Test
diff --git a/tests/src/com/android/ims/rcs/uce/presence/publish/PublishProcessorTest.java b/tests/src/com/android/ims/rcs/uce/presence/publish/PublishProcessorTest.java
index 4e8cdfd..2de4714 100644
--- a/tests/src/com/android/ims/rcs/uce/presence/publish/PublishProcessorTest.java
+++ b/tests/src/com/android/ims/rcs/uce/presence/publish/PublishProcessorTest.java
@@ -30,6 +30,7 @@
 import android.net.Uri;
 import android.telephony.ims.RcsContactPresenceTuple;
 import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.SipDetails;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -82,6 +83,7 @@
 
         doReturn(true).when(mDeviceCapabilities).isImsRegistered();
         RcsContactUceCapability capability = getRcsContactUceCapability();
+        doReturn(capability).when(mDeviceCapabilities).getChangedPresenceCapability(any());
         doReturn(capability).when(mDeviceCapabilities).getDeviceCapabilities(anyInt(), any());
 
         doReturn(mTaskId).when(mResponseCallback).getTaskId();
@@ -112,7 +114,7 @@
         PublishProcessor publishProcessor = getPublishProcessor();
 
         publishProcessor.doPublish(PublishController.PUBLISH_TRIGGER_RETRY);
-
+        verify(mDeviceCapabilities).getChangedPresenceCapability(any());
         verify(mProcessorState, never()).resetRetryCount();
     }
 
@@ -139,7 +141,7 @@
 
         publishProcessor.onNetworkResponse(mResponseCallback);
 
-        verify(mPublishCtrlCallback).updatePublishRequestResult(anyInt(), any(), any());
+        verify(mPublishCtrlCallback).updatePublishRequestResult(anyInt(), any(), any(), eq(null));
         verify(mResponseCallback).onDestroy();
         verify(mProcessorState).setPublishingFlag(false);
         verify(mPublishCtrlCallback).clearRequestCanceledTimer();
@@ -171,6 +173,7 @@
 
         publishProcessor.onCommandError(mResponseCallback);
 
+        verify(mDeviceCapabilities).setPresencePublishResult(false);
         verify(mProcessorState).increaseRetryCount();
         verify(mPublishCtrlCallback).requestPublishFromInternal(
                 eq(PublishController.PUBLISH_TRIGGER_RETRY));
@@ -188,11 +191,13 @@
         doReturn(mTaskId).when(mProcessorState).getCurrentTaskId();
         doReturn(mTaskId).when(mResponseCallback).getTaskId();
         doReturn(false).when(mResponseCallback).needRetry();
+        doReturn(true).when(mResponseCallback).isRequestSuccess();
         PublishProcessor publishProcessor = getPublishProcessor();
 
         publishProcessor.onCommandError(mResponseCallback);
 
-        verify(mPublishCtrlCallback).updatePublishRequestResult(anyInt(), any(), any());
+        verify(mDeviceCapabilities).setPresencePublishResult(true);
+        verify(mPublishCtrlCallback).updatePublishRequestResult(anyInt(), any(), any(), eq(null));
         verify(mResponseCallback).onDestroy();
         verify(mProcessorState).setPublishingFlag(false);
         verify(mPublishCtrlCallback).clearRequestCanceledTimer();
@@ -209,6 +214,7 @@
 
         publishProcessor.onNetworkResponse(mResponseCallback);
 
+        verify(mDeviceCapabilities).setPresencePublishResult(false);
         verify(mProcessorState).increaseRetryCount();
         verify(mPublishCtrlCallback).requestPublishFromInternal(
                 eq(PublishController.PUBLISH_TRIGGER_RETRY));
@@ -226,11 +232,20 @@
         doReturn(false).when(mResponseCallback).needRetry();
         doReturn(true).when(mResponseCallback).isRequestSuccess();
         doReturn(Optional.of(200)).when(mResponseCallback).getNetworkRespSipCode();
+
+        SipDetails details = new SipDetails.Builder(SipDetails.METHOD_PUBLISH)
+                .setCSeq(10).setSipResponseCode(200, "OK").setCallId("CallId").build();
+        Optional<SipDetails> sipDetails = Optional.ofNullable(details);
+        doReturn(sipDetails).when(mResponseCallback).getSipDetails();
+
         PublishProcessor publishProcessor = getPublishProcessor();
 
         publishProcessor.onNetworkResponse(mResponseCallback);
+        SipDetails actualInfo = sipDetails.orElse(null);
 
-        verify(mPublishCtrlCallback).updatePublishRequestResult(anyInt(), any(), any());
+        verify(mDeviceCapabilities).setPresencePublishResult(true);
+        verify(mPublishCtrlCallback).updatePublishRequestResult(anyInt(), any(), any(),
+                eq(actualInfo));
         verify(mResponseCallback).onDestroy();
         verify(mProcessorState).setPublishingFlag(false);
         verify(mPublishCtrlCallback).clearRequestCanceledTimer();
@@ -260,13 +275,21 @@
         doReturn(0).when(mResponseCallback).getPublishState();
         doReturn("").when(mResponseCallback).getPidfXml();
 
+        SipDetails details = new SipDetails.Builder(SipDetails.METHOD_PUBLISH)
+                .setCSeq(10).setSipResponseCode(200, "").setCallId("CallId").build();
+        Optional<SipDetails> sipDetails = Optional.ofNullable(details);
+        doReturn(sipDetails).when(mResponseCallback).getSipDetails();
+
         PublishProcessor publishProcessor = getPublishProcessor();
 
         publishProcessor.publishUpdated(mResponseCallback);
+        SipDetails actualInfo = sipDetails.orElse(null);
 
+        verify(mDeviceCapabilities).setPresencePublishResult(true);
         verify(mProcessorState).setLastPublishedTime(any());
         verify(mProcessorState).resetRetryCount();
-        verify(mPublishCtrlCallback).updatePublishRequestResult(anyInt(), any(), any());
+        verify(mPublishCtrlCallback).updatePublishRequestResult(anyInt(), any(), any(),
+                eq(actualInfo));
     }
 
     private PublishProcessor getPublishProcessor() {
diff --git a/tests/src/com/android/ims/rcs/uce/request/SubscribeCoordinatorTest.java b/tests/src/com/android/ims/rcs/uce/request/SubscribeCoordinatorTest.java
index bcd3c98..2c0042e 100644
--- a/tests/src/com/android/ims/rcs/uce/request/SubscribeCoordinatorTest.java
+++ b/tests/src/com/android/ims/rcs/uce/request/SubscribeCoordinatorTest.java
@@ -25,14 +25,11 @@
 import static com.android.ims.rcs.uce.request.UceRequestCoordinator.REQUEST_UPDATE_RESOURCE_TERMINATED;
 import static com.android.ims.rcs.uce.request.UceRequestCoordinator.REQUEST_UPDATE_TERMINATED;
 
-import static java.lang.Boolean.TRUE;
-
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -41,6 +38,7 @@
 import android.net.Uri;
 import android.telephony.ims.RcsContactPresenceTuple;
 import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.SipDetails;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -49,21 +47,23 @@
 import com.android.ims.ImsTestBase;
 import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult;
 import com.android.ims.rcs.uce.UceStatsWriter;
+import com.android.ims.rcs.uce.eab.EabCapabilityResult;
 import com.android.ims.rcs.uce.request.UceRequestCoordinator.RequestResult;
 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+
 @RunWith(AndroidJUnit4.class)
 public class SubscribeCoordinatorTest extends ImsTestBase {
 
@@ -127,6 +127,17 @@
     @Test
     @SmallTest
     public void testRequestNetworkRespSuccess() throws Exception {
+        int responseCode = 200;
+        String responsePhrase = "OK";
+        int reasonHdrCause = 1;
+        String reasonHdrText = "reasonText";
+        int cSeq = 10;
+        String callId = "TestCallId";
+        SipDetails details = new SipDetails.Builder(SipDetails.METHOD_SUBSCRIBE).setCSeq(cSeq)
+                .setSipResponseCode(responseCode, responsePhrase).setCallId(callId)
+                .setSipResponseReasonHeader(reasonHdrCause, reasonHdrText).build();
+        doReturn(Optional.ofNullable(details)).when(mResponse).getSipDetails();
+
         SubscribeRequestCoordinator coordinator = getSubscribeCoordinator();
         doReturn(true).when(mResponse).isNetworkResponseOK();
         doReturn(Optional.of(200)).when(mResponse).getNetworkRespSipCode();
@@ -138,8 +149,21 @@
         assertEquals(1, requestList.size());
         assertTrue(resultList.isEmpty());
 
-        verify(mUceStatsWriter).setSubscribeResponse(eq(mSubId), eq(mTaskId), eq(200));
+        Iterator<RequestResult> requestResults = resultList.iterator();
+        while (requestResults.hasNext()) {
+            RequestResult req = requestResults.next();
+            SipDetails receivedInfo = req.getSipDetails().orElse(null);
+            assertNotNull(receivedInfo);
+            assertEquals(SipDetails.METHOD_SUBSCRIBE, receivedInfo.getMethod());
+            assertEquals(cSeq, receivedInfo.getCSeq());
+            assertEquals(responseCode, receivedInfo.getResponseCode());
+            assertEquals(responsePhrase, receivedInfo.getResponsePhrase());
+            assertEquals(reasonHdrCause, receivedInfo.getReasonHeaderCause());
+            assertEquals(reasonHdrText, receivedInfo.getReasonHeaderText());
+            assertEquals(callId, receivedInfo.getCallId());
+        }
 
+        verify(mUceStatsWriter).setSubscribeResponse(eq(mSubId), eq(mTaskId), eq(200));
         verify(mRequest, never()).onFinish();
     }
 
@@ -150,10 +174,20 @@
 
         SubscribeRequestCoordinator coordinator = getSubscribeCoordinator();
 
+        List<EabCapabilityResult> eabResultList = new ArrayList<>();
+
+        Uri contactUri = Uri.fromParts("tel", "123456789", null);
+        EabCapabilityResult result = new EabCapabilityResult(contactUri,
+                EabCapabilityResult.EAB_CONTACT_NOT_FOUND_FAILURE, null);
+        eabResultList.add(result);
+
+        doReturn(eabResultList).when(mRequestMgrCallback).
+                getCapabilitiesFromCacheIncludingExpired(any());
+
         coordinator.onRequestUpdated(mTaskId, REQUEST_UPDATE_NETWORK_RESPONSE);
 
         verify(mUceStatsWriter).setSubscribeResponse(eq(mSubId), eq(mTaskId), eq(400));
-
+        verify(mRequestMgrCallback, never()).saveCapabilities(any());
         verify(mRequest).onFinish();
     }
 
diff --git a/tests/src/com/android/ims/rcs/uce/request/SubscribeRequestTest.java b/tests/src/com/android/ims/rcs/uce/request/SubscribeRequestTest.java
index b4f9cca..543ad6d 100644
--- a/tests/src/com/android/ims/rcs/uce/request/SubscribeRequestTest.java
+++ b/tests/src/com/android/ims/rcs/uce/request/SubscribeRequestTest.java
@@ -19,7 +19,9 @@
 import static android.telephony.ims.stub.RcsCapabilityExchangeImplBase.COMMAND_CODE_NOT_SUPPORTED;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -28,6 +30,7 @@
 import android.net.Uri;
 import android.telephony.ims.RcsContactTerminatedReason;
 import android.telephony.ims.RcsUceAdapter;
+import android.telephony.ims.SipDetails;
 import android.telephony.ims.aidl.ISubscribeResponseCallback;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -36,10 +39,6 @@
 import com.android.ims.ImsTestBase;
 import com.android.ims.rcs.uce.presence.subscribe.SubscribeController;
 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
-import com.android.ims.rcs.uce.util.NetworkSipCode;
-
-import java.util.ArrayList;
-import java.util.List;
 
 import org.junit.After;
 import org.junit.Before;
@@ -47,6 +46,9 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @RunWith(AndroidJUnit4.class)
 public class SubscribeRequestTest extends ImsTestBase {
 
@@ -88,6 +90,7 @@
         subscribeRequest.requestCapabilities(uriList);
 
         verify(mRequestResponse).setRequestInternalError(RcsUceAdapter.ERROR_GENERIC_FAILURE);
+        verify(mRequestResponse, never()).setSipDetails(any());
         verify(mRequestManagerCallback).notifyRequestError(eq(mCoordId), anyLong());
         verify(mSubscribeController, never()).requestCapabilities(any(), any());
     }
@@ -101,6 +104,7 @@
         callback.onCommandError(COMMAND_CODE_NOT_SUPPORTED);
 
         verify(mRequestResponse).setCommandError(COMMAND_CODE_NOT_SUPPORTED);
+        verify(mRequestResponse, never()).setSipDetails(any(SipDetails.class));
         verify(mRequestManagerCallback).notifyCommandError(eq(mCoordId), anyLong());
     }
 
@@ -109,12 +113,16 @@
     public void testNetworkResponse() throws Exception {
         SubscribeRequest subscribeRequest = getSubscribeRequest();
 
-        int sipCode = NetworkSipCode.SIP_CODE_FORBIDDEN;
-        String reason = "forbidden";
-        ISubscribeResponseCallback callback = subscribeRequest.getResponseCallback();
-        callback.onNetworkResponse(sipCode, reason);
+        int sipCode = 200;
+        String reason = "OK";
+        SipDetails details = new SipDetails.Builder(SipDetails.METHOD_SUBSCRIBE).setCSeq(1)
+                .setSipResponseCode(sipCode, reason).setCallId("callId").build();
 
-        verify(mRequestResponse).setNetworkResponseCode(sipCode, reason);
+        ISubscribeResponseCallback callback = subscribeRequest.getResponseCallback();
+        callback.onNetworkResponse(details);
+
+        verify(mRequestResponse).setSipDetails(eq(details));
+        verify(mRequestResponse, never()).setNetworkResponseCode(anyInt(), anyString());
         verify(mRequestManagerCallback).notifyNetworkResponse(eq(mCoordId), anyLong());
     }
 
@@ -130,6 +138,7 @@
         callback.onResourceTerminated(list);
 
         verify(mRequestResponse).addTerminatedResource(list);
+        verify(mRequestResponse, never()).setSipDetails(any());
         verify(mRequestManagerCallback).notifyResourceTerminated(eq(mCoordId), anyLong());
     }
 
@@ -144,6 +153,7 @@
         callback.onNotifyCapabilitiesUpdate(pidfXml);
 
         verify(mRequestResponse).addUpdatedCapabilities(any());
+        verify(mRequestResponse, never()).setSipDetails(any());
         verify(mRequestManagerCallback).notifyCapabilitiesUpdated(eq(mCoordId), anyLong());
     }
 
@@ -159,6 +169,7 @@
         callback.onTerminated(reason, retryAfterMillis);
 
         verify(mRequestResponse).setTerminated(reason, retryAfterMillis);
+        verify(mRequestResponse, never()).setSipDetails(any());
         verify(mRequestManagerCallback).notifyTerminated(eq(mCoordId), anyLong());
     }
 
diff --git a/tests/src/com/android/ims/rcs/uce/request/UceRequestManagerTest.java b/tests/src/com/android/ims/rcs/uce/request/UceRequestManagerTest.java
index b380eac..fa8214e 100644
--- a/tests/src/com/android/ims/rcs/uce/request/UceRequestManagerTest.java
+++ b/tests/src/com/android/ims/rcs/uce/request/UceRequestManagerTest.java
@@ -31,6 +31,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -133,7 +134,7 @@
         waitForHandlerAction(handler, 500L);
 
         verify(mUceRequest, never()).executeRequest();
-        verify(mCapabilitiesCallback).onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L);
+        verify(mCapabilitiesCallback).onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L, null);
     }
 
     /**
@@ -164,7 +165,7 @@
         verify(mCapabilitiesCallback).onCapabilitiesReceived(
                 cachedNumbers.stream().map(EabCapabilityResult::getContactCapabilities).collect(
                 Collectors.toList()));
-        verify(mCapabilitiesCallback).onComplete();
+        verify(mCapabilitiesCallback).onComplete(eq(null));
         // The cache should have been hit, so no network requests should have been generated.
         verify(mRequestRepository, never()).addRequestCoordinator(any());
     }
@@ -196,7 +197,7 @@
         waitForHandlerAction(handler, 500L);
         // Extract caps from EabCapabilityResult and ensure the Lists match.
         verify(mCapabilitiesCallback, never()).onCapabilitiesReceived(any());
-        verify(mCapabilitiesCallback, never()).onComplete();
+        verify(mCapabilitiesCallback, never()).onComplete(any());
         // A network request should have been generated for the expired contact.
         verify(mRequestRepository).addRequestCoordinator(any());
     }
@@ -240,7 +241,7 @@
         // Extract caps from EabCapabilityResult and ensure the Lists match.
         verify(mCapabilitiesCallback).onCapabilitiesReceived(
                 Collections.singletonList(cachedItem.getContactCapabilities()));
-        verify(mCapabilitiesCallback, never()).onComplete();
+        verify(mCapabilitiesCallback, never()).onComplete(any());
         // The cache should have been hit, but there was also entry that was not in the cache, so
         // ensure that is requested.
         verify(mRequestRepository).addRequestCoordinator(any());