Merge "Support bulk capability exchange in UCE"
diff --git a/src/java/com/android/ims/FeatureConnection.java b/src/java/com/android/ims/FeatureConnection.java
index 7103043..748ae57 100644
--- a/src/java/com/android/ims/FeatureConnection.java
+++ b/src/java/com/android/ims/FeatureConnection.java
@@ -32,6 +32,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.NoSuchElementException;
+
 /**
  * Base class of MmTelFeatureConnection and RcsFeatureConnection.
  */
@@ -79,7 +81,8 @@
                     mBinder.linkToDeath(mDeathRecipient, 0);
                 }
             } catch (RemoteException e) {
-                // No need to do anything if the binder is already dead.
+                Log.w(TAG, "setBinder: linkToDeath on already dead Binder, setting null");
+                mBinder = null;
             }
         }
     }
@@ -103,8 +106,12 @@
         synchronized (mLock) {
             if (mIsAvailable) {
                 mIsAvailable = false;
-                if (mBinder != null) {
-                    mBinder.unlinkToDeath(mDeathRecipient, 0);
+                try {
+                    if (mBinder != null) {
+                        mBinder.unlinkToDeath(mDeathRecipient, 0);
+                    }
+                } catch (NoSuchElementException e) {
+                    Log.w(TAG, "onRemovedOrDied: unlinkToDeath called on unlinked Binder.");
                 }
             }
         }
diff --git a/src/java/com/android/ims/ImsCall.java b/src/java/com/android/ims/ImsCall.java
index ea9318b..0d5b396 100755
--- a/src/java/com/android/ims/ImsCall.java
+++ b/src/java/com/android/ims/ImsCall.java
@@ -32,6 +32,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsCallSession;
+import android.telephony.ims.ImsCallSessionListener;
 import android.telephony.ims.ImsConferenceState;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsStreamMediaProfile;
@@ -94,11 +95,21 @@
      */
     public static class Listener {
         /**
-         * Called when a request is sent out to initiate a new call
-         * and 1xx response is received from the network.
+         * Called after the network first begins to establish the call session and is now connecting
+         * to the remote party.
          * The default implementation calls {@link #onCallStateChanged}.
-         *
-         * @param call the call object that carries out the IMS call
+         * <p/>
+         * see: {@link ImsCallSessionListener#callSessionInitiating}
+         */
+        public void onCallInitiating(ImsCall call) {
+            onCallStateChanged(call);
+        }
+
+        /**
+         * Called after the network has contacted the remote party.
+         * The default implementation calls {@link #onCallStateChanged}.
+         * <p/>
+         * see: {@link ImsCallSessionListener#callSessionProgressing}
          */
         public void onCallProgressing(ImsCall call) {
             onCallStateChanged(call);
@@ -2436,6 +2447,32 @@
     @VisibleForTesting
     public class ImsCallSessionListenerProxy extends ImsCallSession.Listener {
         @Override
+        public void callSessionInitiating(ImsCallSession session, ImsCallProfile profile) {
+            logi("callSessionInitiating :: session=" + session + " profile=" + profile);
+            if (isTransientConferenceSession(session)) {
+                // If it is a transient (conference) session, there is no action for this signal.
+                logi("callSessionInitiating :: not supported for transient conference session=" +
+                        session);
+                return;
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+                setCallProfile(profile);
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onCallInitiating(ImsCall.this);
+                } catch (Throwable t) {
+                    loge("callSessionInitiating :: ", t);
+                }
+            }
+        }
+
+        @Override
         public void callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile) {
             logi("callSessionProgressing :: session=" + session + " profile=" + profile);
 
diff --git a/src/java/com/android/ims/ImsManager.java b/src/java/com/android/ims/ImsManager.java
index 867a273..3dfa7fc 100644
--- a/src/java/com/android/ims/ImsManager.java
+++ b/src/java/com/android/ims/ImsManager.java
@@ -50,6 +50,7 @@
 import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.aidl.IImsSmsListener;
+import android.telephony.ims.aidl.IRcsConfigCallback;
 import android.telephony.ims.aidl.ISipTransport;
 import android.telephony.ims.feature.CapabilityChangeRequest;
 import android.telephony.ims.feature.ImsFeature;
@@ -865,6 +866,14 @@
     }
 
     /**
+     * Returns whether the user sets call composer setting per sub.
+     */
+    public boolean isCallComposerEnabledByUser() {
+        return new TelephonyManager(mContext, getSubId()).getCallComposerStatus() ==
+                TelephonyManager.CALL_COMPOSER_STATUS_ON;
+    }
+
+    /**
      * Change persistent VT enabled setting
      *
      * @deprecated Does not support MSIM devices. Please use {@link #setVtSetting(boolean)} instead.
@@ -1465,6 +1474,7 @@
             updateVolteFeatureValue(request);
             updateWfcFeatureAndProvisionedValues(request);
             updateVideoCallFeatureValue(request);
+            updateCallComposerFeatureValue(request);
             // Only turn on IMS for RTT if there's an active subscription present. If not, the
             // modem will be in emergency-call-only mode and will use separate signaling to
             // establish an RTT emergency call.
@@ -1642,6 +1652,31 @@
     }
 
     /**
+     * Update call composer capability
+     */
+    private void updateCallComposerFeatureValue(CapabilityChangeRequest request) {
+        boolean isUserSetEnabled = isCallComposerEnabledByUser();
+        boolean isCarrierConfigEnabled = getBooleanCarrierConfig(
+                CarrierConfigManager.KEY_SUPPORTS_CALL_COMPOSER_BOOL);
+
+        boolean isFeatureOn = isUserSetEnabled && isCarrierConfigEnabled;
+
+        log("updateCallComposerFeatureValue: isUserSetEnabled = " + isUserSetEnabled
+                + ", isCarrierConfigEnabled = " + isCarrierConfigEnabled
+                        + ", isFeatureOn = " + isFeatureOn);
+
+        if (isFeatureOn) {
+            request.addCapabilitiesToEnableForTech(
+                    MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER,
+                            ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+        } else {
+            request.addCapabilitiesToDisableForTech(
+                    MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER,
+                            ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+        }
+    }
+
+    /**
      * Do NOT use this directly, instead use {@link #getInstance(Context, int)}.
      */
     private ImsManager(Context context, int phoneId) {
@@ -2828,6 +2863,47 @@
                 provisionStatus);
     }
 
+    /**
+     * Adds a callback of RCS provisioning for a specified subscription.
+     * @param callback A {@link android.telephony.ims.aidl.IRcsConfigCallback}
+     *         for RCS provisioning change.
+     * @param subId The subscription that is associated with the callback.
+     * @throws IllegalStateException when the {@link ImsService} connection is not available.
+     * @throws IllegalArgumentException when the {@link IRcsConfigCallback} argument is null.
+     */
+    public void addRcsProvisioningCallbackForSubscription(IRcsConfigCallback callback, int subId) {
+        if (callback == null) {
+            throw new IllegalArgumentException("provisioning callback can't be null");
+        }
+
+        mMmTelConnectionRef.get().addRcsProvisioningCallbackForSubscription(callback, subId);
+        log("Capability Callback registered for subscription.");
+    }
+
+    /**
+     * Removes a previously registered {@link android.telephony.ims.aidl.IRcsConfigCallback}.
+     * @throws IllegalStateException when the {@link ImsService} connection is not available.
+     * @throws IllegalArgumentException when the {@link IRcsConfigCallback} argument is null.
+     */
+    public void removeRcsProvisioningCallbackForSubscription(
+            IRcsConfigCallback callback, int subId) {
+        if (callback == null) {
+            throw new IllegalArgumentException("provisioning callback can't be null");
+        }
+
+        mMmTelConnectionRef.get().removeRcsProvisioningCallbackForSubscription(callback, subId);
+    }
+
+    /**
+     * Removes all RCS provisioning callbacks
+     *
+     * <p>This method is called when default message application change or some other event
+     * which need force to remove all RCS provisioning callbacks.
+     */
+    public void clearRcsProvisioningCallbacks() {
+        mMmTelConnectionRef.get().clearRcsProvisioningCallbacks();
+    }
+
     private boolean isDataEnabled() {
         return new TelephonyManager(mContext, getSubId()).isDataConnectionAllowed();
     }
diff --git a/src/java/com/android/ims/MmTelFeatureConnection.java b/src/java/com/android/ims/MmTelFeatureConnection.java
index cc7eace..d4fd2f3 100644
--- a/src/java/com/android/ims/MmTelFeatureConnection.java
+++ b/src/java/com/android/ims/MmTelFeatureConnection.java
@@ -32,6 +32,7 @@
 import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.aidl.IImsSmsListener;
+import android.telephony.ims.aidl.IRcsConfigCallback;
 import android.telephony.ims.aidl.ISipTransport;
 import android.telephony.ims.feature.CapabilityChangeRequest;
 import android.telephony.ims.feature.ImsFeature;
@@ -197,6 +198,52 @@
         }
     }
 
+    private class RcsProvisioningCallbackManager
+            extends ImsCallbackAdapterManager<IRcsConfigCallback> {
+        public RcsProvisioningCallbackManager (Context context, Object lock) {
+            super(context, lock, mSlotId);
+        }
+
+        @Override
+        public void registerCallback(IRcsConfigCallback localCallback) {
+            IImsConfig binder = getConfig();
+            if (binder == null) {
+                // Config interface is not currently available.
+                Log.w(TAG + " [" + mSlotId + "]",
+                        "RcsProvisioningCallbackManager - couldn't register,"
+                        + " binder is null.");
+                throw new IllegalStateException("RcsConfig is not available!");
+            }
+            try {
+                binder.addRcsConfigCallback(localCallback);
+            }catch (RemoteException e) {
+                throw new IllegalStateException("ImsService is not available!");
+            }
+        }
+
+        @Override
+        public void unregisterCallback(IRcsConfigCallback localCallback) {
+            IImsConfig binder = getConfig();
+            if (binder != null) {
+                try {
+                    binder.removeRcsConfigCallback(localCallback);
+                } catch (RemoteException e) {
+                    Log.w(TAG + " [" + mSlotId + "]", "RcsProvisioningCallbackManager - couldn't"
+                            + " unregister, binder is dead.");
+                }
+            } else {
+                Log.w(TAG + " [" + mSlotId + "]", "RcsProvisioningCallbackManager - couldn't"
+                        + " unregister, binder is null.");
+            }
+            try {
+                localCallback.onRemoved();
+            } catch (RemoteException e) {
+                Log.w(TAG + " [" + mSlotId + "]", "RcsProvisioningCallbackManager - couldn't"
+                        + " notify onRemoved, binder is dead.");
+            }
+        }
+    }
+
     // Updated by IImsServiceFeatureCallback when FEATURE_EMERGENCY_MMTEL is sent.
     private boolean mSupportsEmergencyCalling = false;
     // MMTEL specific binder Interfaces
@@ -207,6 +254,7 @@
     private final ImsRegistrationCallbackAdapter mRegistrationCallbackManager;
     private final CapabilityCallbackManager mCapabilityCallbackManager;
     private final ProvisioningCallbackManager mProvisioningCallbackManager;
+    private final RcsProvisioningCallbackManager mRcsProvisioningCallbackManager;
 
     public MmTelFeatureConnection(Context context, int slotId, IImsMmTelFeature f,
             IImsConfig c, IImsRegistration r, ISipTransport s) {
@@ -216,6 +264,7 @@
         mRegistrationCallbackManager = new ImsRegistrationCallbackAdapter(context, mLock);
         mCapabilityCallbackManager = new CapabilityCallbackManager(context, mLock);
         mProvisioningCallbackManager = new ProvisioningCallbackManager(context, mLock);
+        mRcsProvisioningCallbackManager = new RcsProvisioningCallbackManager(context, mLock);
     }
 
     @Override
@@ -464,6 +513,20 @@
         }
     }
 
