Merge changes from topic "RCS single registration metrics" into sc-v2-dev

* changes:
  Add metrics for UCE event about Publish, Subscribe, Options and metrics for RCS registration features
  [UCE] Reset the retry count and times related publish when the publish status changed to not_published
  Create UceStatsWriter for RCS metrics
diff --git a/src/java/com/android/ims/rcs/uce/UceStatsWriter.java b/src/java/com/android/ims/rcs/uce/UceStatsWriter.java
new file mode 100644
index 0000000..1db9040
--- /dev/null
+++ b/src/java/com/android/ims/rcs/uce/UceStatsWriter.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.rcs.uce;
+
+import android.annotation.IntDef;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.telephony.ims.stub.RcsCapabilityExchangeImplBase;
+import android.telephony.ims.RcsContactPresenceTuple;
+import android.telephony.ims.RcsContactUceCapability;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The UceStatsWriter should be a singleton class for storing atoms in RcsStats.
+ * ims-common provides an interface for setting atoms to telephony-common.
+ **/
+public class UceStatsWriter {
+    private static UceStatsWriter sInstance = null;
+    private UceStatsCallback mCallBack;
+
+    /**
+     * @hide
+     */
+    // Defines which UCE event occurred.
+    @IntDef(value = {
+        PUBLISH_EVENT,
+        SUBSCRIBE_EVENT,
+        INCOMING_OPTION_EVENT,
+        OUTGOING_OPTION_EVENT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UceEventType {}
+    /**
+     * UCE events related to Publish Method.
+     */
+    public static final int PUBLISH_EVENT = 0;
+    /**
+     * UCE events related to Subscribe Method.
+     */
+    public static final int SUBSCRIBE_EVENT = 1;
+    /**
+     * UCE events related to incoming Options Method.
+     */
+    public static final int INCOMING_OPTION_EVENT = 2;
+    /**
+     * UCE events related to outgoing Options Method.
+     */
+    public static final int OUTGOING_OPTION_EVENT = 3;
+
+    /**
+     * The callback interface is called by the Metrics data creator to receive information from
+     * others controllers.
+     */
+    public interface UceStatsCallback {
+        /**
+         * Notify the callback listener that the feature tag associated with
+         * IMS registration of this device have changed.
+         */
+        public void onImsRegistrationFeatureTagStats(int subId, List<String> featureTagList,
+            int registrationTech);
+
+        /**
+         * Notify that the active IMS registration to the carrier network has been terminated.
+         */
+        public void onStoreCompleteImsRegistrationFeatureTagStats(int subId);
+
+        /**
+         * Notify the callback listener that the PIDF ServiceDescriptions associated with
+         * the UCE presence of this device have changed.
+         */
+        void onImsRegistrationServiceDescStats(int subId, List<String> serviceIdList,
+            List<String> serviceIdVersionList, int registrationTech);
+
+        /**
+         * Notify the callback listener that a subscribe response received.
+         */
+        void onSubscribeResponse(int subId, long taskId, int networkResponse);
+
+        /**
+         * Notify the callback listener that a UCE Network Event has occurred.
+         */
+        void onUceEvent(int subId, int type, boolean successful, int commandCode,
+            int networkResponse);
+
+        /**
+         * Notify the callback listener that a subscribe has ended.
+         */
+        void onSubscribeTerminated(int subId, long taskId, String reason);
+
+        /**
+         * Notify that the Presence Notify Event has changed.
+         */
+        void onPresenceNotifyEvent(int subId, long taskId,
+            List<RcsContactUceCapability> updatedCapList);
+
+        /**
+         * Notify that the active UCE PUBLISH to the carrier network has been terminated.
+         */
+        void onStoreCompleteImsRegistrationServiceDescStats(int subId);
+    }
+
+    /**
+     * create an instance of UceStatsWriter
+     */
+    public static UceStatsWriter init(UceStatsCallback callback) {
+        synchronized (UceStatsWriter.class) {
+            if (sInstance == null) {
+                sInstance = new UceStatsWriter(callback);
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * get the current instance of UceStatsWriter
+     */
+    public static UceStatsWriter getInstance() {
+        synchronized (UceStatsWriter.class) {
+            return sInstance;
+        }
+    }
+
+    /**
+     * Stats about each Feature tag that was included in IMS registration received from
+     * the network during register.
+     * @param subId The subId associated with the event.
+     * @param featureTagList Ims Feature tag list.
+     * @param registrationTech The registration tech associated with the feature tag.
+     */
+    public void setImsRegistrationFeatureTagStats(int subId, List<String> featureTagList,
+        @ImsRegistrationImplBase.ImsRegistrationTech int registrationTech) {
+        if (mCallBack == null) {
+            return;
+        }
+        mCallBack.onImsRegistrationFeatureTagStats(subId, featureTagList, registrationTech);
+    }
+
+    /**
+     * Update time of stats for each stored feature tag.
+     * @param subId The subId associated with the event.
+     */
+    public void setStoreCompleteImsRegistrationFeatureTagStats(int subId) {
+        if (mCallBack == null) {
+            return;
+        }
+        mCallBack.onStoreCompleteImsRegistrationFeatureTagStats(subId);
+    }
+
+    /**
+     * Stats about each ServiceDescription that was included in the PIDF XML sent to
+     * the network during publish
+     * @param subId The subId associated with the event.
+     * @param tupleList Tuple information set in PIDF.
+     * @param registrationTech The registration tech associated with the feature tag.
+     */
+    public void setImsRegistrationServiceDescStats(int subId,
+        List<RcsContactPresenceTuple> tupleList,
+        @ImsRegistrationImplBase.ImsRegistrationTech int registrationTech) {
+        if (mCallBack == null) {
+            return;
+        }
+        ArrayList<String> svcId = new ArrayList<>();
+        ArrayList<String> svcVersion = new ArrayList<>();
+
+        for (RcsContactPresenceTuple tuple : tupleList) {
+            svcId.add(tuple.getServiceId());
+            svcVersion.add(tuple.getServiceVersion());
+        }
+        mCallBack.onImsRegistrationServiceDescStats(subId, svcId, svcVersion, registrationTech);
+    }
+
+    /**
+     * Stats related to UCE queries to the network
+     * @param subId The subId associated with the event.
+     * @param taskId The taskId associate with the event.
+     * @param networkResponse The network response code for the Uce event
+     */
+    public void setSubscribeResponse(int subId, long taskId, int networkResponse) {
+        if (mCallBack != null) {
+            mCallBack.onSubscribeResponse(subId, taskId, networkResponse);
+        }
+    }
+
+    /**
+     * Stats related to UCE queries to the network
+     * @param subId The subId associated with the event.
+     * @param type Used to identify the message type.
+     * @param successful Whether the UCE event is successfully finished.
+     * @param commandCode The command error code for the Uce event
+     * @param networkResponse The network response code for the Uce event
+     */
+    public void setUceEvent(int subId, @UceEventType int type, boolean successful,
+        @RcsCapabilityExchangeImplBase.CommandCode int commandCode, int networkResponse) {
+        if (mCallBack != null) {
+            mCallBack.onUceEvent(subId, type, successful, commandCode, networkResponse);
+        }
+    }
+
+    /**
+     * The result of processing received notify messages.
+     * @param subId The subId associated with the event.
+     * @param taskId The taskId associate with the event.
+     * @param updatedCapList Capability information of the user contained in Presence Notify.
+     */
+    public void setPresenceNotifyEvent(int subId, long taskId,
+        List<RcsContactUceCapability> updatedCapList) {
+        if (mCallBack == null || updatedCapList == null || updatedCapList.isEmpty()) {
+            return;
+        }
+        mCallBack.onPresenceNotifyEvent(subId, taskId, updatedCapList);
+    }
+
+    /**
+     * Indicates that the subscription request has become a terminated state.
+     * @param subId The subId associated with the event.
+     * @param taskId The taskId associate with the event.
+     * @param reason The terminated reason associated with the subscription state.
+     */
+    public void setSubscribeTerminated(int subId, long taskId, String reason) {
+        if (mCallBack != null) {
+            mCallBack.onSubscribeTerminated(subId, taskId, reason);
+        }
+    }
+
+    /**
+     * indicates that the device has removed an existing PUBLISH from the carrier's network
+     * containing the device's RCS capabilities state.
+     * The registered time of publication must be set in ImsRegistrationServiceDescStats,
+     * which is the life time of publication, so it can be set only when publication is over.
+     * @param subId The subId associated with the event.
+     */
+    public void setUnPublish(int subId) {
+        if (mCallBack != null) {
+            mCallBack.onStoreCompleteImsRegistrationServiceDescStats(subId);
+        }
+    }
+
+    @VisibleForTesting
+    protected UceStatsWriter(UceStatsCallback callback) {
+        mCallBack = callback;
+    }
+}
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 e881ae0..872d35d 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
@@ -46,6 +46,7 @@
 import android.util.LocalLog;
 import android.util.Log;
 
+import com.android.ims.rcs.uce.UceStatsWriter;
 import com.android.ims.rcs.uce.presence.publish.PublishController.PublishControllerCallback;
 import com.android.ims.rcs.uce.presence.publish.PublishController.PublishTriggerType;
 import com.android.ims.rcs.uce.util.UceUtils;
@@ -53,7 +54,12 @@
 import com.android.internal.telephony.util.HandlerExecutor;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Listen to the device changes and notify the PublishController to publish the device's
@@ -65,6 +71,8 @@
 
     private static final long REGISTER_IMS_CHANGED_DELAY = 15000L;  // 15 seconds
 
+    private final UceStatsWriter mUceStatsWriter;
+
     /**
      * Used to inject ImsMmTelManager instances for testing.
      */
@@ -180,7 +188,7 @@
     private final Object mLock = new Object();
 
     public DeviceCapabilityListener(Context context, int subId, DeviceCapabilityInfo info,
-            PublishControllerCallback callback) {
+            PublishControllerCallback callback, UceStatsWriter uceStatsWriter) {
         mSubId = subId;
         logi("create");
 
@@ -188,6 +196,7 @@
         mCallback = callback;
         mCapabilityInfo = info;
         mInitialized = false;
+        mUceStatsWriter = uceStatsWriter;
 
         mHandlerThread = new HandlerThread("DeviceCapListenerThread");
         mHandlerThread.start();
@@ -447,6 +456,12 @@
                     synchronized (mLock) {
                         logi("onRcsRegistered: " + attributes);
                         if (!mIsImsCallbackRegistered) return;
+
+                        List<String> featureTagList = new ArrayList<>(attributes.getFeatureTags());
+                        int registrationTech = attributes.getRegistrationTechnology();
+
+                        mUceStatsWriter.setImsRegistrationFeatureTagStats(
+                                mSubId, featureTagList, registrationTech);
                         handleImsRcsRegistered(attributes);
                     }
                 }
@@ -456,6 +471,7 @@
                     synchronized (mLock) {
                         logi("onRcsUnregistered: " + info);
                         if (!mIsImsCallbackRegistered) return;
+                        mUceStatsWriter.setStoreCompleteImsRegistrationFeatureTagStats(mSubId);
                         handleImsRcsUnregistered();
                     }
                 }
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 e2340ff..0ff4885 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
@@ -44,6 +44,7 @@
 import com.android.ims.rcs.uce.UceController.UceControllerCallback;
 import com.android.ims.rcs.uce.UceDeviceState;
 import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult;
+import com.android.ims.rcs.uce.UceStatsWriter;
 import com.android.ims.rcs.uce.util.UceUtils;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
@@ -79,7 +80,8 @@
     @VisibleForTesting
     public interface DeviceCapListenerFactory {
         DeviceCapabilityListener createDeviceCapListener(Context context, int subId,
-                DeviceCapabilityInfo capInfo, PublishControllerCallback callback);
+                DeviceCapabilityInfo capInfo, PublishControllerCallback callback,
+                UceStatsWriter uceStatsWriter);
     }
 
     private final int mSubId;
@@ -90,6 +92,7 @@
     private volatile boolean mReceivePublishFromService;
     private volatile RcsFeatureManager mRcsFeatureManager;
     private final UceControllerCallback mUceCtrlCallback;
+    private final UceStatsWriter mUceStatsWriter;
 
     // The capability type that the device is using.
     private @RcsImsCapabilityFlag int mCapabilityType;
@@ -115,8 +118,9 @@
 
     // The listener to listen to the device's capabilities changed.
     private DeviceCapabilityListener mDeviceCapListener;
-    private DeviceCapListenerFactory mDeviceCapListenerFactory = (context, subId, capInfo, callback)
-            -> new DeviceCapabilityListener(context, subId, capInfo, callback);
+    private DeviceCapListenerFactory mDeviceCapListenerFactory = (context, subId, capInfo, callback,
+            uceStatsWriter)
+            -> new DeviceCapabilityListener(context, subId, capInfo, callback, uceStatsWriter);
 
     // Listen to the RCS availability status changed.
     private final IImsCapabilityCallback mRcsCapabilitiesCallback =
@@ -141,6 +145,7 @@
         mSubId = subId;
         mContext = context;
         mUceCtrlCallback = callback;
+        mUceStatsWriter = UceStatsWriter.getInstance();
         logi("create");
         initPublishController(looper);
     }
@@ -148,12 +153,13 @@
     @VisibleForTesting
     public PublishControllerImpl(Context context, int subId, UceControllerCallback c,
             Looper looper, DeviceCapListenerFactory deviceCapFactory,
-            PublishProcessorFactory processorFactory) {
+            PublishProcessorFactory processorFactory, UceStatsWriter instance) {
         mSubId = subId;
         mContext = context;
         mUceCtrlCallback = c;
         mDeviceCapListenerFactory = deviceCapFactory;
         mPublishProcessorFactory = processorFactory;
+        mUceStatsWriter = instance;
         initPublishController(looper);
     }
 
@@ -200,7 +206,7 @@
 
     private void initDeviceCapabilitiesListener() {
         mDeviceCapListener = mDeviceCapListenerFactory.createDeviceCapListener(mContext, mSubId,
-                mDeviceCapabilityInfo, mPublishControllerCallback);
+                mDeviceCapabilityInfo, mPublishControllerCallback, mUceStatsWriter);
     }
 
     @Override
@@ -330,8 +336,7 @@
     public void onUnpublish() {
         logd("onUnpublish");
         if (mIsDestroyedFlag) return;
-        mPublishHandler.sendPublishStateChangedMessage(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED,
-                Instant.now(), null /*pidfXml*/);
+        mPublishHandler.sendUnpublishedMessage(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED);
     }
 
     @Override
@@ -416,6 +421,7 @@
         private static final int MSG_REQUEST_NETWORK_RESPONSE = 10;
         private static final int MSG_REQUEST_CANCELED = 11;
         private static final int MSG_RESET_DEVICE_STATE = 12;
+        private static final int MSG_UNPUBLISHED = 13;
 
         private final WeakReference<PublishControllerImpl> mPublishControllerRef;
 
@@ -496,6 +502,14 @@
                     publishCtrl.handleResetDeviceStateMessage();
                     break;
 
+                case MSG_UNPUBLISHED: {
+                    SomeArgs args = (SomeArgs) message.obj;
+                    int newPublishState = (Integer) args.arg1;
+                    Instant updatedTimestamp = (Instant) args.arg2;
+                    args.recycle();
+                    publishCtrl.handleUnpublishedMessage(newPublishState, updatedTimestamp);
+                    break;
+                }
                 default:
                     publishCtrl.logd("invalid message: " + message.what);
                     break;
@@ -584,6 +598,22 @@
         }
 
         /**
+         * Send the message to notify the publish state is changed.
+         */
+        public void sendUnpublishedMessage(@PublishState int publishState) {
+            PublishControllerImpl publishCtrl = mPublishControllerRef.get();
+            if (publishCtrl == null) return;
+            if (publishCtrl.mIsDestroyedFlag) return;
+
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = publishState;
+            args.arg2 = Instant.now();
+            Message message = obtainMessage();
+            message.what = MSG_UNPUBLISHED;
+            message.obj = args;
+            sendMessage(message);
+        }
+        /**
          * Send the message to notify the new added callback of the latest publish state.
          */
         public void sendNotifyCurrentPublishStateMessage(
@@ -703,6 +733,7 @@
             EVENT_DESCRIPTION.put(MSG_REQUEST_NETWORK_RESPONSE, "REQUEST_NETWORK_RESPONSE");
             EVENT_DESCRIPTION.put(MSG_REQUEST_CANCELED, "REQUEST_CANCELED");
             EVENT_DESCRIPTION.put(MSG_RESET_DEVICE_STATE, "RESET_DEVICE_STATE");
+            EVENT_DESCRIPTION.put(MSG_UNPUBLISHED, "MSG_UNPUBLISHED");
         }
     }
 
