[RCS] Integrate the PresencePublisher in UserCapabilityExchangeImpl

Implement the interface PresencePublisher which was called from PresencePublication

Bug: 139262111
Test: atest UserCapabilityExchangeImplTest
Change-Id: I6f0d1ec3eec3aadfbf493b44771ae176536f7e15
diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index 3ea8df2..f00572c 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -29,6 +29,7 @@
 import android.telephony.ims.aidl.IImsRcsController;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
 import android.telephony.ims.feature.RcsFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
@@ -195,6 +196,40 @@
         }
     }
 
+    @Override
+    public void registerUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c) {
+        enforceReadPrivilegedPermission("registerUcePublishStateCallback");
+        final long token = Binder.clearCallingIdentity();
+        try {
+            UserCapabilityExchangeImpl uce = getRcsFeatureController(subId).getFeature(
+                    UserCapabilityExchangeImpl.class);
+            if (uce == null) {
+                throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+                    "This subscription does not support UCE.");
+            }
+            uce.registerPublishStateCallback(c);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void unregisterUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c) {
+        enforceReadPrivilegedPermission("unregisterUcePublishStateCallback");
+        final long token = Binder.clearCallingIdentity();
+        try {
+            UserCapabilityExchangeImpl uce = getRcsFeatureController(subId).getFeature(
+                    UserCapabilityExchangeImpl.class);
+            if (uce == null) {
+                throw new ServiceSpecificException(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION,
+                    "This subscription does not support UCE.");
+            }
+            uce.unregisterUcePublishStateCallback(c);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     /**
      * Query for the capability of an IMS RCS service
      *
diff --git a/src/com/android/services/telephony/rcs/RcsFeatureController.java b/src/com/android/services/telephony/rcs/RcsFeatureController.java
index 5094c57..fcfe312 100644
--- a/src/com/android/services/telephony/rcs/RcsFeatureController.java
+++ b/src/com/android/services/telephony/rcs/RcsFeatureController.java
@@ -20,9 +20,7 @@
 import android.content.Context;
 import android.net.Uri;
 import android.telephony.ims.ImsException;
-import android.telephony.ims.ImsRcsManager;
 import android.telephony.ims.ImsReasonInfo;
-import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -131,13 +129,14 @@
                     try {
                         // May throw ImsException if for some reason the connection to the
                         // ImsService is gone.
+                        updateConnectionStatus(manager);
                         setupConnectionToService(manager);
                     } catch (ImsException e) {
+                        updateConnectionStatus(null /*manager*/);
                         // Use deprecated Exception for compatibility.
                         throw new com.android.ims.ImsException(e.getMessage(),
                                 ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
                     }
-                    updateConnectionStatus(manager);
                 }
 
                 @Override