+    public void addRcsProvisioningCallbackForSubscription(IRcsConfigCallback callback,
+            int subId) {
+        mRcsProvisioningCallbackManager.addCallbackForSubscription(callback, subId);
+    }
+
+    public void removeRcsProvisioningCallbackForSubscription(IRcsConfigCallback callback,
+            int subId) {
+        mRcsProvisioningCallbackManager.removeCallbackForSubscription(callback , subId);
+    }
+
+    public void clearRcsProvisioningCallbacks() {
+        mRcsProvisioningCallbackManager.close();
+    }
+
     @Override
     protected Integer retrieveFeatureState() {
         if (mBinder != null) {
diff --git a/src/java/com/android/ims/RcsFeatureConnection.java b/src/java/com/android/ims/RcsFeatureConnection.java
index 1dfc1aa..368f22e 100644
--- a/src/java/com/android/ims/RcsFeatureConnection.java
+++ b/src/java/com/android/ims/RcsFeatureConnection.java
@@ -140,7 +140,8 @@
     public void setCapabilityExchangeEventListener(ICapabilityExchangeEventListener listener)
             throws RemoteException {
         synchronized (mLock) {
-            checkServiceIsReady();
+            // Only check if service is alive. The feature status may not be READY.
+            checkServiceIsAlive();
             getServiceInterface(mBinder).setCapabilityExchangeEventListener(listener);
         }
     }
@@ -153,6 +154,15 @@
         }
     }
 
