[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());
}
diff --git a/tests/src/com/android/TestContext.java b/tests/src/com/android/TestContext.java
index c190be9..b1c6923 100644
--- a/tests/src/com/android/TestContext.java
+++ b/tests/src/com/android/TestContext.java
@@ -20,6 +20,7 @@
import static org.mockito.Mockito.doReturn;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -29,6 +30,7 @@
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsManager;
import android.test.mock.MockContext;
import org.mockito.Mock;
@@ -42,6 +44,7 @@
@Mock TelecomManager mMockTelecomManager;
@Mock TelephonyManager mMockTelephonyManager;
@Mock SubscriptionManager mMockSubscriptionManager;
+ @Mock ImsManager mMockImsManager;
private PersistableBundle mCarrierConfig = new PersistableBundle();
@@ -89,6 +92,11 @@
}
@Override
+ public ContentResolver getContentResolver() {
+ return null;
+ }
+
+ @Override
public Object getSystemService(String name) {
switch (name) {
case (Context.CARRIER_CONFIG_SERVICE) : {
@@ -103,6 +111,9 @@
case (Context.TELEPHONY_SUBSCRIPTION_SERVICE) : {
return mMockSubscriptionManager;
}
+ case(Context.TELEPHONY_IMS_SERVICE) : {
+ return mMockImsManager;
+ }
}
return null;
}
diff --git a/tests/src/com/android/services/telephony/rcs/UserCapabilityExchangeImplTest.java b/tests/src/com/android/services/telephony/rcs/UserCapabilityExchangeImplTest.java
new file mode 100644
index 0000000..2457d28
--- /dev/null
+++ b/tests/src/com/android/services/telephony/rcs/UserCapabilityExchangeImplTest.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2020 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.services.telephony.rcs;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.RemoteCallbackList;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
+import android.telephony.ims.stub.RcsCapabilityExchange;
+import android.telephony.ims.stub.RcsPresenceExchangeImplBase;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.ims.RcsFeatureManager;
+import com.android.ims.RcsFeatureManager.RcsFeatureCallbacks;
+import com.android.ims.ResultCode;
+import com.android.service.ims.presence.PresenceBase;
+import com.android.service.ims.presence.PresencePublication;
+import com.android.service.ims.presence.PresencePublisher;
+import com.android.service.ims.presence.PresenceSubscriber;
+import com.android.service.ims.presence.SubscribePublisher;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public class UserCapabilityExchangeImplTest extends TelephonyTestBase {
+
+ private int mSlotId = 0;
+ private int mSubId = 1;
+ private int mUpdatedSubId = 2;
+
+ @Captor ArgumentCaptor<IRcsUcePublishStateCallback> mPublishStateCallbacksCaptor;
+
+ @Mock PresencePublication mPresencePublication;
+ @Mock PresenceSubscriber mPresenceSubscriber;
+ @Mock RcsFeatureManager mRcsFeatureManager;
+ @Mock ImsMmTelManager mImsMmTelManager;
+ @Mock RemoteCallbackList<IRcsUcePublishStateCallback> mPublishStateCallbacks;
+
+ private Looper mLooper;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ ImsManager imsManager =
+ (ImsManager) mContext.getSystemService(Context.TELEPHONY_IMS_SERVICE);
+ when(imsManager.getImsMmTelManager(mSubId)).thenReturn(mImsMmTelManager);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+
+ if (mLooper != null) {
+ mLooper.quit();
+ mLooper = null;
+ }
+ }
+
+ @Test
+ public void testServiceConnected() throws Exception {
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onRcsConnected(mRcsFeatureManager);
+
+ verify(mRcsFeatureManager).addFeatureListenerCallback(any(RcsFeatureCallbacks.class));
+ verify(mPresencePublication).updatePresencePublisher(any(PresencePublisher.class));
+ verify(mPresenceSubscriber).updatePresenceSubscriber(any(SubscribePublisher.class));
+ }
+
+ @Test
+ public void testServiceDisconnected() throws Exception {
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onRcsDisconnected();
+
+ verify(mPresencePublication).removePresencePublisher();
+ verify(mPresenceSubscriber).removePresenceSubscriber();
+ }
+
+ @Test
+ public void testSubscriptionUpdated() throws Exception {
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onAssociatedSubscriptionUpdated(mUpdatedSubId);
+
+ verify(mImsMmTelManager).registerImsRegistrationCallback(any(Executor.class),
+ any(RegistrationManager.RegistrationCallback.class));
+ verify(mImsMmTelManager).registerMmTelCapabilityCallback(any(Executor.class),
+ any(ImsMmTelManager.CapabilityCallback.class));
+ verify(mPresencePublication).handleAssociatedSubscriptionChanged(mUpdatedSubId);
+ verify(mPresenceSubscriber).handleAssociatedSubscriptionChanged(mUpdatedSubId);
+ }
+
+ @Test
+ public void testUcePublishStateRetrieval() throws Exception {
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.getUcePublishState();
+
+ verify(mPresencePublication).getPublishState();
+ }
+
+ @Test
+ public void testRegisterPublishStateCallbacks() throws Exception {
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.registerPublishStateCallback(any(IRcsUcePublishStateCallback.class));
+ verify(mPublishStateCallbacks).register(mPublishStateCallbacksCaptor.capture());
+ }
+
+ @Test
+ public void testOnNotifyUpdateCapabilities() throws Exception {
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onRcsConnected(mRcsFeatureManager);
+
+ int triggerType = RcsPresenceExchangeImplBase.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN;
+ uceImpl.mRcsFeatureCallback.onNotifyUpdateCapabilities(triggerType);
+ waitForMs(1000);
+
+ verify(mPresencePublication).onStackPublishRequested(triggerType);
+ }
+
+ @Test
+ public void testRequestPublicationWithSuccessfulResponse() throws Exception {
+ int taskId = 1;
+ int sipResponse = 200;
+ Uri contact = Uri.fromParts("sip", "test", null);
+ RcsContactUceCapability.Builder builder = new RcsContactUceCapability.Builder(contact);
+ RcsContactUceCapability capability = builder.build();
+
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onRcsConnected(mRcsFeatureManager);
+
+ doAnswer(invocation -> {
+ uceImpl.mRcsFeatureCallback.onCommandUpdate(RcsCapabilityExchange.COMMAND_CODE_SUCCESS,
+ taskId);
+ uceImpl.mRcsFeatureCallback.onNetworkResponse(sipResponse, null, taskId);
+ return null;
+ }).when(mRcsFeatureManager).requestPublication(capability, taskId);
+
+ // Request publication
+ int result = uceImpl.requestPublication(capability, contact.toString(), taskId);
+
+ assertEquals(ResultCode.SUCCESS, result);
+ verify(mPresencePublication).onCommandStatusUpdated(taskId, taskId, ResultCode.SUCCESS);
+ verify(mPresencePublication).onSipResponse(taskId, sipResponse, null);
+ }
+
+ @Test
+ public void testRequestPublicationWithFailedResponse() throws Exception {
+ int taskId = 1;
+ Uri contact = Uri.fromParts("sip", "test", null);
+ RcsContactUceCapability.Builder builder = new RcsContactUceCapability.Builder(contact);
+ RcsContactUceCapability capability = builder.build();
+
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onRcsConnected(mRcsFeatureManager);
+
+ doAnswer(invocation -> {
+ uceImpl.mRcsFeatureCallback.onCommandUpdate(
+ RcsCapabilityExchange.COMMAND_CODE_GENERIC_FAILURE, taskId);
+ return null;
+ }).when(mRcsFeatureManager).requestPublication(capability, taskId);
+
+ // Request publication
+ int result = uceImpl.requestPublication(capability, contact.toString(), taskId);
+
+ assertEquals(ResultCode.SUCCESS, result);
+ verify(mPresencePublication).onCommandStatusUpdated(taskId, taskId,
+ ResultCode.PUBLISH_GENERIC_FAILURE);
+ }
+
+ @Test
+ public void testUpdatePublisherState() throws Exception {
+ IRcsUcePublishStateCallback callback = Mockito.mock(IRcsUcePublishStateCallback.class);
+ doAnswer(invocation -> {
+ callback.onPublishStateChanged(anyInt());
+ return null;
+ }).when(mPublishStateCallbacks).broadcast(any());
+
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onRcsConnected(mRcsFeatureManager);
+ uceImpl.registerPublishStateCallback(callback);
+ uceImpl.updatePublisherState(PresenceBase.PUBLISH_STATE_200_OK);
+
+ assertEquals(PresenceBase.PUBLISH_STATE_200_OK, uceImpl.getPublisherState());
+ verify(callback).onPublishStateChanged(anyInt());
+ }
+
+ @Test
+ public void testUnpublish() throws Exception {
+ IRcsUcePublishStateCallback callback = Mockito.mock(IRcsUcePublishStateCallback.class);
+ doAnswer(invocation -> {
+ callback.onPublishStateChanged(anyInt());
+ return null;
+ }).when(mPublishStateCallbacks).broadcast(any());
+
+ UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+ uceImpl.onRcsConnected(mRcsFeatureManager);
+ uceImpl.mRcsFeatureCallback.onUnpublish();
+ waitForMs(1000);
+
+ assertEquals(PresenceBase.PUBLISH_STATE_NOT_PUBLISHED, uceImpl.getPublisherState());
+ verify(callback).onPublishStateChanged(anyInt());
+ }
+
+ private UserCapabilityExchangeImpl createUserCapabilityExchangeImpl() throws Exception {
+ HandlerThread handlerThread = new HandlerThread("UceImplHandlerThread");
+ handlerThread.start();
+ mLooper = handlerThread.getLooper();
+ UserCapabilityExchangeImpl uceImpl = new UserCapabilityExchangeImpl(mContext, mSlotId,
+ mSubId, mLooper, mPresencePublication, mPresenceSubscriber,
+ mPublishStateCallbacks);
+ verify(mPresencePublication).handleAssociatedSubscriptionChanged(1);
+ verify(mPresenceSubscriber).handleAssociatedSubscriptionChanged(1);
+ waitForHandlerAction(uceImpl.getHandler(), 1000);
+ verify(mImsMmTelManager, atLeast(1)).registerImsRegistrationCallback(
+ any(Executor.class), any(RegistrationManager.RegistrationCallback.class));
+ verify(mContext).registerReceiver(any(), any());
+ return uceImpl;
+ }
+}