diff --git a/src/com/android/services/telephony/rcs/TelephonyRcsService.java b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
index c85e9a9..69d8f82 100644
--- a/src/com/android/services/telephony/rcs/TelephonyRcsService.java
+++ b/src/com/android/services/telephony/rcs/TelephonyRcsService.java
@@ -274,6 +274,7 @@
         synchronized (mLock) {
             for (int i = 0; i < mNumSlots; i++) {
                 RcsFeatureController f = mFeatureControllers.get(i);
+                if (f == null) continue;
                 pw.increaseIndent();
                 f.dump(fd, printWriter, args);
                 pw.decreaseIndent();
diff --git a/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java b/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
index ac8f9bf..0606d32 100644
--- a/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
+++ b/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
@@ -16,16 +16,43 @@
 
 package com.android.services.telephony.rcs;
 
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.provider.Settings;
+import android.provider.Telephony;
+import android.telecom.TelecomManager;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.RcsUceAdapter;
+import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.RcsCapabilityExchange;
+import android.telephony.ims.stub.RcsPresenceExchangeImplBase;
 import android.util.Log;
 
 import com.android.ims.RcsFeatureManager;
+import com.android.ims.RcsFeatureManager.RcsFeatureCallbacks;
 import com.android.ims.ResultCode;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.phone.R;
 import com.android.service.ims.presence.ContactCapabilityResponse;
 import com.android.service.ims.presence.PresenceBase;
@@ -34,7 +61,10 @@
 import com.android.service.ims.presence.PresenceSubscriber;
 import com.android.service.ims.presence.SubscribePublisher;
 
+import java.lang.ref.WeakReference;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
@@ -46,12 +76,30 @@
 
     private static final String LOG_TAG = "RcsUceImpl";
 
-    private int mSlotId;
-    private int mSubId;
+    private final int mSlotId;
+    private volatile int mSubId;
+    private volatile boolean mImsContentChangedCallbackRegistered = false;
+    // The result of requesting publish
+    private volatile int mPublishState = PresenceBase.PUBLISH_STATE_NOT_PUBLISHED;
+    // The network type which IMS registers on
+    private volatile int mNetworkRegistrationType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+    // The MMTel capabilities of this subscription Id
+    private MmTelFeature.MmTelCapabilities mMmTelCapabilities;
+    private final Object mCapabilitiesLock = new Object();
+
+    private final Context mContext;
+    private final UceImplHandler mUceImplHandler;
+    private RcsFeatureManager mRcsFeatureManager;
 
     private final PresencePublication mPresencePublication;
     private final PresenceSubscriber mPresenceSubscriber;
 
+    // The task Ids of updating capabilities
+    private final Set<Integer> mRequestingPublishTaskIds = new HashSet<>();
+
+    // The callbacks to notify publish state changed.
+    private final RemoteCallbackList<IRcsUcePublishStateCallback> mPublishStateCallbacks;
+
     private final ConcurrentHashMap<Integer, IRcsUceControllerCallback> mPendingCapabilityRequests =
             new ConcurrentHashMap<>();
 
@@ -60,6 +108,13 @@
         mSubId = subId;
         logi("created");
 
+        mContext = context;
+        mPublishStateCallbacks = new RemoteCallbackList<>();
+
+        HandlerThread handlerThread = new HandlerThread("UceImplHandlerThread");
+        handlerThread.start();
+        mUceImplHandler = new UceImplHandler(this, handlerThread.getLooper());
+
         String[] volteError = context.getResources().getStringArray(
                 R.array.config_volte_provision_error_on_publish_response);
         String[] rcsError = context.getResources().getStringArray(
@@ -73,13 +128,31 @@
                 volteError, rcsError);
 
         onAssociatedSubscriptionUpdated(mSubId);
+        registerReceivers();
     }
 
+    @VisibleForTesting
+    UserCapabilityExchangeImpl(Context context, int slotId, int subId, Looper looper,
+            PresencePublication presencePublication, PresenceSubscriber presenceSubscriber,
+            RemoteCallbackList<IRcsUcePublishStateCallback> publishStateCallbacks) {
+        mSlotId = slotId;
+        mSubId = subId;
+        mContext = context;
+        mPublishStateCallbacks = publishStateCallbacks;
+        mUceImplHandler = new UceImplHandler(this, looper);
+        mPresencePublication = presencePublication;
+        mPresenceSubscriber = presenceSubscriber;
+        onAssociatedSubscriptionUpdated(mSubId);
+        registerReceivers();
+    }
 
     // Runs on main thread.
     @Override
     public void onRcsConnected(RcsFeatureManager rcsFeatureManager) {
         logi("onRcsConnected");
+        mRcsFeatureManager = rcsFeatureManager;
+        mRcsFeatureManager.addFeatureListenerCallback(mRcsFeatureCallback);
+
         mPresencePublication.updatePresencePublisher(this);
         mPresenceSubscriber.updatePresenceSubscriber(this);
     }
@@ -90,11 +163,22 @@
         logi("onRcsDisconnected");
         mPresencePublication.removePresencePublisher();
         mPresenceSubscriber.removePresenceSubscriber();
+
+        if (mRcsFeatureManager != null) {
+            mRcsFeatureManager.releaseConnection();
+            mRcsFeatureManager = null;
+        }
     }
 
     // Runs on main thread.
     @Override
     public void onAssociatedSubscriptionUpdated(int subId) {
+        logi("onAssociatedSubscriptionUpdated: new subId=" + subId);
+
+        // Listen to the IMS content changed with new subId.
+        mUceImplHandler.registerImsContentChangedReceiver(subId);
+
+        mSubId = subId;
         mPresencePublication.handleAssociatedSubscriptionChanged(subId);
         mPresenceSubscriber.handleAssociatedSubscriptionChanged(subId);
     }
@@ -105,6 +189,10 @@
      */
     // Called on main thread.
     public void onDestroy() {
+        logi("onDestroy");
+        mUceImplHandler.getLooper().quit();
+        unregisterReceivers();
+        unregisterImsProvisionCallback(mSubId);
         onRcsDisconnected();
     }
 
@@ -117,6 +205,52 @@
         return toUcePublishState(publishState);
     }
 