+    private void checkServiceIsAlive() throws RemoteException {
+        if (!sImsSupportedOnDevice) {
+            throw new RemoteException("IMS is not supported on this device.");
+        }
+        if (!isBinderAlive()) {
+            throw new RemoteException("ImsServiceProxy is not alive.");
+        }
+    }
+
     public int queryCapabilityStatus() throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
diff --git a/src/java/com/android/ims/RcsFeatureManager.java b/src/java/com/android/ims/RcsFeatureManager.java
index cc519e8..b2faf90 100644
--- a/src/java/com/android/ims/RcsFeatureManager.java
+++ b/src/java/com/android/ims/RcsFeatureManager.java
@@ -34,6 +34,7 @@
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsRcsController;
 import android.telephony.ims.aidl.IImsRcsFeature;
+import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.aidl.IOptionsRequestCallback;
 import android.telephony.ims.aidl.IPublishResponseCallback;
@@ -160,8 +161,7 @@
 
     /**
      * Opens a persistent connection to the RcsFeature. This must be called before the RcsFeature
-     * can be used to communicate. Triggers a {@link RcsFeature#onFeatureReady()} call on the
-     * service side.
+     * can be used to communicate.
      */
     public void openConnection() throws android.telephony.ims.ImsException {
         try {
@@ -327,6 +327,10 @@
         return mRcsFeatureConnection.getSipTransport();
     }
 
+    public IImsRegistration getImsRegistration() {
+        return mRcsFeatureConnection.getRegistration();
+    }
+
     /**
      * Query for the specific capability.
      */
@@ -479,7 +483,7 @@
         if (capabilityType == CAPABILITY_OPTIONS) {
             return b.getBoolean(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL, false);
         } else if (capabilityType == CAPABILITY_PRESENCE) {
-            return b.getBoolean(CarrierConfigManager.KEY_USE_RCS_PRESENCE_BOOL, false);
+            return b.getBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL, false);
         }
         return false;
     }
diff --git a/src/java/com/android/ims/rcs/uce/UceController.java b/src/java/com/android/ims/rcs/uce/UceController.java
index ad04996..4d01899 100644
--- a/src/java/com/android/ims/rcs/uce/UceController.java
+++ b/src/java/com/android/ims/rcs/uce/UceController.java
@@ -26,7 +26,6 @@
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.RcsUceAdapter.PublishState;
 import android.telephony.ims.RcsUceAdapter.StackPublishTriggerType;
-import android.telephony.ims.RcsUceAdapter.PublishStateCallback;
 import android.telephony.ims.aidl.ICapabilityExchangeEventListener;
 import android.telephony.ims.aidl.IOptionsRequestCallback;
 import android.telephony.ims.aidl.IOptionsResponseCallback;
@@ -45,6 +44,7 @@
 import com.android.ims.rcs.uce.presence.subscribe.SubscribeController;
 import com.android.ims.rcs.uce.presence.subscribe.SubscribeControllerImpl;
 import com.android.ims.rcs.uce.request.UceRequestManager;
+import com.android.ims.rcs.uce.util.UceUtils;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.time.Duration;
@@ -59,7 +59,7 @@
  */
 public class UceController {
 
-    private static final String LOG_TAG = "UceController";
+    private static final String LOG_TAG = UceUtils.getLogPrefix() + "UceController";
 
     /**
      * The callback interface is called by the internal controllers to receive information from
@@ -579,8 +579,7 @@
                 } else {
                     mAllowedTimestamp = Instant.now().plus(retryAfterMillis, ChronoUnit.MILLIS);
                 }
-                Log.d(LOG_TAG, "forbidUceRequest: " + mIsForbidden
-                        + ", time=" + mAllowedTimestamp);
+                Log.d(LOG_TAG, "forbidUceRequest: " + mIsForbidden + ",time=" + mAllowedTimestamp);
             }
         }
 
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 b81aea8..389f104 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
@@ -18,10 +18,8 @@
 
 import android.content.Context;
 import android.net.Uri;
-import android.telecom.PhoneAccount;
 import android.telecom.TelecomManager;
 import android.telephony.AccessNetworkConstants;
-import android.telephony.TelephonyManager;
 import android.telephony.ims.RcsContactPresenceTuple;
 import android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities;
 import android.telephony.ims.RcsContactUceCapability;
@@ -30,11 +28,13 @@
 import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
 import android.util.Log;
 
+import com.android.ims.rcs.uce.util.UceUtils;
+
 /**
  * Stores the device's capabilities information.
  */
 public class DeviceCapabilityInfo {
-    private static final String LOG_TAG = "DeviceCapabilityInfo";
+    private static final String LOG_TAG = UceUtils.getLogPrefix() + "DeviceCapabilityInfo";
 
     private final int mSubId;
 
@@ -208,11 +208,13 @@
         boolean oldVoWifiAvailable = isVoWifiAvailable(mMmtelNetworkRegType, mMmTelCapabilities);
         boolean oldVtAvailable = isVtAvailable(mMmtelNetworkRegType, mMmTelCapabilities);
         boolean oldViWifiAvailable = isViWifiAvailable(mMmtelNetworkRegType, mMmTelCapabilities);
+        boolean oldCallComposerAvailable = isCallComposerAvailable(mMmTelCapabilities);
 
         boolean volteAvailable = isVolteAvailable(mMmtelNetworkRegType, capabilities);
         boolean voWifiAvailable = isVoWifiAvailable(mMmtelNetworkRegType, capabilities);
         boolean vtAvailable = isVtAvailable(mMmtelNetworkRegType, capabilities);
         boolean viWifiAvailable = isViWifiAvailable(mMmtelNetworkRegType, capabilities);
+        boolean callComposerAvailable = isCallComposerAvailable(capabilities);
 
         logd("updateMmtelCapabilitiesChanged: from " + mMmTelCapabilities + " to " + capabilities);
 
@@ -222,7 +224,8 @@
         if (oldVolteAvailable != volteAvailable
                 || oldVoWifiAvailable != voWifiAvailable
                 || oldVtAvailable != vtAvailable
-                || oldViWifiAvailable != viWifiAvailable) {
+                || oldViWifiAvailable != viWifiAvailable
+                || oldCallComposerAvailable != callComposerAvailable) {
             return true;
         }
         return false;
@@ -248,6 +251,11 @@
                 && capabilities.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO);
     }
 