@@ -909,6 +940,9 @@
             if (mPublishState == newPublishState) return;
             mPublishState = newPublishState;
         }
+        if (newPublishState == RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED) {
+            mUceStatsWriter.setUnPublish(mSubId);
+        }
 
         // Trigger the publish state changed in handler thread since it may take time.
         logd("Notify publish state changed: " + mPublishState);
@@ -988,6 +1022,13 @@
         mUceCtrlCallback.resetDeviceState();
     }
 
+    private void handleUnpublishedMessage(@PublishState int newPublishState,
+            Instant updatedTimestamp) {
+        if (mIsDestroyedFlag) return;
+        mPublishProcessor.resetState();
+        handlePublishStateChangedMessage(newPublishState, updatedTimestamp, null);
+    }
+
     @VisibleForTesting
     public void setCapabilityType(int type) {
         mCapabilityType = type;
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 68aeaa8..01aa22b 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,8 @@
 import android.content.Context;
 import android.os.RemoteException;
 import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.telephony.ims.stub.RcsCapabilityExchangeImplBase;
 import android.text.TextUtils;
 import android.util.IndentingPrintWriter;
 import android.util.LocalLog;
@@ -31,6 +33,7 @@
 import com.android.ims.rcs.uce.presence.pidfparser.PidfParser;
 import com.android.ims.rcs.uce.presence.publish.PublishController.PublishControllerCallback;
 import com.android.ims.rcs.uce.presence.publish.PublishController.PublishTriggerType;
+import com.android.ims.rcs.uce.UceStatsWriter;
 import com.android.ims.rcs.uce.util.UceUtils;
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -53,6 +56,8 @@
     private volatile boolean mIsDestroyed;
     private volatile RcsFeatureManager mRcsFeatureManager;
 
+    private final UceStatsWriter mUceStatsWriter;
+
     // Manage the state of the publish processor.
     private PublishProcessorState mProcessorState;
 
@@ -74,6 +79,18 @@
         mDeviceCapabilities = capabilityInfo;
         mPublishCtrlCallback = publishCtrlCallback;
         mProcessorState = new PublishProcessorState(subId);
+        mUceStatsWriter = UceStatsWriter.getInstance();
+    }
+
+    @VisibleForTesting
+    public PublishProcessor(Context context, int subId, DeviceCapabilityInfo capabilityInfo,
+            PublishControllerCallback publishCtrlCallback, UceStatsWriter instance) {
+        mSubId = subId;
+        mContext = context;
+        mDeviceCapabilities = capabilityInfo;
+        mPublishCtrlCallback = publishCtrlCallback;
+        mProcessorState = new PublishProcessorState(subId);
+        mUceStatsWriter = instance;
     }
 
     /**
@@ -158,6 +175,13 @@
             return false;
         }
 
+        featureManager.getImsRegistrationTech((tech) -> {
+            int registrationTech = (tech == null)
+                    ? ImsRegistrationImplBase.REGISTRATION_TECH_NONE : tech;
+            mUceStatsWriter.setImsRegistrationServiceDescStats(mSubId,
+                    deviceCapability.getCapabilityTuples(), registrationTech);
+        });
+
         // Publish to the Presence server.
         return publishCapabilities(featureManager, pidfXml);
     }
@@ -244,6 +268,13 @@
         mLocalLog.log("Receive command error code=" + requestResponse.getCmdErrorCode());
         logd("onCommandError: " + requestResponse.toString());
 
+        int cmdError = requestResponse.getCmdErrorCode().orElse(0);
+        boolean successful = false;
+        if (cmdError == RcsCapabilityExchangeImplBase.COMMAND_CODE_NO_CHANGE) {
+            successful = true;
+        }
+        mUceStatsWriter.setUceEvent(mSubId, UceStatsWriter.PUBLISH_EVENT, successful, cmdError, 0);
+
         if (requestResponse.needRetry() && !mProcessorState.isReachMaximumRetries()) {
             handleRequestRespWithRetry(requestResponse);
         } else {
@@ -267,6 +298,10 @@
         mLocalLog.log("Receive network response code=" + requestResponse.getNetworkRespSipCode());
         logd("onNetworkResponse: " + requestResponse.toString());
 
+        int responseCode = requestResponse.getNetworkRespSipCode().orElse(0);
+        mUceStatsWriter.setUceEvent(mSubId, UceStatsWriter.PUBLISH_EVENT, true, 0,
+            responseCode);
+
         if (requestResponse.needRetry() && !mProcessorState.isReachMaximumRetries()) {
             handleRequestRespWithRetry(requestResponse);
         } else {
@@ -445,6 +480,12 @@
         return mProcessorState.isPublishingNow();
     }
 
+    /**
+     * Reset the retry count and time related publish.
+     */
+    public void resetState() {
+        mProcessorState.resetState();
+    }
     @VisibleForTesting
     public void setProcessorState(PublishProcessorState processorState) {
         mProcessorState = processorState;
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 40d901f..0e9adfa 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
@@ -348,6 +348,16 @@
         }
     }
 