+    @VisibleForTesting
+    public UceImplHandler getHandler() {
+        return mUceImplHandler;
+    }
+
+    /**
+     * Register receiver to receive UCE publish state changed.
+     */
+    public void registerPublishStateCallback(IRcsUcePublishStateCallback c) {
+        synchronized (mPublishStateCallbacks) {
+            mPublishStateCallbacks.register(c);
+        }
+    }
+
+    /**
+     * Unregister UCE publish state callback.
+     */
+    public void unregisterUcePublishStateCallback(IRcsUcePublishStateCallback c) {
+        synchronized (mPublishStateCallbacks) {
+            mPublishStateCallbacks.unregister(c);
+        }
+    }
+
+    private void clearPublishStateCallbacks() {
+        synchronized (mPublishStateCallbacks) {
+            logi("clearPublishStateCallbacks");
+            final int lastIndex = mPublishStateCallbacks.getRegisteredCallbackCount() - 1;
+            for (int index = lastIndex; index >= 0; index--) {
+                IRcsUcePublishStateCallback callback =
+                        mPublishStateCallbacks.getRegisteredCallbackItem(index);
+                mPublishStateCallbacks.unregister(callback);
+            }
+        }
+    }
+
+    private void notifyPublishStateChanged(@PresenceBase.PresencePublishState int state) {
+        int result = toUcePublishState(state);
+        mPublishStateCallbacks.broadcast(c -> {
+            try {
+                c.onPublishStateChanged(result);
+            } catch (RemoteException e) {
+                logw("notifyPublishStateChanged error: " + e);
+            }
+        });
+    }
+
     /**
      * Perform a capabilities request and call {@link IRcsUceControllerCallback} with the result.
      */
@@ -191,15 +325,169 @@
         mPendingCapabilityRequests.put(taskId, c);
     }
 
+    /**
+     * The feature callback is to receive the request and update from RcsPresExchangeImplBase
+     */
+    @VisibleForTesting
+    public RcsFeatureCallbacks mRcsFeatureCallback = new RcsFeatureCallbacks() {
+        public void onCommandUpdate(int commandCode, int operationToken) {
+            logi("onCommandUpdate: code=" + commandCode + ", token=" + operationToken);
+            onCommandUpdateForPublishRequest(commandCode, operationToken);
+        }
+
+        /** See {@link RcsPresenceExchangeImplBase#onNetworkResponse(int, String, int)} */
+        public void onNetworkResponse(int responseCode, String reason, int operationToken) {
+            logi("onNetworkResponse: code=" + responseCode + ", reason=" + reason
+                    + ", operationToken=" + operationToken);
+            onNetworkResponseForPublishRequest(responseCode, reason, operationToken);
+        }
+
+        /** See {@link RcsPresenceExchangeImplBase#onCapabilityRequestResponse(List, int)} */
+        public void onCapabilityRequestResponsePresence(List<RcsContactUceCapability> infos,
+                int operationToken) {
+
+        }
+
+        /** See {@link RcsPresenceExchangeImplBase#onNotifyUpdateCapabilites(int)} */
+        public void onNotifyUpdateCapabilities(int publishTriggerType) {
+            logi("onNotifyUpdateCapabilities: type=" + publishTriggerType);
+            mUceImplHandler.notifyUpdateCapabilities(publishTriggerType);
+        }
+
+        /** See {@link RcsPresenceExchangeImplBase#onUnpublish()} */
+        public void onUnpublish() {
+            logi("onUnpublish");
+            mUceImplHandler.unpublish();
+        }
+    };
+
+    private static class UceImplHandler extends Handler {
+        private static final int EVENT_REGISTER_IMS_CHANGED_RECEIVER = 1;
+        private static final int EVENT_NOTIFY_UPDATE_CAPABILITIES = 2;
+        private static final int EVENT_UNPUBLISH = 3;
+
+        private static final int REGISTER_IMS_CHANGED_DELAY = 10000;  //10 seconds
+
+        private final WeakReference<UserCapabilityExchangeImpl> mUceImplRef;
+
+        UceImplHandler(UserCapabilityExchangeImpl uceImpl, Looper looper) {
+            super(looper);
+            mUceImplRef = new WeakReference(uceImpl);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            UserCapabilityExchangeImpl uceImpl = mUceImplRef.get();
+            if (uceImpl == null) {
+                return;
+            }
+            switch (msg.what) {
+                case EVENT_REGISTER_IMS_CHANGED_RECEIVER:
+                    int subId = msg.arg1;
+                    uceImpl.registerImsContentChangedReceiverInternal(subId);
+                    break;
+                case EVENT_NOTIFY_UPDATE_CAPABILITIES:
+                    int publishTriggerType = msg.arg1;
+                    uceImpl.onNotifyUpdateCapabilities(publishTriggerType);
+                    break;
+                case EVENT_UNPUBLISH:
+                    uceImpl.updatePublisherState(PresenceBase.PUBLISH_STATE_NOT_PUBLISHED);
+                    break;
+                default:
+                    Log.w(LOG_TAG, "handleMessage: error=" + msg.what);
+                    break;
+            }
+        }
+
+        private void retryRegisteringImsContentChangedReceiver(int subId) {
+            sendRegisteringImsContentChangedMessage(subId, REGISTER_IMS_CHANGED_DELAY);
+        }
+
+        private void registerImsContentChangedReceiver(int subId) {
+            sendRegisteringImsContentChangedMessage(subId, 0);
+        }
+
+        private void sendRegisteringImsContentChangedMessage(int subId, int delay) {
+            if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                return;
+            }
+            removeRegisteringImsContentChangedReceiver();
+            Message message = obtainMessage(EVENT_REGISTER_IMS_CHANGED_RECEIVER);
+            message.arg1 = subId;
+            sendMessageDelayed(message, delay);
+        }
+
+        private void removeRegisteringImsContentChangedReceiver() {
+            removeMessages(EVENT_REGISTER_IMS_CHANGED_RECEIVER);
+        }
+
+        private void notifyUpdateCapabilities(int publishTriggerType) {
+            Message message = obtainMessage(EVENT_NOTIFY_UPDATE_CAPABILITIES);
+            message.arg1 = publishTriggerType;
+            sendMessage(message);
+        }
+
+        private void unpublish() {
+            sendEmptyMessage(EVENT_UNPUBLISH);
+        }
+    }
+
+    private void onNotifyUpdateCapabilities(int publishTriggerType) {
+        mPresencePublication.onStackPublishRequested(publishTriggerType);
+    }
+
     @Override