+    private boolean isCallComposerAvailable(MmTelCapabilities capabilities) {
+        return capabilities.isCapable(
+                MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER);
+    }
+
     /**
      * Get the device's capabilities.
      */
@@ -266,10 +274,20 @@
                 RcsContactPresenceTuple.SERVICE_ID_MMTEL, "1.0");
         tupleBuilder.addContactUri(uri).addServiceCapabilities(servCapsBuilder.build());
 
+        RcsContactPresenceTuple.Builder callComposerTupleBuilder =
+                new RcsContactPresenceTuple.Builder(
+                        RcsContactPresenceTuple.TUPLE_BASIC_STATUS_OPEN,
+                        RcsContactPresenceTuple.SERVICE_ID_CALL_COMPOSER, "2.0");
+        callComposerTupleBuilder.addContactUri(uri).addServiceCapabilities(
+                servCapsBuilder.build());
+
         PresenceBuilder presenceBuilder = new PresenceBuilder(uri,
                 RcsContactUceCapability.SOURCE_TYPE_CACHED,
                 RcsContactUceCapability.REQUEST_RESULT_FOUND);
         presenceBuilder.addCapabilityTuple(tupleBuilder.build());
+        if (hasCallComposerCapability()) {
+            presenceBuilder.addCapabilityTuple(callComposerTupleBuilder.build());
+        }
 
         return presenceBuilder.build();
     }
@@ -292,6 +310,15 @@
         return false;
     }
 
+    // Check if the device has the Call Composer capability
+    private synchronized boolean hasCallComposerCapability() {
+        if (mMmTelCapabilities != null && mMmTelCapabilities.isCapable(
+                MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER)) {
+            return true;
+        }
+        return false;
+    }
+
     private synchronized MmTelCapabilities deepCopyCapabilities(MmTelCapabilities capabilities) {
         MmTelCapabilities mmTelCapabilities = new MmTelCapabilities();
         if (capabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VOICE)) {
@@ -306,6 +333,9 @@
         if (capabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_SMS)) {
             mmTelCapabilities.addCapabilities(MmTelCapabilities.CAPABILITY_TYPE_SMS);
         }
+        if (capabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER)) {
+            mmTelCapabilities.addCapabilities(MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER);
+        }
         return mmTelCapabilities;
     }
 
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 d1b5275..a9d5d90 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
@@ -40,6 +40,7 @@
 import android.util.Log;
 
 import com.android.ims.rcs.uce.presence.publish.PublishController.PublishControllerCallback;
+import com.android.ims.rcs.uce.util.UceUtils;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.util.HandlerExecutor;
 