+    /**
+     * Reset the retry count and related time when the publication status has
+     * changed to not_published.
+     */
+    public void resetState() {
+        synchronized (mLock) {
+            mPublishThrottle.resetState();
+        }
+    }
+
     /*
      * Check if it has reached the maximum retry count.
      */
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 a150dd6..a266093 100644
--- a/src/java/com/android/ims/rcs/uce/request/OptionsRequestCoordinator.java
+++ b/src/java/com/android/ims/rcs/uce/request/OptionsRequestCoordinator.java
@@ -24,6 +24,7 @@
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
 
 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
+import com.android.ims.rcs.uce.UceStatsWriter;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.Collection;
@@ -44,7 +45,14 @@
 
         public Builder(int subId, Collection<UceRequest> requests,
                 RequestManagerCallback callback) {
-            mRequestCoordinator = new OptionsRequestCoordinator(subId, requests, callback);
+            mRequestCoordinator = new OptionsRequestCoordinator(subId, requests, callback,
+                    UceStatsWriter.getInstance());
+        }
+        @VisibleForTesting
+        public Builder(int subId, Collection<UceRequest> requests,
+                RequestManagerCallback callback, UceStatsWriter instance) {
+            mRequestCoordinator = new OptionsRequestCoordinator(subId, requests, callback,
+                    instance);
         }
 
         public Builder setCapabilitiesCallback(IRcsUceControllerCallback callback) {
@@ -105,9 +113,12 @@
     // The callback to notify the result of the capabilities request.
     private IRcsUceControllerCallback mCapabilitiesCallback;
 
+    private final UceStatsWriter mUceStatsWriter;
+
     private OptionsRequestCoordinator(int subId, Collection<UceRequest> requests,
-            RequestManagerCallback requestMgrCallback) {
+            RequestManagerCallback requestMgrCallback, UceStatsWriter instance) {
         super(subId, requests, requestMgrCallback);
+        mUceStatsWriter = instance;
         logd("OptionsRequestCoordinator: created");
     }
 
@@ -189,6 +200,11 @@
         // Finish this request.
         request.onFinish();
 
+        int commandErrorCode = response.getCommandError().orElse(0);
+        mUceStatsWriter.setUceEvent(mSubId, UceStatsWriter.OUTGOING_OPTION_EVENT,
+            false, commandErrorCode, 0);
+
+
         // Remove this request from the activated collection and notify RequestManager.
         Long taskId = request.getTaskId();
         RequestResult requestResult = sCommandErrorCreator.createRequestResult(taskId, response);
@@ -203,6 +219,11 @@
         CapabilityRequestResponse response = request.getRequestResponse();
         logd("handleNetworkResponse: " + response.toString());
 
+        int responseCode = response.getNetworkRespSipCode().orElse(0);
+        mUceStatsWriter.setUceEvent(mSubId, UceStatsWriter.OUTGOING_OPTION_EVENT, true,
+            0, responseCode);
+
+
         List<RcsContactUceCapability> updatedCapList = response.getUpdatedContactCapability();
         if (!updatedCapList.isEmpty()) {
             // Save the capabilities and trigger the capabilities callback
diff --git a/src/java/com/android/ims/rcs/uce/request/RemoteOptionsCoordinator.java b/src/java/com/android/ims/rcs/uce/request/RemoteOptionsCoordinator.java
index c8aa3f7..5a3e33b 100644
--- a/src/java/com/android/ims/rcs/uce/request/RemoteOptionsCoordinator.java
+++ b/src/java/com/android/ims/rcs/uce/request/RemoteOptionsCoordinator.java
@@ -25,6 +25,7 @@
 
 import com.android.ims.rcs.uce.request.RemoteOptionsRequest.RemoteOptResponse;
 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
+import com.android.ims.rcs.uce.UceStatsWriter;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.Collection;
@@ -41,7 +42,13 @@
         RemoteOptionsCoordinator mRemoteOptionsCoordinator;
 
         public Builder(int subId, Collection<UceRequest> requests, RequestManagerCallback c) {
-            mRemoteOptionsCoordinator = new RemoteOptionsCoordinator(subId, requests, c);
+            mRemoteOptionsCoordinator = new RemoteOptionsCoordinator(subId, requests, c,
+                    UceStatsWriter.getInstance());
+        }
+        @VisibleForTesting
+        public Builder(int subId, Collection<UceRequest> requests, RequestManagerCallback c,
+                UceStatsWriter instance) {
+            mRemoteOptionsCoordinator = new RemoteOptionsCoordinator(subId, requests, c, instance);
         }
 
         public Builder setOptionsRequestCallback(IOptionsRequestCallback callback) {
@@ -78,9 +85,12 @@
     // The callback to notify the result of the remote options request.
     private IOptionsRequestCallback mOptionsReqCallback;
 
+    private final UceStatsWriter mUceStatsWriter;
+
     private RemoteOptionsCoordinator(int subId, Collection<UceRequest> requests,
-            RequestManagerCallback requestMgrCallback) {
+            RequestManagerCallback requestMgrCallback, UceStatsWriter instance) {
         super(subId, requests, requestMgrCallback);
+        mUceStatsWriter = instance;
         logd("RemoteOptionsCoordinator: created");
     }
 
@@ -144,6 +154,9 @@
             boolean isRemoteNumberBlocked) {
         try {
             logd("triggerOptionsReqCallback: start");
+            mUceStatsWriter.setUceEvent(mSubId, UceStatsWriter.INCOMING_OPTION_EVENT, true, 0,
+                200);
+
             mOptionsReqCallback.respondToCapabilityRequest(deviceCaps, isRemoteNumberBlocked);
         } catch (RemoteException e) {
             logw("triggerOptionsReqCallback exception: " + e);
@@ -155,6 +168,9 @@
     private void triggerOptionsReqWithErrorCallback(int errorCode, String reason) {
         try {
             logd("triggerOptionsReqWithErrorCallback: start");
+            mUceStatsWriter.setUceEvent(mSubId, UceStatsWriter.INCOMING_OPTION_EVENT, true, 0,
+                errorCode);
+
             mOptionsReqCallback.respondToCapabilityRequestWithError(errorCode, reason);
         } catch (RemoteException e) {
             logw("triggerOptionsReqWithErrorCallback exception: " + e);
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 b3cc781..f44686a 100644
--- a/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java
+++ b/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java
@@ -29,6 +29,7 @@
 import com.android.ims.rcs.uce.presence.pidfparser.PidfParserUtils;
 import com.android.ims.rcs.uce.request.SubscriptionTerminatedHelper.TerminatedResult;
 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
+import com.android.ims.rcs.uce.UceStatsWriter;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
@@ -53,7 +54,13 @@
          * The builder of the SubscribeRequestCoordinator class.
          */
         public Builder(int subId, Collection<UceRequest> requests, RequestManagerCallback c) {
-            mRequestCoordinator = new SubscribeRequestCoordinator(subId, requests, c);
+            mRequestCoordinator = new SubscribeRequestCoordinator(subId, requests, c,
+                    UceStatsWriter.getInstance());
+        }
+        @VisibleForTesting
+        public Builder(int subId, Collection<UceRequest> requests, RequestManagerCallback c,
+                UceStatsWriter instance) {
+            mRequestCoordinator = new SubscribeRequestCoordinator(subId, requests, c, instance);
         }
 
         /**
@@ -154,9 +161,12 @@
     // The callback to notify the result of the capabilities request.
     private volatile IRcsUceControllerCallback mCapabilitiesCallback;
 
+    private final UceStatsWriter mUceStatsWriter;
+
     private SubscribeRequestCoordinator(int subId, Collection<UceRequest> requests,
-            RequestManagerCallback requestMgrCallback) {
+            RequestManagerCallback requestMgrCallback, UceStatsWriter instance) {
         super(subId, requests, requestMgrCallback);
+        mUceStatsWriter = instance;
         logd("SubscribeRequestCoordinator: created");
     }
 
@@ -248,6 +258,10 @@
         // Finish this request.
         request.onFinish();
 
+        int commandErrorCode = response.getCommandError().orElse(0);
+        mUceStatsWriter.setUceEvent(mSubId, UceStatsWriter.SUBSCRIBE_EVENT,
+            false, commandErrorCode, 0);
+
         // Remove this request from the activated collection and notify RequestManager.
         Long taskId = request.getTaskId();
         RequestResult requestResult = sCommandErrorCreator.createRequestResult(taskId, response,
@@ -263,6 +277,9 @@
         CapabilityRequestResponse response = request.getRequestResponse();
         logd("handleNetworkResponse: " + response.toString());
 
+        int respCode = response.getNetworkRespSipCode().orElse(0);
+        mUceStatsWriter.setSubscribeResponse(mSubId, request.getTaskId(), respCode);
+
         // Refresh the device state with the request result.
         response.getResponseSipCode().ifPresent(sipCode -> {
             String reason = response.getResponseReason().orElse("");
@@ -379,6 +396,7 @@
             return;
         }
 
+        mUceStatsWriter.setPresenceNotifyEvent(mSubId, taskId, updatedCapList);
         // Save the updated capabilities to the cache.
         mRequestManagerCallback.saveCapabilities(updatedCapList);
 
@@ -402,6 +420,8 @@
             return;
         }
 
+        mUceStatsWriter.setPresenceNotifyEvent(mSubId, taskId, terminatedResources);
+
         // Save the terminated capabilities to the cache.
         mRequestManagerCallback.saveCapabilities(terminatedResources);
 
@@ -442,6 +462,7 @@
 
         // Remove this request from the activated collection and notify RequestManager.
         Long taskId = request.getTaskId();
+        mUceStatsWriter.setSubscribeTerminated(mSubId, taskId, response.getTerminatedReason());
         RequestResult requestResult = sTerminatedCreator.createRequestResult(taskId, response,
                 mRequestManagerCallback);
         moveRequestToFinishedCollection(taskId, requestResult);
diff --git a/tests/src/com/android/ims/rcs/uce/UceStatsWriterTest.java b/tests/src/com/android/ims/rcs/uce/UceStatsWriterTest.java
new file mode 100644
index 0000000..49edf19
--- /dev/null
+++ b/tests/src/com/android/ims/rcs/uce/UceStatsWriterTest.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.rcs.uce;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import android.telephony.ims.RcsContactPresenceTuple;
+import android.telephony.ims.RcsContactUceCapability;
+
+import com.android.ims.rcs.uce.UceStatsWriter;
+import com.android.ims.rcs.uce.UceStatsWriter.UceStatsCallback;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class UceStatsWriterTest {
+    private int mSubId = 3;
+    private long mTaskId = 5;
+    private int mRegistrationTech = 3;
+    private int mType = 2;
+    private boolean mSuccessful = true;
+    private int mCommandCode = 4;
+    private int mNetworkResponse = 200;
+    private String mReason = "noresource";
+
+    private Callback mCallback;
+    private TestableUceStatsWriter mWrite;
+
+    class Callback implements UceStatsCallback {
+        int subId;
+        List<String> featureTagList;
+        List<String> serviceIdList;
+        List<String> versions;
+        int registrationTech;
+        int type;
+        boolean successful;
+        int commandCode;
+        int networkResponse;
+        long taskId;
+        List<RcsContactUceCapability> updatedCapList;
+        String reason;
+
+        Callback() {
+        }
+
+        public void onImsRegistrationFeatureTagStats(int subId, List<String> featureTagList,
+            int registrationTech) {
+            this.subId = subId;
+            this.featureTagList = featureTagList;
+            this.registrationTech = registrationTech;
+        }
+
+        public void onStoreCompleteImsRegistrationFeatureTagStats(int subId) {}
+
+        public void onImsRegistrationServiceDescStats(int subId, List<String> serviceIdList,
+            List<String> serviceIdVersionList, int registrationTech) {
+            this.subId = subId;
+            this.serviceIdList = serviceIdList;
+            this.versions = serviceIdVersionList;
+            this.registrationTech = registrationTech;
+        }
+
+        public void onSubscribeResponse(int subId, long taskId, int networkResponse) {
+            this.subId = subId;
+            this.taskId = taskId;
+            this.successful = true;
+            this.commandCode = 0;
+            this.networkResponse = networkResponse;
+        }
+
+        public void onUceEvent(int subId, int type, boolean successful, int commandCode,
+            int networkResponse) {
+            this.subId = subId;
+            this.type = type;
+            this.successful = successful;
+            this.commandCode = commandCode;
+            this.networkResponse = networkResponse;
+        }
+
+        public void onSubscribeTerminated(int subId, long taskId, String reason) {
+            this.subId = subId;
+            this.taskId = taskId;
+            this.reason = reason;
+        }
+
+        public void onPresenceNotifyEvent(int subId, long taskId,
+            List<RcsContactUceCapability> updatedCapList) {
+            this.subId = subId;
+            this.taskId = taskId;
+            this.updatedCapList = updatedCapList;
+        }
+
+        public void onStoreCompleteImsRegistrationServiceDescStats(int subId) {
+            this.subId = subId;
+        }
+    }
+
+    private class TestableUceStatsWriter extends UceStatsWriter {
+        public TestableUceStatsWriter(UceStatsCallback callback) {
+            super(callback);
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mCallback = new Callback();
+        mWrite = new TestableUceStatsWriter(mCallback);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+    }
+
+    @Test
+    @SmallTest
+    public void setImsRegistrationFeatureTagStats() throws Exception {
+        List<String> featureTags = new ArrayList<>();
+        featureTags.add("+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcse.im\"");
+        featureTags.add("+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"");
+        featureTags.add("+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.ftsms\"");
+        mWrite.setImsRegistrationFeatureTagStats(mSubId, featureTags, mRegistrationTech);
+        assertEquals(mSubId, mCallback.subId);
+        for (int index = 0; index < featureTags.size(); index++) {
+            assertEquals(featureTags.get(index), mCallback.featureTagList.get(index));
+        }
+        assertEquals(mRegistrationTech, mCallback.registrationTech);
+    }
+
+    @Test
+    @SmallTest
+    public void setImsRegistrationServiceDescStats() throws Exception {
+        List<RcsContactPresenceTuple> tupleList = new ArrayList<>();
+        tupleList.add(getContactChatTuple());
+        tupleList.add(getContactFtTuple());
+        tupleList.add(getContactUnknown1Tuple());
+        tupleList.add(getContactUnknown2Tuple());
+        mWrite.setImsRegistrationServiceDescStats(mSubId, tupleList, mRegistrationTech);
+        assertEquals(mSubId, mCallback.subId);
+        for (int index = 0; index < tupleList.size(); index++) {
+            assertEquals(tupleList.get(index).getServiceId(), mCallback.serviceIdList.get(index));
+            assertEquals(tupleList.get(index).getServiceVersion(), mCallback.versions.get(index));
+        }
+        assertEquals(mRegistrationTech, mCallback.registrationTech);
+    }
+
+    @Test
+    @SmallTest
+    public void setSubscribeEvent() throws Exception {
+        mWrite.setSubscribeResponse(mSubId, mTaskId, mNetworkResponse);
+        assertEquals(mSubId, mCallback.subId);
+        assertEquals(mTaskId, mCallback.taskId);
+        assertTrue(mCallback.successful);
+        assertEquals(0, mCallback.commandCode);
+        assertEquals(mNetworkResponse, mCallback.networkResponse);
+    }
+
+    @Test
+    @SmallTest
+    public void setSubscribeTerminated() throws Exception {
+        mWrite.setSubscribeResponse(mSubId, mTaskId, mNetworkResponse);
+        mWrite.setSubscribeTerminated(mSubId, mTaskId, mReason);
+        assertEquals(mSubId, mCallback.subId);
+        assertEquals(mTaskId, mCallback.taskId);
+        assertEquals(mReason, mCallback.reason);
+    }
+
+    @Test
+    @SmallTest
+    public void setUceEvent() throws Exception {
+        mWrite.setUceEvent(mSubId, mType, mSuccessful, mCommandCode, mNetworkResponse);
+        assertEquals(mSubId, mCallback.subId);
+        assertEquals(mType, mCallback.type);
+        assertEquals(mSuccessful, mCallback.successful);
+        assertEquals(mCommandCode, mCallback.commandCode);
+        assertEquals(mNetworkResponse, mCallback.networkResponse);
+    }
+
+    @Test
+    @SmallTest
+    public void setPresenceNotifyEvent() throws Exception {
+        List<RcsContactUceCapability> updatedCapList = new ArrayList<>();
+        RcsContactUceCapability.PresenceBuilder builder =
+            new RcsContactUceCapability.PresenceBuilder(null, 0, 2);
+        builder.addCapabilityTuple(getContactChatTuple());
+        builder.addCapabilityTuple(getContactCallComposer2Tuple());
+        builder.addCapabilityTuple(getContactUnknown1Tuple());
+        updatedCapList.add(builder.build());
+
+        mWrite.setPresenceNotifyEvent(mSubId, mTaskId, updatedCapList);
+        assertEquals(mSubId, mCallback.subId);
+        assertEquals(mTaskId, mCallback.taskId);
+        assertEquals(updatedCapList.size(), mCallback.updatedCapList.size());
+        for (int index = 0; index < updatedCapList.size(); index++) {
+            RcsContactUceCapability input = updatedCapList.get(index);
+            RcsContactUceCapability output = mCallback.updatedCapList.get(index);
+            assertEquals(input.getCapabilityTuples().size(), output.getCapabilityTuples().size());
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void setPresenceNotifyEvent_withCallComposer2Caps() throws Exception {
+        RcsContactPresenceTuple tuple = getContactCallComposer2Tuple();
+        List<RcsContactUceCapability> updatedCapList = new ArrayList<>();
+        RcsContactUceCapability.PresenceBuilder builder =
+            new RcsContactUceCapability.PresenceBuilder(null, 0, 2);
+        builder.addCapabilityTuple(getContactCallComposer2Tuple());
+        updatedCapList.add(builder.build());
+
+        mWrite.setPresenceNotifyEvent(mSubId, mTaskId, updatedCapList);
+        assertEquals(mSubId, mCallback.subId);
+        assertEquals(mTaskId, mCallback.taskId);
+        assertEquals(updatedCapList.size(), mCallback.updatedCapList.size());
+    }
+
+    @Test
+    @SmallTest
+    public void setUnPublish() throws Exception {
+        mWrite.setUnPublish(mSubId);
+        assertEquals(mSubId, mCallback.subId);
+    }
+
+    private RcsContactPresenceTuple getContactChatTuple() {
+        RcsContactPresenceTuple.Builder builder =
+            new RcsContactPresenceTuple.Builder("open", RcsContactPresenceTuple.SERVICE_ID_CHAT_V1,
+                "1.0");
+        return builder.build();
+    }
+
+    private RcsContactPresenceTuple getContactMmtelTuple() {
+        RcsContactPresenceTuple.Builder builder =
+            new RcsContactPresenceTuple.Builder("open", RcsContactPresenceTuple.SERVICE_ID_MMTEL,
+                "1.0");
+        return builder.build();
+    }
+
+    private RcsContactPresenceTuple getContactFtTuple() {
+        RcsContactPresenceTuple.Builder builder =
+            new RcsContactPresenceTuple.Builder("open", RcsContactPresenceTuple.SERVICE_ID_FT,
+                "1.0");
+        return builder.build();
+    }
+
+    private RcsContactPresenceTuple getContactCallComposer2Tuple() {
+        RcsContactPresenceTuple.Builder builder =
+            new RcsContactPresenceTuple.Builder("open",
+                RcsContactPresenceTuple.SERVICE_ID_CALL_COMPOSER,
+                "2.0");
+        return builder.build();
+    }
+
+    private RcsContactPresenceTuple getContactUnknown1Tuple() {
+        RcsContactPresenceTuple.Builder builder =
+            new RcsContactPresenceTuple.Builder("open", "Unknown1",
+                "8.0");
+        return builder.build();
+    }
+
+    private RcsContactPresenceTuple getContactUnknown2Tuple() {
+        RcsContactPresenceTuple.Builder builder =
+            new RcsContactPresenceTuple.Builder("open", "Unknown2",
+                "9.0");
+        return builder.build();
+    }
+}
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 bf33103..1959dd0 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
@@ -19,7 +19,11 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.refEq;
 import static org.mockito.Mockito.verify;
 
 import android.content.BroadcastReceiver;
@@ -39,12 +43,18 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.ims.ImsTestBase;
+import com.android.ims.rcs.uce.UceStatsWriter;
 
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 
 @RunWith(AndroidJUnit4.class)
 public class DeviceCapabilityListenerTest extends ImsTestBase {
@@ -60,6 +70,7 @@
     @Mock DeviceCapabilityListener.ImsMmTelManagerFactory mImsMmTelMgrFactory;
     @Mock DeviceCapabilityListener.ImsRcsManagerFactory mImsRcsMgrFactory;
     @Mock DeviceCapabilityListener.ProvisioningManagerFactory mProvisioningMgrFactory;
+    @Mock UceStatsWriter mUceStatsWriter;
 
     int mSubId = 1;
 
@@ -77,6 +88,10 @@
         doReturn(true).when(mDeviceCapability).updateVtSetting(anyBoolean());
         doReturn(true).when(mDeviceCapability).updateVtSetting(anyBoolean());
         doReturn(true).when(mDeviceCapability).updateMmtelCapabilitiesChanged(any());
+
+        doNothing().when(mUceStatsWriter).setImsRegistrationFeatureTagStats(
+                anyInt(), anyList(), anyInt());
+        doNothing().when(mUceStatsWriter).setStoreCompleteImsRegistrationFeatureTagStats(anyInt());
     }
 
     @After
@@ -183,8 +198,18 @@
         DeviceCapabilityListener deviceCapListener = createDeviceCapabilityListener();
         deviceCapListener.setImsCallbackRegistered(true);
         RegistrationCallback registrationCallback = deviceCapListener.mRcsRegistrationCallback;
+
+        List<String> list = new ArrayList<>();
+        list.add("+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcse.im\"");
+        list.add("+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"");
+        list.add("+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.ftsms\"");
+        Set<String> featureTags = new HashSet<String>(list);
+
         ImsRegistrationAttributes attr = new ImsRegistrationAttributes.Builder(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE).build();
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE)
+                .setFeatureTags(featureTags)
+                .build();
+
         // Notify DeviceCapabilityListener that registered has caused a change and requires publish
         doReturn(true).when(mDeviceCapability).updateImsRcsRegistered(attr);
 
@@ -195,6 +220,8 @@
         verify(mDeviceCapability).updateImsRcsRegistered(attr);
         verify(mCallback).requestPublishFromInternal(
                 PublishController.PUBLISH_TRIGGER_RCS_REGISTERED);
+        verify(mUceStatsWriter).setImsRegistrationFeatureTagStats(anyInt(),
+            refEq(list), eq(ImsRegistrationImplBase.REGISTRATION_TECH_LTE));
     }
 
     @Test
@@ -216,6 +243,7 @@
         verify(mDeviceCapability).updateImsRcsUnregistered();
         verify(mCallback).requestPublishFromInternal(
                 PublishController.PUBLISH_TRIGGER_RCS_UNREGISTERED);
+        verify(mUceStatsWriter).setStoreCompleteImsRegistrationFeatureTagStats(anyInt());
     }
 
     @Test
@@ -237,7 +265,7 @@
 
     private DeviceCapabilityListener createDeviceCapabilityListener() {
         DeviceCapabilityListener deviceCapListener = new DeviceCapabilityListener(mContext,
-                mSubId, mDeviceCapability, mCallback);
+                mSubId, mDeviceCapability, mCallback, mUceStatsWriter);
         deviceCapListener.setImsMmTelManagerFactory(mImsMmTelMgrFactory);
         deviceCapListener.setImsRcsManagerFactory(mImsRcsMgrFactory);
         deviceCapListener.setProvisioningMgrFactory(mProvisioningMgrFactory);
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 b4c9b87..804702c 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
@@ -43,6 +43,7 @@
 import com.android.ims.RcsFeatureManager;
 import com.android.ims.rcs.uce.UceController;
 import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult;
+import com.android.ims.rcs.uce.UceStatsWriter;
 import com.android.ims.rcs.uce.presence.publish.PublishController.PublishControllerCallback;
 import com.android.ims.rcs.uce.presence.publish.PublishControllerImpl.DeviceCapListenerFactory;
 import com.android.ims.rcs.uce.presence.publish.PublishControllerImpl.PublishProcessorFactory;
@@ -68,6 +69,7 @@
     @Mock UceController.UceControllerCallback mUceCtrlCallback;
     @Mock RemoteCallbackList<IRcsUcePublishStateCallback> mPublishStateCallbacks;
     @Mock DeviceStateResult mDeviceStateResult;
+    @Mock UceStatsWriter mUceStatsWriter;
 
     private int mSubId = 1;
 
@@ -77,7 +79,7 @@
         doReturn(mPublishProcessor).when(mPublishProcessorFactory).createPublishProcessor(any(),
                 eq(mSubId), any(), any());
         doReturn(mDeviceCapListener).when(mDeviceCapListenerFactory).createDeviceCapListener(any(),
-                eq(mSubId), any(), any());
+                eq(mSubId), any(), any(), any());
         doReturn(mDeviceStateResult).when(mUceCtrlCallback).getDeviceState();
         doReturn(false).when(mDeviceStateResult).isRequestForbidden();
     }
@@ -162,6 +164,8 @@
     @SmallTest
     public void testUnpublish() throws Exception {
         PublishControllerImpl publishController = createPublishController();
+        //To initialize the public state to publish_ok.
+        publishController.setCapabilityType(RcsImsCapabilities.CAPABILITY_TYPE_OPTIONS_UCE);
 
         publishController.onUnpublish();
 
@@ -169,6 +173,8 @@
         waitForHandlerAction(handler, 1000);
         int publishState = publishController.getUcePublishState();
         assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED, publishState);
+        verify(mPublishProcessor).resetState();
+        verify(mUceStatsWriter).setUnPublish(eq(mSubId));
     }
 
     @Test
@@ -349,7 +355,7 @@
     private PublishControllerImpl createPublishController() {
         PublishControllerImpl publishController = new PublishControllerImpl(mContext, mSubId,
                 mUceCtrlCallback, Looper.getMainLooper(), mDeviceCapListenerFactory,
-                mPublishProcessorFactory);
+                mPublishProcessorFactory, mUceStatsWriter);
         publishController.setPublishStateCallback(mPublishStateCallbacks);
         publishController.setCapabilityType(RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE);
         return publishController;
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 d83158f..d894c33 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
@@ -34,6 +34,7 @@
 
 import com.android.ims.ImsTestBase;
 import com.android.ims.RcsFeatureManager;
+import com.android.ims.rcs.uce.UceStatsWriter;
 import com.android.ims.rcs.uce.presence.publish.PublishController.PublishControllerCallback;
 
 import org.junit.After;
@@ -42,6 +43,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+import java.util.Optional;
 @RunWith(AndroidJUnit4.class)
 public class PublishProcessorTest extends ImsTestBase {
 
@@ -50,6 +52,7 @@
     @Mock PublishControllerCallback mPublishCtrlCallback;
     @Mock PublishProcessorState mProcessorState;
     @Mock PublishRequestResponse mResponseCallback;
+    @Mock UceStatsWriter mUceStatsWriter;
 
     private int mSub = 1;
     private long mTaskId = 1L;
@@ -144,6 +147,8 @@
         doReturn(mTaskId).when(mProcessorState).getCurrentTaskId();
         doReturn(mTaskId).when(mResponseCallback).getTaskId();
         doReturn(true).when(mResponseCallback).needRetry();
+        doReturn(Optional.of(10)).when(mResponseCallback).getCmdErrorCode();
+
         PublishProcessor publishProcessor = getPublishProcessor();
 
         publishProcessor.onCommandError(mResponseCallback);
@@ -154,6 +159,8 @@
         verify(mResponseCallback).onDestroy();
         verify(mProcessorState).setPublishingFlag(false);
         verify(mPublishCtrlCallback).clearRequestCanceledTimer();
+        verify(mUceStatsWriter).setUceEvent(eq(mSub), eq(UceStatsWriter.PUBLISH_EVENT), eq(true),
+                eq(10), eq(0));
     }
 
     @Test
@@ -200,6 +207,7 @@
         doReturn(mTaskId).when(mResponseCallback).getTaskId();
         doReturn(false).when(mResponseCallback).needRetry();
         doReturn(true).when(mResponseCallback).isRequestSuccess();
+        doReturn(Optional.of(200)).when(mResponseCallback).getNetworkRespSipCode();
         PublishProcessor publishProcessor = getPublishProcessor();
 
         publishProcessor.onNetworkResponse(mResponseCallback);
@@ -208,6 +216,9 @@
         verify(mResponseCallback).onDestroy();
         verify(mProcessorState).setPublishingFlag(false);
         verify(mPublishCtrlCallback).clearRequestCanceledTimer();
+
+        verify(mUceStatsWriter).setUceEvent(eq(mSub), eq(UceStatsWriter.PUBLISH_EVENT), eq(true),
+                eq(0), eq(200));
     }
 
     @Test
@@ -223,7 +234,7 @@
 
     private PublishProcessor getPublishProcessor() {
         PublishProcessor publishProcessor = new PublishProcessor(mContext, mSub,
-                mDeviceCapabilities, mPublishCtrlCallback);
+                mDeviceCapabilities, mPublishCtrlCallback, mUceStatsWriter);
         publishProcessor.setProcessorState(mProcessorState);
         publishProcessor.onRcsConnected(mRcsFeatureManager);
         return publishProcessor;
diff --git a/tests/src/com/android/ims/rcs/uce/request/OptionsCoordinatorTest.java b/tests/src/com/android/ims/rcs/uce/request/OptionsCoordinatorTest.java
index 9c270fb..1759de5 100644
--- a/tests/src/com/android/ims/rcs/uce/request/OptionsCoordinatorTest.java
+++ b/tests/src/com/android/ims/rcs/uce/request/OptionsCoordinatorTest.java
@@ -38,11 +38,13 @@
 import com.android.ims.ImsTestBase;
 import com.android.ims.rcs.uce.request.UceRequestCoordinator.RequestResult;
 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
+import com.android.ims.rcs.uce.UceStatsWriter;
 
 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;
@@ -57,6 +59,7 @@
     @Mock CapabilityRequestResponse mResponse;
     @Mock RequestManagerCallback mRequestMgrCallback;
     @Mock IRcsUceControllerCallback mUceCallback;
+    @Mock UceStatsWriter mUceStatsWriter;
 
     private int mSubId = 1;
     private long mTaskId = 1L;
@@ -93,11 +96,14 @@
     @Test
     @SmallTest
     public void testRequestCommandError() throws Exception {
+        doReturn(Optional.of(3)).when(mResponse).getCommandError();
         OptionsRequestCoordinator coordinator = getOptionsCoordinator();
 
         coordinator.onRequestUpdated(mTaskId, REQUEST_UPDATE_COMMAND_ERROR);
 
         verify(mRequest).onFinish();
+        verify(mUceStatsWriter).setUceEvent(eq(mSubId), eq(UceStatsWriter.OUTGOING_OPTION_EVENT),
+            eq(false), eq(3), eq(0));
 
         Collection<UceRequest> requestList = coordinator.getActivatedRequest();
         Collection<RequestResult> resultList = coordinator.getFinishedRequest();
@@ -111,6 +117,7 @@
     public void testRequestNetworkResponse() throws Exception {
         OptionsRequestCoordinator coordinator = getOptionsCoordinator();
         doReturn(true).when(mResponse).isNetworkResponseOK();
+        doReturn(Optional.of(200)).when(mResponse).getNetworkRespSipCode();
 
         final List<RcsContactUceCapability> updatedCapList = new ArrayList<>();
         RcsContactUceCapability updatedCapability = getContactUceCapability();
@@ -124,6 +131,8 @@
         verify(mResponse).removeUpdatedCapabilities(updatedCapList);
 
         verify(mRequest).onFinish();
+        verify(mUceStatsWriter).setUceEvent(eq(mSubId), eq(UceStatsWriter.OUTGOING_OPTION_EVENT),
+            eq(true), eq(0), eq(200));
 
         Collection<UceRequest> requestList = coordinator.getActivatedRequest();
         Collection<RequestResult> resultList = coordinator.getFinishedRequest();
@@ -134,9 +143,10 @@
 
     private OptionsRequestCoordinator getOptionsCoordinator() {
         OptionsRequestCoordinator.Builder builder = new OptionsRequestCoordinator.Builder(
-                mSubId, Collections.singletonList(mRequest), mRequestMgrCallback);
+                mSubId, Collections.singletonList(mRequest), mRequestMgrCallback, mUceStatsWriter);
         builder.setCapabilitiesCallback(mUceCallback);
-        return builder.build();
+        OptionsRequestCoordinator coordinator = builder.build();
+        return coordinator;
     }
 
     private RcsContactUceCapability getContactUceCapability() {
diff --git a/tests/src/com/android/ims/rcs/uce/request/RemoteOptionsCoordinatorTest.java b/tests/src/com/android/ims/rcs/uce/request/RemoteOptionsCoordinatorTest.java
index 1a6ed4a..8007711 100644
--- a/tests/src/com/android/ims/rcs/uce/request/RemoteOptionsCoordinatorTest.java
+++ b/tests/src/com/android/ims/rcs/uce/request/RemoteOptionsCoordinatorTest.java
@@ -37,6 +37,7 @@
 import com.android.ims.rcs.uce.request.RemoteOptionsRequest.RemoteOptResponse;
 import com.android.ims.rcs.uce.request.UceRequestCoordinator.RequestResult;
 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
+import com.android.ims.rcs.uce.UceStatsWriter;
 
 import java.util.Collection;
 import java.util.Collections;
@@ -54,6 +55,7 @@
     @Mock RemoteOptResponse mResponse;
     @Mock RequestManagerCallback mRequestMgrCallback;
     @Mock IOptionsRequestCallback mOptRequestCallback;
+    @Mock UceStatsWriter mUceStatsWriter;
 
     private int mSubId = 1;
     private long mTaskId = 1L;
@@ -84,6 +86,8 @@
         verify(mOptRequestCallback).respondToCapabilityRequest(updatedCapability, true);
 
         verify(mRequest).onFinish();
+        verify(mUceStatsWriter).setUceEvent(eq(mSubId), eq(UceStatsWriter.INCOMING_OPTION_EVENT),
+            eq(true), eq(0), eq(200));
 
         Collection<UceRequest> requestList = coordinator.getActivatedRequest();
         Collection<RequestResult> resultList = coordinator.getFinishedRequest();
@@ -94,7 +98,7 @@
 
     private RemoteOptionsCoordinator getRemoteOptCoordinator() {
         RemoteOptionsCoordinator.Builder builder = new RemoteOptionsCoordinator.Builder(
-                mSubId, Collections.singletonList(mRequest), mRequestMgrCallback);
+                mSubId, Collections.singletonList(mRequest), mRequestMgrCallback, mUceStatsWriter);
         builder.setOptionsRequestCallback(mOptRequestCallback);
         return builder.build();
     }
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 137b4ac..bcd3c98 100644
--- a/tests/src/com/android/ims/rcs/uce/request/SubscribeCoordinatorTest.java
+++ b/tests/src/com/android/ims/rcs/uce/request/SubscribeCoordinatorTest.java
@@ -39,6 +39,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.net.Uri;
+import android.telephony.ims.RcsContactPresenceTuple;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
 
@@ -47,6 +48,7 @@
 
 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.request.UceRequestCoordinator.RequestResult;
 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
 
@@ -70,6 +72,7 @@
     @Mock RequestManagerCallback mRequestMgrCallback;
     @Mock IRcsUceControllerCallback mUceCallback;
     @Mock DeviceStateResult mDeviceStateResult;
+    @Mock UceStatsWriter mUceStatsWriter;
 
     private int mSubId = 1;
     private long mTaskId = 1L;
@@ -106,6 +109,7 @@
     @Test
     @SmallTest
     public void testRequestCommandError() throws Exception {
+        doReturn(Optional.of(3)).when(mResponse).getCommandError();
         SubscribeRequestCoordinator coordinator = getSubscribeCoordinator();
 
         coordinator.onRequestUpdated(mTaskId, REQUEST_UPDATE_COMMAND_ERROR);
@@ -114,6 +118,9 @@
         Collection<RequestResult> resultList = coordinator.getFinishedRequest();
         assertTrue(requestList.isEmpty());
         assertEquals(1, resultList.size());
+
+        verify(mUceStatsWriter).setUceEvent(eq(mSubId), eq(UceStatsWriter.SUBSCRIBE_EVENT),
+            eq(false), eq(3), eq(0));
         verify(mRequest).onFinish();
     }
 
@@ -122,6 +129,7 @@
     public void testRequestNetworkRespSuccess() throws Exception {
         SubscribeRequestCoordinator coordinator = getSubscribeCoordinator();
         doReturn(true).when(mResponse).isNetworkResponseOK();
+        doReturn(Optional.of(200)).when(mResponse).getNetworkRespSipCode();
 
         coordinator.onRequestUpdated(mTaskId, REQUEST_UPDATE_NETWORK_RESPONSE);
 
@@ -129,11 +137,28 @@
         Collection<RequestResult> resultList = coordinator.getFinishedRequest();
         assertEquals(1, requestList.size());
         assertTrue(resultList.isEmpty());
+
+        verify(mUceStatsWriter).setSubscribeResponse(eq(mSubId), eq(mTaskId), eq(200));
+
         verify(mRequest, never()).onFinish();
     }
 
     @Test
     @SmallTest
+    public void testRequestNetworkRespFailure() throws Exception {
+        doReturn(Optional.of(400)).when(mResponse).getNetworkRespSipCode();
+
+        SubscribeRequestCoordinator coordinator = getSubscribeCoordinator();
+
+        coordinator.onRequestUpdated(mTaskId, REQUEST_UPDATE_NETWORK_RESPONSE);
+
+        verify(mUceStatsWriter).setSubscribeResponse(eq(mSubId), eq(mTaskId), eq(400));
+
+        verify(mRequest).onFinish();
+    }
+
+    @Test
+    @SmallTest
     public void testRequestNetworkRespError() throws Exception {
         SubscribeRequestCoordinator coordinator = getSubscribeCoordinator();
         doReturn(false).when(mResponse).isNetworkResponseOK();
@@ -162,7 +187,8 @@
         SubscribeRequestCoordinator coordinator = getSubscribeCoordinator();
 
         final List<RcsContactUceCapability> updatedCapList = new ArrayList<>();
-        RcsContactUceCapability updatedCapability = getContactUceCapability();
+        RcsContactPresenceTuple tuple = getContactPresenceTuple();
+        RcsContactUceCapability updatedCapability = getContactUceCapability(tuple);
         updatedCapList.add(updatedCapability);
         doReturn(updatedCapList).when(mResponse).getUpdatedContactCapability();
 
@@ -171,6 +197,8 @@
         verify(mRequestMgrCallback).saveCapabilities(updatedCapList);
         verify(mUceCallback).onCapabilitiesReceived(updatedCapList);
         verify(mResponse).removeUpdatedCapabilities(updatedCapList);
+
+        verify(mUceStatsWriter).setPresenceNotifyEvent(eq(mSubId), eq(mTaskId), any());
     }
 
     @Test
@@ -188,6 +216,8 @@
         verify(mRequestMgrCallback).saveCapabilities(updatedCapList);
         verify(mUceCallback).onCapabilitiesReceived(updatedCapList);
         verify(mResponse).removeTerminatedResources(updatedCapList);
+
+        verify(mUceStatsWriter).setPresenceNotifyEvent(eq(mSubId), eq(mTaskId), any());
     }
 
     @Test
@@ -211,12 +241,16 @@
     public void testRequestTerminated() throws Exception {
         SubscribeRequestCoordinator coordinator = getSubscribeCoordinator();
 
+        doReturn("noresource").when(mResponse).getTerminatedReason();
+
         coordinator.onRequestUpdated(mTaskId, REQUEST_UPDATE_TERMINATED);
 
         Collection<UceRequest> requestList = coordinator.getActivatedRequest();
         Collection<RequestResult> resultList = coordinator.getFinishedRequest();
         assertTrue(requestList.isEmpty());
         assertEquals(1, resultList.size());
+
+        verify(mUceStatsWriter).setSubscribeTerminated(eq(mSubId), eq(mTaskId), eq("noresource"));
     }
 
     @Test
@@ -234,8 +268,18 @@
 
     private SubscribeRequestCoordinator getSubscribeCoordinator() {
         SubscribeRequestCoordinator.Builder builder = new SubscribeRequestCoordinator.Builder(
-                mSubId, Collections.singletonList(mRequest), mRequestMgrCallback);
+                mSubId, Collections.singletonList(mRequest), mRequestMgrCallback, mUceStatsWriter);
         builder.setCapabilitiesCallback(mUceCallback);
+        SubscribeRequestCoordinator subCoor = builder.build();
+        return subCoor;
+    }
+
+    private RcsContactUceCapability getContactUceCapability(RcsContactPresenceTuple tuple) {
+        int requestResult = RcsContactUceCapability.REQUEST_RESULT_FOUND;
+        RcsContactUceCapability.PresenceBuilder builder =
+                new RcsContactUceCapability.PresenceBuilder(
+                        mContact, RcsContactUceCapability.SOURCE_TYPE_NETWORK, requestResult);
+        builder.addCapabilityTuple(tuple);
         return builder.build();
     }
 
@@ -246,4 +290,12 @@
                         mContact, RcsContactUceCapability.SOURCE_TYPE_NETWORK, requestResult);
         return builder.build();
     }
+
+    private RcsContactPresenceTuple getContactPresenceTuple() {
+        RcsContactPresenceTuple.Builder builder =
+            new RcsContactPresenceTuple.Builder("open", RcsContactPresenceTuple.SERVICE_ID_CHAT_V1,
+                "1.0");
+        return builder.build();
+
+    }
 }