-    public int getPublisherState() {
-        return 0;
+    public @PresenceBase.PresencePublishState int getPublisherState() {
+        return mPublishState;
     }
 
     @Override
     public int requestPublication(RcsContactUceCapability capabilities, String contactUri,
             int taskId) {
-        return 0;
+        if (mRcsFeatureManager == null) {
+            logw("requestPublication error: RcsFeatureManager is null.");
+            return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+        }
+
+        logi("requestPublication: taskId=" + taskId);
+        addPublishRequestTaskId(taskId);
+
+        try {
+            mRcsFeatureManager.requestPublication(capabilities, taskId);
+        } catch (Exception ex) {
+            logw("requestPublication error: " + ex.getMessage());
+            removePublishRequestTaskId(taskId);
+            return ResultCode.PUBLISH_GENERIC_FAILURE;
+        }
+        return ResultCode.SUCCESS;
+    }
+
+    /*
+     * Handle the callback method RcsFeatureCallbacks#onCommandUpdate(int, int)
+     */
+    private void onCommandUpdateForPublishRequest(int commandCode, int operationToken) {
+        if (!isPublishRequestExisted(operationToken)) {
+            return;
+        }
+        int resultCode = ResultCode.SUCCESS;
+        if (commandCode != RcsCapabilityExchange.COMMAND_CODE_SUCCESS) {
+            logw("Command is failed: taskId=" + operationToken + ", code=" + commandCode);
+            removePublishRequestTaskId(operationToken);
+            resultCode = ResultCode.PUBLISH_GENERIC_FAILURE;
+        }
+        mPresencePublication.onCommandStatusUpdated(operationToken, operationToken, resultCode);
+    }
+
+    /*
+     * Handle the callback method RcsFeatureCallbacks#onNetworkResponse(int, String, int)
+     */
+    private void onNetworkResponseForPublishRequest(int responseCode, String reason,
+            int operationToken) {
+        if (!isPublishRequestExisted(operationToken)) {
+            return;
+        }
+        removePublishRequestTaskId(operationToken);
+        mPresencePublication.onSipResponse(operationToken, responseCode, reason);
     }
 
     @Override
@@ -218,8 +506,28 @@
     }
 
     @Override