@@ -49,7 +50,11 @@
  */
 public class DeviceCapabilityListener {
 
-    private static final String LOG_TAG = "DeviceCapListener";
+    private static final String LOG_TAG = UceUtils.getLogPrefix() + "DeviceCapListener";
+
+    // Delay to send the registered changed because the registered state changed of MMTEL and RCS
+    // may be called at the same time.
+    private static final long DELAY_SEND_IMS_REGISTERED_CHANGED_MSG = 500L;
 
     /**
      * Used to inject ImsMmTelManager instances for testing.
@@ -477,7 +482,8 @@
     private void handleImsMmtelRegistered(int imsTransportType) {
         mCapabilityInfo.updateImsMmtelRegistered(imsTransportType);
         mCallback.requestPublishFromInternal(
-                PublishController.PUBLISH_TRIGGER_MMTEL_REGISTERED, 0L);
+                PublishController.PUBLISH_TRIGGER_MMTEL_REGISTERED,
+                DELAY_SEND_IMS_REGISTERED_CHANGED_MSG);
     }
 
     /*
@@ -490,8 +496,8 @@
     }
 
     private void handleMmtelCapabilitiesStatusChanged(MmTelCapabilities capabilities) {
-        logi("MMTel capabilities status changed");
         boolean isChanged = mCapabilityInfo.updateMmtelCapabilitiesChanged(capabilities);
+        logi("MMTel capabilities status changed: isChanged=" + isChanged);
         if (isChanged) {
             mCallback.requestPublishFromInternal(
                     PublishController.PUBLISH_TRIGGER_MMTEL_CAPABILITY_CHANGE, 0L);
@@ -503,7 +509,9 @@
      */
     private void handleImsRcsRegistered(int imsTransportType) {
         mCapabilityInfo.updateImsRcsRegistered(imsTransportType);
-        mCallback.requestPublishFromInternal(PublishController.PUBLISH_TRIGGER_RCS_REGISTERED, 0L);
+        mCallback.requestPublishFromInternal(
+                PublishController.PUBLISH_TRIGGER_RCS_REGISTERED,
+                DELAY_SEND_IMS_REGISTERED_CHANGED_MSG);
     }
 
     /*
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 e4e33f0..f55a885 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
@@ -26,6 +26,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.Instant;
 
 /**
  * The interface related to the PUBLISH request.
@@ -118,7 +119,7 @@
         /**
          * Update the publish request result.
          */
-        void updatePublishRequestResult(int publishState);
+        void updatePublishRequestResult(int publishState, Instant updatedTimestamp);
     }
 
     /**
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 39494f9..9003bfd 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
@@ -31,11 +31,20 @@
 
 import com.android.ims.RcsFeatureManager;
 import com.android.ims.rcs.uce.UceController.UceControllerCallback;
+import com.android.ims.rcs.uce.util.UceUtils;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.lang.ref.WeakReference;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The implementation of PublishController.
+ */
 public class PublishControllerImpl implements PublishController {
 
-    private static final String LOG_TAG = "PublishController";
+    private static final String LOG_TAG = UceUtils.getLogPrefix() + "PublishController";
 
     /**
      * Used to inject PublishProcessor instances for testing.
@@ -63,6 +72,8 @@
 
     // The device publish state
     private @PublishState int mPublishState;
+    // The timestamp of updating the publish state
+    private Instant mPublishStateUpdatedTime = Instant.now();
 
     // The callbacks to notify publish state changed.
     private RemoteCallbackList<IRcsUcePublishStateCallback> mPublishStateCallbacks;
@@ -108,7 +119,7 @@
         mPublishState = RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED;
         mPublishStateCallbacks = new RemoteCallbackList<>();
 
-        mPublishHandler = new PublishHandler(looper);
+        mPublishHandler = new PublishHandler(this, looper);
         mDeviceCapabilityInfo = new DeviceCapabilityInfo(mSubId);
 
         initPublishProcessor();
@@ -167,7 +178,11 @@
         synchronized (mPublishStateLock) {
             if (mIsDestroyedFlag) return;
             mPublishStateCallbacks.register(c);
+            logd("registerPublishStateCallback: size="
+                    + mPublishStateCallbacks.getRegisteredCallbackCount());
         }
+        // Notify the current publish state
+        mPublishHandler.onNotifyCurrentPublishState(c);
     }
 
     /**
@@ -201,7 +216,8 @@
     public void onUnpublish() {
         logd("onUnpublish");
         if (mIsDestroyedFlag) return;
-        mPublishHandler.onPublishStateChanged(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED);
+        mPublishHandler.onPublishStateChanged(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED,
+                Instant.now());
     }
 
     @Override
@@ -220,13 +236,15 @@
 
                 @Override
                 public void onRequestCommandError(PublishRequestResponse requestResponse) {
-                    logd("onRequestCommandError: taskId=" + requestResponse.getTaskId());
+                    logd("onRequestCommandError: taskId=" + requestResponse.getTaskId()
+                            + ", time=" + requestResponse.getResponseTimestamp());
                     mPublishHandler.onRequestCommandError(requestResponse);
                 }
 
                 @Override
                 public void onRequestNetworkResp(PublishRequestResponse requestResponse) {
-                    logd("onRequestNetworkResp: taskId=" + requestResponse.getTaskId());
+                    logd("onRequestNetworkResp: taskId=" + requestResponse.getTaskId()
+                            + ", time=" + requestResponse.getResponseTimestamp());
                     mPublishHandler.onRequestNetworkResponse(requestResponse);
                 }
 
@@ -243,9 +261,10 @@
                 }
 
                 @Override
-                public void updatePublishRequestResult(@PublishState int publishState) {
-                    logd("updatePublishRequestResult: " + publishState);
-                    mPublishHandler.onPublishStateChanged(publishState);
+                public void updatePublishRequestResult(@PublishState int publishState,
+                        Instant updatedTime) {
+                    logd("updatePublishRequestResult: " + publishState + ", time=" + updatedTime);
+                    mPublishHandler.onPublishStateChanged(publishState, updatedTime);
                 }
             };
 
@@ -254,49 +273,63 @@
      */
     @Override
     public void requestPublishCapabilitiesFromService(int triggerType) {
-        logi("Receive the publish request from service: " + triggerType);
+        logi("Receive the publish request from service: service trigger type=" + triggerType);
         mPublishHandler.requestPublish(PublishController.PUBLISH_TRIGGER_SERVICE);
     }
 
-    private class PublishHandler extends Handler {
+    private static class PublishHandler extends Handler {
         private static final int MSG_PUBLISH_STATE_CHANGED = 1;
-        private static final int MSG_REQUEST_PUBLISH = 2;
-        private static final int MSG_REQUEST_CMD_ERROR = 3;
-        private static final int MSG_REQUEST_SIP_RESPONSE = 4;
-        private static final int MSG_REQUEST_CANCELED = 5;
+        private static final int MSG_NOTIFY_CURRENT_PUBLISH_STATE = 2;
+        private static final int MSG_REQUEST_PUBLISH = 3;
+        private static final int MSG_REQUEST_CMD_ERROR = 4;
+        private static final int MSG_REQUEST_NETWORK_RESPONSE = 5;
+        private static final int MSG_REQUEST_CANCELED = 6;
 
-        public PublishHandler(Looper looper) {
+        private final WeakReference<PublishControllerImpl> mPublishControllerRef;
+
+        public PublishHandler(PublishControllerImpl publishController, Looper looper) {
             super(looper);
+            mPublishControllerRef = new WeakReference<>(publishController);
         }
 
         @Override
         public void handleMessage(Message message) {
-            if (mIsDestroyedFlag) return;
-            logd("handleMessage: " + message.what);
+            PublishControllerImpl publishCtrl = mPublishControllerRef.get();
+            if (publishCtrl == null) {
+                return;
+            }
+            if (publishCtrl.mIsDestroyedFlag) return;
+            publishCtrl.logd("handleMessage: " + EVENT_DESCRIPTION.get(message.what));
             switch (message.what) {
                 case MSG_PUBLISH_STATE_CHANGED:
                     int newPublishState = message.arg1;
-                    handlePublishStateChangedMessage(newPublishState);
+                    Instant updatedTimestamp = (Instant) message.obj;
+                    publishCtrl.handlePublishStateChangedMessage(newPublishState, updatedTimestamp);
+                    break;
+
+                case MSG_NOTIFY_CURRENT_PUBLISH_STATE:
+                    IRcsUcePublishStateCallback c = (IRcsUcePublishStateCallback) message.obj;
+                    publishCtrl.handleNotifyCurrentPublishStateMessage(c);
                     break;
 
                 case MSG_REQUEST_PUBLISH:
                     int type = (Integer) message.obj;
-                    handleRequestPublishMessage(type);
+                    publishCtrl.handleRequestPublishMessage(type);
                     break;
 
                 case MSG_REQUEST_CMD_ERROR:
                     PublishRequestResponse cmdErrorResponse = (PublishRequestResponse) message.obj;
-                    mPublishProcessor.onCommandError(cmdErrorResponse);
+                    publishCtrl.mPublishProcessor.onCommandError(cmdErrorResponse);
                     break;
 
-                case MSG_REQUEST_SIP_RESPONSE:
+                case MSG_REQUEST_NETWORK_RESPONSE:
                     PublishRequestResponse networkResponse = (PublishRequestResponse) message.obj;
-                    mPublishProcessor.onNetworkResponse(networkResponse);
+                    publishCtrl.mPublishProcessor.onNetworkResponse(networkResponse);
                     break;
 
                 case MSG_REQUEST_CANCELED:
                     long taskId = (Long) message.obj;
-                    handleRequestCanceledMessage(taskId);
+                    publishCtrl.handleRequestCanceledMessage(taskId);
                     break;
             }
         }
@@ -305,17 +338,25 @@
          * Remove all the messages from the handler.
          */
         public void onDestroy() {
-            removeMessages(MSG_PUBLISH_STATE_CHANGED);
-            removeMessages(MSG_REQUEST_PUBLISH);
+            removeCallbacksAndMessages(null);
         }
 
         /**
          * Send the message to notify the publish state is changed.
          */
-        public void onPublishStateChanged(@PublishState int publishState) {
+        public void onPublishStateChanged(@PublishState int publishState,
+                @NonNull Instant updatedTimestamp) {
             Message message = obtainMessage();
             message.what = MSG_PUBLISH_STATE_CHANGED;
             message.arg1 = publishState;
+            message.obj = updatedTimestamp;
+            sendMessage(message);
+        }
+
+        public void onNotifyCurrentPublishState(IRcsUcePublishStateCallback callback) {
+            Message message = obtainMessage();
+            message.what = MSG_NOTIFY_CURRENT_PUBLISH_STATE;
+            message.obj = callback;
             sendMessage(message);
         }
 
@@ -330,11 +371,19 @@
          * Send the request publish message with the delay.
          */
         public void requestPublish(@PublishTriggerType int type, long delay) {
-            if (mIsDestroyedFlag) return;
-            logd("requestPublish: " + type + ", delay=" + delay);
+            PublishControllerImpl publishCtrl = mPublishControllerRef.get();
+            if (publishCtrl == null) {
+                return;
+            }
+            if (publishCtrl.mIsDestroyedFlag) return;
+            publishCtrl.logd("requestPublish: " + type + ", delay=" + delay);
 
-            // Remove existing publish request.
-            removeMessages(MSG_REQUEST_PUBLISH, (Integer) type);
+            // Don't send duplicated publish request because it always publish the latest device
+            // capabilities.
+            if (hasMessages(MSG_REQUEST_PUBLISH)) {
+                publishCtrl.logd("requestPublish: Skip. there is already a request in the queue");
+                return;
+            }
 
             Message message = obtainMessage();
             message.what = MSG_REQUEST_PUBLISH;
@@ -347,8 +396,11 @@
         }
 
         public void onRequestCommandError(PublishRequestResponse requestResponse) {
-            if (mIsDestroyedFlag) return;
-
+            PublishControllerImpl publishCtrl = mPublishControllerRef.get();
+            if (publishCtrl == null) {
+                return;
+            }
+            if (publishCtrl.mIsDestroyedFlag) return;
             Message message = obtainMessage();
             message.what = MSG_REQUEST_CMD_ERROR;
             message.obj = requestResponse;
@@ -356,16 +408,23 @@
         }
 
         public void onRequestNetworkResponse(PublishRequestResponse requestResponse) {
-            if (mIsDestroyedFlag) return;
-
+            PublishControllerImpl publishCtrl = mPublishControllerRef.get();
+            if (publishCtrl == null) {
+                return;
+            }
+            if (publishCtrl.mIsDestroyedFlag) return;
             Message message = obtainMessage();
-            message.what = MSG_REQUEST_SIP_RESPONSE;
+            message.what = MSG_REQUEST_NETWORK_RESPONSE;
             message.obj = requestResponse;
             sendMessage(message);
         }
 
         public void setRequestCanceledTimer(long taskId, long delay) {
-            if (mIsDestroyedFlag) return;
+            PublishControllerImpl publishCtrl = mPublishControllerRef.get();
+            if (publishCtrl == null) {
+                return;
+            }
+            if (publishCtrl.mIsDestroyedFlag) return;
             removeMessages(MSG_REQUEST_CANCELED, (Long) taskId);
 
             Message message = obtainMessage();
@@ -375,21 +434,45 @@
         }
 
         public void clearRequestCanceledTimer() {
-            if (mIsDestroyedFlag) return;
+            PublishControllerImpl publishCtrl = mPublishControllerRef.get();
+            if (publishCtrl == null) {
+                return;
+            }
+            if (publishCtrl.mIsDestroyedFlag) return;
             removeMessages(MSG_REQUEST_CANCELED);
         }
+
+        private static Map<Integer, String> EVENT_DESCRIPTION = new HashMap<>();
+        static {
+            EVENT_DESCRIPTION.put(MSG_PUBLISH_STATE_CHANGED, "PUBLISH_STATE_CHANGED");
+            EVENT_DESCRIPTION.put(MSG_NOTIFY_CURRENT_PUBLISH_STATE, "NOTIFY_PUBLISH_STATE");
+            EVENT_DESCRIPTION.put(MSG_REQUEST_PUBLISH, "REQUEST_PUBLISH");
+            EVENT_DESCRIPTION.put(MSG_REQUEST_CMD_ERROR, "REQUEST_CMD_ERROR");
+            EVENT_DESCRIPTION.put(MSG_REQUEST_NETWORK_RESPONSE, "REQUEST_NETWORK_RESPONSE");
+            EVENT_DESCRIPTION.put(MSG_REQUEST_CANCELED, "REQUEST_CANCELED");
+        }
     }
 
     /**
      * Update the publish state and notify the publish state callback if the new state is different
      * from original state.
      */
-    private void handlePublishStateChangedMessage(@PublishState int newPublishState) {
+    private void handlePublishStateChangedMessage(@PublishState int newPublishState,
+            Instant updatedTimestamp) {
         synchronized (mPublishStateLock) {
             if (mIsDestroyedFlag) return;
-            logd("publish state changes from " + mPublishState + " to " + newPublishState);
+            // Check if the time of the given publish state is not earlier than existing time.
+            if (updatedTimestamp == null || !updatedTimestamp.isAfter(mPublishStateUpdatedTime)) {
+                logd("handlePublishStateChangedMessage: updatedTimestamp is not allowed: "
+                        + mPublishStateUpdatedTime + " to " + updatedTimestamp
+                        + ", publishState=" + newPublishState);
+                return;
+            }
+            logd("publish state changes from " + mPublishState + " to " + newPublishState +
+                    ", time=" + updatedTimestamp);
             if (mPublishState == newPublishState) return;
             mPublishState = newPublishState;
+            mPublishStateUpdatedTime = updatedTimestamp;
         }
 
         // Trigger the publish state changed in handler thread since it may take time.
@@ -404,6 +487,15 @@
         logd("Notify publish state changed: completed");
     }
 
+    private void handleNotifyCurrentPublishStateMessage(IRcsUcePublishStateCallback callback) {
+        if (mIsDestroyedFlag || callback == null) return;
+        try {
+            callback.onPublishStateChanged(getUcePublishState());
+        } catch (RemoteException e) {
+            logw("handleCurrentPublishStateUpdateMessage exception: " + e);
+        }
+    }
+
     public void handleRequestPublishMessage(@PublishTriggerType int type) {
         if (mIsDestroyedFlag) return;
         if (mUceCtrlCallback.isRequestForbiddenByNetwork()) {
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 1a44b89..80b1f50 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
@@ -31,13 +31,14 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
+import java.time.Instant;
 
 /**
  * Send the publish request and handle the response of the publish request result.
  */
 public class PublishProcessor {
 
-    private static final String LOG_TAG = "PublishProcessor";
+    private static final String LOG_TAG = UceUtils.getLogPrefix() + "PublishProcessor";
 
     // The length of time waiting for the response callback.
     private static final long RESPONSE_CALLBACK_WAITING_TIME = 60000L;
@@ -265,7 +266,8 @@
         } else {
             // Update the publish state if the request is failed and doesn't need to retry.
             int publishState = requestResponse.getPublishStateByCmdErrorCode();
-            mPublishCtrlCallback.updatePublishRequestResult(publishState);
+            Instant responseTimestamp = requestResponse.getResponseTimestamp();
+            mPublishCtrlCallback.updatePublishRequestResult(publishState, responseTimestamp);
 
             // Check if there is a pending request
             checkAndSendPendingRequest();
@@ -309,7 +311,8 @@
             }
             // Update the publish state if the request doesn't need to retry.
             int publishResult = requestResponse.getPublishStateByNetworkResponse();
-            mPublishCtrlCallback.updatePublishRequestResult(publishResult);
+            Instant responseTimestamp = requestResponse.getResponseTimestamp();
+            mPublishCtrlCallback.updatePublishRequestResult(publishResult, responseTimestamp);
 
             // Check if there is a pending request
             checkAndSendPendingRequest();
diff --git a/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessorState.java b/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessorState.java
index 2ac2fef..6362dd7 100644
--- a/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessorState.java
+++ b/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessorState.java
@@ -30,7 +30,7 @@
  */
 public class PublishProcessorState {
 
-    private static final String LOG_TAG = "PublishProcessorState";
+    private static final String LOG_TAG = UceUtils.getLogPrefix() + "PublishProcessorState";
 
     // The waiting period before the first retry.
     private static final int RETRY_BASE_PERIOD = 1;   // minute
@@ -129,7 +129,7 @@
     // Adjust the timestamp to allow request PUBLISH with the specific delay time
     private void adjustAllowedPublishTimestamp() {
         synchronized (mLock) {
-            Log.v(LOG_TAG, "adjustAllowedPublishTimestamp: retry=" + mRetryCount);
+            Log.d(LOG_TAG, "adjustAllowedPublishTimestamp: retry=" + mRetryCount);
             if (mAllowedTimestamp == null) {
                 // Now for the initialization.
                 mAllowedTimestamp = Instant.now();
@@ -137,7 +137,7 @@
                 long nextRetryDuration = getNextRetryDuration();
                 mAllowedTimestamp = Instant.now().plus(Duration.ofMillis(nextRetryDuration));
             }
-            Log.v(LOG_TAG, "adjustAllowedPublishTimestamp: timestamp="
+            Log.d(LOG_TAG, "adjustAllowedPublishTimestamp: timestamp="
                     + mAllowedTimestamp.toString());
         }
     }
@@ -145,11 +145,13 @@
     // Return the milliseconds of the next retry delay.
     private long getNextRetryDuration() {
         synchronized (mLock) {
-            int power = mRetryCount - 1;
-            if (power < 0) {
-                power = 0;
+            // If the current retry count is zero, the duration is also zero.
+            if (mRetryCount == 0) {
+                return 0L;
             }
+
             // Next retry duration (minute)
+            int power = mRetryCount - 1;
             Double retryDuration = RETRY_BASE_PERIOD * Math.pow(2, power);
 
             // Convert to millis
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 33bdefc..b845826 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,6 +16,7 @@
 
 package com.android.ims.rcs.uce.presence.publish;
 
+import android.annotation.Nullable;
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.aidl.IPublishResponseCallback;
 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase;
@@ -23,6 +24,8 @@
 import com.android.ims.rcs.uce.presence.publish.PublishController.PublishControllerCallback;
 import com.android.ims.rcs.uce.util.NetworkSipCode;
 
+import java.time.Instant;
+
 /**
  * Receiving the result callback of the publish request.
  */
@@ -36,6 +39,9 @@
     private int mNetworkRespSipCode;
     private String mNetworkRespReason;
 
+    // The timestamp when receive the response from the network.
+    private Instant mResponseTimestamp;
+
     public PublishRequestResponse(PublishControllerCallback publishCtrlCallback, long taskId) {
         mTaskId = taskId;
         mPublishCtrlCallback = publishCtrlCallback;
@@ -70,11 +76,16 @@
         return mNetworkRespSipCode;
     }
 
+    public @Nullable Instant getResponseTimestamp() {
+        return mResponseTimestamp;
+    }
+
     public void onDestroy() {
         mPublishCtrlCallback = null;
     }
 
     private void onCommandError(int errorCode) {
+        mResponseTimestamp = Instant.now();
         mCmdErrorCode = errorCode;
         updateRetryFlagByCommandError();
 
@@ -85,6 +96,7 @@
     }
 
     private void onNetworkResponse(int sipCode, String reason) {
+        mResponseTimestamp = Instant.now();
         mNetworkRespSipCode = sipCode;
         mNetworkRespReason = reason;
         updateRetryFlagByNetworkResponse();
@@ -150,10 +162,14 @@
      * Convert the network sip code to the publish state
      */
     public int getPublishStateByNetworkResponse() {
-        if (NetworkSipCode.SIP_CODE_REQUEST_TIMEOUT == mNetworkRespSipCode) {
-            return RcsUceAdapter.PUBLISH_STATE_REQUEST_TIMEOUT;
+        switch (mNetworkRespSipCode) {
+            case NetworkSipCode.SIP_CODE_OK:
+                return RcsUceAdapter.PUBLISH_STATE_OK;
+            case NetworkSipCode.SIP_CODE_REQUEST_TIMEOUT:
+                return RcsUceAdapter.PUBLISH_STATE_REQUEST_TIMEOUT;
+            default:
+                return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
         }
-        return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
     }
 
     /**
@@ -163,9 +179,10 @@
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append("taskId=").append(mTaskId)
-                .append(", mCmdErrorCode=").append(mCmdErrorCode)
-                .append(", onNetworkResponse=").append(mNetworkRespSipCode)
-                .append(", mNetworkResponseReason=").append(mNetworkRespReason)
+                .append(", CmdErrorCode=").append(mCmdErrorCode)
+                .append(", NetworkResponse=").append(mNetworkRespSipCode)
+                .append(", NetworkResponseReason=").append(mNetworkRespReason)
+                .append(", ResponseTimestamp=").append(mResponseTimestamp)
                 .append(", isRequestSuccess=").append(isRequestSuccess())
                 .append(", needRetry=").append(mNeedRetry);
         return builder.toString();
diff --git a/src/java/com/android/ims/rcs/uce/util/UceUtils.java b/src/java/com/android/ims/rcs/uce/util/UceUtils.java
index 37c0e30..1cc5104 100644
--- a/src/java/com/android/ims/rcs/uce/util/UceUtils.java
+++ b/src/java/com/android/ims/rcs/uce/util/UceUtils.java
@@ -25,11 +25,20 @@
 
 public class UceUtils {
 
+    private static final String LOG_PREFIX = "RcsUce.";
+
     private static final String LOG_TAG = "UceUtils";
 
     private static long TASK_ID = 0L;
 
     /**
+     * Get the log prefix of RCS UCE
+     */
+    public static String getLogPrefix() {
+        return LOG_PREFIX;
+    }
+
+    /**
      * Generate the unique UCE request task id.
      */
     public static synchronized long generateTaskId() {
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 05085f8..16db1f1 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
@@ -144,7 +144,7 @@
 
         verify(mDeviceCapability).updateImsMmtelRegistered(anyInt());
         verify(mCallback).requestPublishFromInternal(
-                PublishController.PUBLISH_TRIGGER_MMTEL_REGISTERED, 0L);
+                PublishController.PUBLISH_TRIGGER_MMTEL_REGISTERED, 500L);
     }
 
     @Test
@@ -171,7 +171,7 @@
 
         verify(mDeviceCapability).updateImsRcsRegistered(anyInt());
         verify(mCallback).requestPublishFromInternal(
-                PublishController.PUBLISH_TRIGGER_RCS_REGISTERED, 0L);
+                PublishController.PUBLISH_TRIGGER_RCS_REGISTERED, 500L);
     }
 
     @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 68373df..c1e4de4 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
@@ -182,7 +182,7 @@
 
         publishProcessor.onCommandError(mResponseCallback);
 
-        verify(mPublishCtrlCallback).updatePublishRequestResult(anyInt());
+        verify(mPublishCtrlCallback).updatePublishRequestResult(anyInt(), any());
         verify(mResponseCallback).onDestroy();
         verify(mProcessorState).setPublishingFlag(false);
         verify(mPublishCtrlCallback).clearRequestCanceledTimer();
@@ -219,7 +219,7 @@
 
         publishProcessor.onNetworkResponse(mResponseCallback);
 
-        verify(mPublishCtrlCallback).updatePublishRequestResult(anyInt());
+        verify(mPublishCtrlCallback).updatePublishRequestResult(anyInt(), any());
         verify(mResponseCallback).onDestroy();
         verify(mProcessorState).setPublishingFlag(false);
         verify(mPublishCtrlCallback).clearRequestCanceledTimer();