-    public void updatePublisherState(int publishState) {
+    public void updatePublisherState(@PresenceBase.PresencePublishState int publishState) {
+        logi("updatePublisherState: from " + mPublishState + " to " + publishState);
+        mPublishState = publishState;
+        notifyPublishStateChanged(publishState);
+    }
 
+    private void addPublishRequestTaskId(int taskId) {
+        synchronized (mRequestingPublishTaskIds) {
+            mRequestingPublishTaskIds.add(taskId);
+        }
+    }
+
+    private void removePublishRequestTaskId(int taskId) {
+        synchronized (mRequestingPublishTaskIds) {
+            mRequestingPublishTaskIds.remove(taskId);
+        }
+    }
+
+    private boolean isPublishRequestExisted(Integer taskId) {
+        synchronized (mRequestingPublishTaskIds) {
+            return mRequestingPublishTaskIds.contains(taskId);
+        }
     }
 
     private static String getNumberFromUri(Uri uri) {
@@ -274,6 +582,218 @@
         }
     }
 
+    /*
+     * Register receivers for updating capabilities
+     */
+    private void registerReceivers() {
+        IntentFilter filter = new IntentFilter(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
+        filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        mContext.registerReceiver(mReceiver, filter);
+
+        ContentResolver resolver = mContext.getContentResolver();
+        if (resolver != null) {
+            // Register mobile data content changed.
+            resolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.MOBILE_DATA), false,
+                    mMobileDataObserver);
+
+            // Register SIM info content changed.
+            resolver.registerContentObserver(Telephony.SimInfo.CONTENT_URI, false,
+                    mSimInfoContentObserver);
+        }
+    }
+
+    private void unregisterReceivers() {
+        mContext.unregisterReceiver(mReceiver);
+        ContentResolver resolver = mContext.getContentResolver();
+        if (resolver != null) {
+            resolver.unregisterContentObserver(mMobileDataObserver);
+            resolver.unregisterContentObserver(mSimInfoContentObserver);
+        }
+    }
+
+    /**
+     * Register IMS and provision content changed.
+     *
+     * Call the UceImplHandler#registerImsContentChangedReceiver instead of
+     * calling this method directly.
+     */
+    private void registerImsContentChangedReceiverInternal(int subId) {
+        mUceImplHandler.removeRegisteringImsContentChangedReceiver();
+        try {
+            final int originalSubId = mSubId;
+            if ((originalSubId == subId) && (mImsContentChangedCallbackRegistered)) {
+                logi("registerImsContentChangedReceiverInternal: already registered. skip");
+                return;
+            }
+            // Unregister original IMS and Provision callback
+            unregisterImsProvisionCallback(originalSubId);
+            // Register new IMS and Provision callback
+            registerImsProvisionCallback(subId);
+        } catch (ImsException e) {
+            logw("registerImsContentChangedReceiverInternal error: " + e);
+            mUceImplHandler.retryRegisteringImsContentChangedReceiver(subId);
+        }
+    }
+
+    private void unregisterImsProvisionCallback(int subId) {
+        if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return;
+        }
+        // Unregister IMS callback
+        ImsMmTelManager imsMmtelManager = getImsMmTelManager(subId);
+        if (imsMmtelManager != null) {
+            imsMmtelManager.unregisterImsRegistrationCallback(mImsRegistrationCallback);
+            imsMmtelManager.unregisterMmTelCapabilityCallback(mCapabilityCallback);
+        }
+        // Unregister provision changed callback
+        ProvisioningManager provisioningManager =
+                ProvisioningManager.createForSubscriptionId(subId);
+        provisioningManager.unregisterProvisioningChangedCallback(mProvisioningChangedCallback);
+
+        // Remove all publish state callbacks
+        clearPublishStateCallbacks();
+
+        mImsContentChangedCallbackRegistered = false;
+    }
+
+    private void registerImsProvisionCallback(int subId) throws ImsException {
+        if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return;
+        }
+        // Register IMS callback
+        ImsMmTelManager imsMmtelManager = getImsMmTelManager(subId);
+        if (imsMmtelManager != null) {
+            imsMmtelManager.registerImsRegistrationCallback(mContext.getMainExecutor(),
+                    mImsRegistrationCallback);
+            imsMmtelManager.registerMmTelCapabilityCallback(mContext.getMainExecutor(),
+                    mCapabilityCallback);
+        }
+        // Register provision changed callback
+        ProvisioningManager provisioningManager =
+                ProvisioningManager.createForSubscriptionId(subId);
+        provisioningManager.registerProvisioningChangedCallback(mContext.getMainExecutor(),
+                mProvisioningChangedCallback);
+
+        mImsContentChangedCallbackRegistered = true;
+        logi("registerImsProvisionCallback");
+    }
+
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent == null) return;
+            switch (intent.getAction()) {
+                case TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED:
+                    int preferredMode = intent.getIntExtra(
+                            TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF);
+                    logi("TTY preferred mode changed: " + preferredMode);
+                    mPresencePublication.onTtyPreferredModeChanged(preferredMode);
+                    break;
+
+                case Intent.ACTION_AIRPLANE_MODE_CHANGED:
+                    boolean airplaneMode = intent.getBooleanExtra("state", false);
+                    logi("Airplane mode changed: " + airplaneMode);
+                    mPresencePublication.onAirplaneModeChanged(airplaneMode);
+                    break;
+            }
+        }
+    };
+
+    private ContentObserver mMobileDataObserver = new ContentObserver(
+            new Handler(Looper.getMainLooper())) {
+        @Override
+        public void onChange(boolean selfChange) {
+            boolean isEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.MOBILE_DATA, 1) == 1;
+            logi("Mobile data changed: enabled=" + isEnabled);
+            mPresencePublication.onMobileDataChanged(isEnabled);
+        }
+    };
+
+    private ContentObserver mSimInfoContentObserver = new ContentObserver(
+            new Handler(Looper.getMainLooper())) {
+        @Override
+        public void onChange(boolean selfChange) {
+            if (mSubId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                return;
+            }
+            ImsMmTelManager ims = getImsMmTelManager(mSubId);
+            boolean isEnabled = ims.isVtSettingEnabled();
+            logi("SimInfo changed: VT setting=" + isEnabled);
+            mPresencePublication.onVtEnabled(isEnabled);
+        }
+    };
+
+    private RegistrationManager.RegistrationCallback mImsRegistrationCallback =
+            new RegistrationManager.RegistrationCallback() {
+        @Override
+        public void onRegistered(int imsTransportType) {
+            logi("onRegistered: type=" + imsTransportType);
+            mNetworkRegistrationType = imsTransportType;
+            mPresencePublication.onImsConnected();
+
+            // Also trigger PresencePublication#onFeatureCapabilityChanged method
+            MmTelFeature.MmTelCapabilities capabilities = null;
+            synchronized (mCapabilitiesLock) {
+                capabilities = mMmTelCapabilities;
+            }
+            mPresencePublication.onFeatureCapabilityChanged(mNetworkRegistrationType, capabilities);
+        }
+
+        @Override
+        public void onUnregistered(ImsReasonInfo info) {
+            logi("onUnregistered");
+            mNetworkRegistrationType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+
+            // Also trigger PresencePublication#onFeatureCapabilityChanged method
+            MmTelFeature.MmTelCapabilities capabilities = null;
+            synchronized (mCapabilitiesLock) {
+                capabilities = mMmTelCapabilities;
+            }
+            mPresencePublication.onFeatureCapabilityChanged(mNetworkRegistrationType, capabilities);
+            mPresencePublication.onImsDisconnected();
+        }
+    };
+
+    private ImsMmTelManager.CapabilityCallback mCapabilityCallback =
+            new ImsMmTelManager.CapabilityCallback() {
+        @Override
+        public void onCapabilitiesStatusChanged(MmTelFeature.MmTelCapabilities capabilities) {
+            synchronized (mCapabilitiesLock) {
+                mMmTelCapabilities = capabilities;
+            }
+            mPresencePublication.onFeatureCapabilityChanged(mNetworkRegistrationType, capabilities);
+        }
+    };
+
+    private ProvisioningManager.Callback mProvisioningChangedCallback =
+            new ProvisioningManager.Callback() {
+        @Override
+        public void onProvisioningIntChanged(int item, int value) {
+            logi("onProvisioningIntChanged: item=" + item);
+            switch (item) {
+                case ProvisioningManager.KEY_EAB_PROVISIONING_STATUS:
+                case ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS:
+                case ProvisioningManager.KEY_VT_PROVISIONING_STATUS:
+                    mPresencePublication.handleProvisioningChanged();
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    private ImsMmTelManager getImsMmTelManager(int subId) {
+        try {
+            ImsManager imsManager = (ImsManager) mContext.getSystemService(
+                    Context.TELEPHONY_IMS_SERVICE);
+            return (imsManager == null) ? null : imsManager.getImsMmTelManager(subId);
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
+
     private void logi(String log) {
         Log.i(LOG_TAG, getLogPrefix().append(log).toString());
     }