aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/java/com/android/ims/FeatureConnection.java175
-rw-r--r--src/java/com/android/ims/FeatureConnector.java381
-rw-r--r--src/java/com/android/ims/FeatureUpdates.java72
-rw-r--r--src/java/com/android/ims/IFeatureConnector.java24
-rw-r--r--src/java/com/android/ims/ImsFeatureBinderRepository.java438
-rw-r--r--src/java/com/android/ims/ImsManager.java262
-rw-r--r--src/java/com/android/ims/MmTelFeatureConnection.java118
-rw-r--r--src/java/com/android/ims/RcsFeatureConnection.java106
-rw-r--r--src/java/com/android/ims/RcsFeatureManager.java148
-rw-r--r--tests/src/com/android/ims/FeatureConnectionTest.java77
-rw-r--r--tests/src/com/android/ims/FeatureConnectorTest.java389
-rw-r--r--tests/src/com/android/ims/ImsFeatureBinderRepositoryTest.java361
-rw-r--r--tests/src/com/android/ims/ImsFeatureContainerTest.java71
-rw-r--r--tests/src/com/android/ims/ImsManagerTest.java15
14 files changed, 1795 insertions, 842 deletions
diff --git a/src/java/com/android/ims/FeatureConnection.java b/src/java/com/android/ims/FeatureConnection.java
index d8f881be..f13444e0 100644
--- a/src/java/com/android/ims/FeatureConnection.java
+++ b/src/java/com/android/ims/FeatureConnection.java
@@ -18,7 +18,6 @@ package com.android.ims;
import android.annotation.Nullable;
import android.content.Context;
-import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
@@ -29,11 +28,7 @@ import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;
-import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.util.HandlerExecutor;
-
-import java.util.concurrent.Executor;
/**
* Base class of MmTelFeatureConnection and RcsFeatureConnection.
@@ -41,51 +36,26 @@ import java.util.concurrent.Executor;
public abstract class FeatureConnection {
protected static final String TAG = "FeatureConnection";
- public interface IFeatureUpdate {
- /**
- * Called when the ImsFeature has changed its state. Use
- * {@link ImsFeature#getFeatureState()} to get the new state.
- */
- void notifyStateChanged();
-
- /**
- * Called when the ImsFeature has become unavailable due to the binder switching or app
- * crashing. A new ImsServiceProxy should be requested for that feature.
- */
- void notifyUnavailable();
- }
-
protected static boolean sImsSupportedOnDevice = true;
protected final int mSlotId;
protected Context mContext;
protected IBinder mBinder;
- @VisibleForTesting
- public Executor mExecutor;
// We are assuming the feature is available when started.
protected volatile boolean mIsAvailable = true;
// ImsFeature Status from the ImsService. Cached.
protected Integer mFeatureStateCached = null;
- protected IFeatureUpdate mStatusCallback;
- protected IImsRegistration mRegistrationBinder;
- protected IImsConfig mConfigBinder;
+ protected long mFeatureCapabilities;
+ private final IImsRegistration mRegistrationBinder;
+ private final IImsConfig mConfigBinder;
protected final Object mLock = new Object();
- public FeatureConnection(Context context, int slotId) {
+ public FeatureConnection(Context context, int slotId, IImsConfig c, IImsRegistration r) {
mSlotId = slotId;
mContext = context;
-
- // Callbacks should be scheduled on the main thread.
- if (context.getMainLooper() != null) {
- mExecutor = context.getMainExecutor();
- } else {
- // Fallback to the current thread.
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
- mExecutor = new HandlerExecutor(new Handler(Looper.myLooper()));
- }
+ mRegistrationBinder = r;
+ mConfigBinder = c;
}
protected TelephonyManager getTelephonyManager() {
@@ -128,58 +98,13 @@ public abstract class FeatureConnection {
synchronized (mLock) {
if (mIsAvailable) {
mIsAvailable = false;
- mRegistrationBinder = null;
if (mBinder != null) {
mBinder.unlinkToDeath(mDeathRecipient, 0);
}
- if (mStatusCallback != null) {
- Log.d(TAG, "onRemovedOrDied: notifyUnavailable");
- mStatusCallback.notifyUnavailable();
- // Unlink because this FeatureConnection should no longer send callbacks.
- mStatusCallback = null;
- }
}
}
}
- /**
- * The listener for ImsManger and RcsFeatureManager to receive IMS feature status changed.
- * @param callback Callback that will fire when the feature status has changed.
- */
- public void setStatusCallback(IFeatureUpdate callback) {
- mStatusCallback = callback;
- }
-
- @VisibleForTesting
- public IImsServiceFeatureCallback getListener() {
- return mListenerBinder;
- }
-
- /**
- * The callback to receive ImsFeature status changed.
- */
- private final IImsServiceFeatureCallback mListenerBinder =
- new IImsServiceFeatureCallback.Stub() {
- @Override
- public void imsFeatureCreated(int slotId, int feature) {
- mExecutor.execute(() -> {
- handleImsFeatureCreatedCallback(slotId, feature);
- });
- }
- @Override
- public void imsFeatureRemoved(int slotId, int feature) {
- mExecutor.execute(() -> {
- handleImsFeatureRemovedCallback(slotId, feature);
- });
- }
- @Override
- public void imsStatusChanged(int slotId, int feature, int status) {
- mExecutor.execute(() -> {
- handleImsStatusChangedCallback(slotId, feature, status);
- });
- }
- };
-
public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTech()
throws RemoteException {
IImsRegistration registration = getRegistration();
@@ -192,41 +117,10 @@ public abstract class FeatureConnection {
}
public @Nullable IImsRegistration getRegistration() {
- synchronized (mLock) {
- // null if cache is invalid;
- if (mRegistrationBinder != null) {
- return mRegistrationBinder;
- }
- }
- // We don't want to synchronize on a binder call to another process.
- IImsRegistration regBinder = getRegistrationBinder();
- synchronized (mLock) {
- // mRegistrationBinder may have changed while we tried to get the registration
- // interface.
- if (mRegistrationBinder == null) {
- mRegistrationBinder = regBinder;
- }
- }
return mRegistrationBinder;
}
- public @Nullable
- IImsConfig getConfig() {
- synchronized (mLock) {
- // null if cache is invalid;
- if (mConfigBinder != null) {
- return mConfigBinder;
- }
- }
- // We don't want to synchronize on a binder call to another process.
- IImsConfig configBinder = getConfigBinder();
- synchronized (mLock) {
- // mRegistrationBinder may have changed while we tried to get the registration
- // interface.
- if (mConfigBinder == null) {
- mConfigBinder = configBinder;
- }
- }
+ public @Nullable IImsConfig getConfig() {
return mConfigBinder;
}
@@ -260,6 +154,27 @@ public abstract class FeatureConnection {
return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
}
+ public void updateFeatureState(int state) {
+ synchronized (mLock) {
+ mFeatureStateCached = state;
+ }
+ }
+
+ public long getFeatureCapabilties() {
+ synchronized (mLock) {
+ return mFeatureCapabilities;
+ }
+ }
+
+ public void updateFeatureCapabilities(long caps) {
+ synchronized (mLock) {
+ if (mFeatureCapabilities != caps) {
+ mFeatureCapabilities = caps;
+ onFeatureCapabilitiesUpdated(caps);
+ }
+ }
+ }
+
/**
* @return an integer describing the current Feature Status, defined in
* {@link ImsFeature.ImsState}.
@@ -285,41 +200,9 @@ public abstract class FeatureConnection {
}
/**
- * An ImsFeature has been created for this FeatureConnection for the associated
- * {@link ImsFeature.FeatureType}.
- * @param slotId The slot ID associated with the event.
- * @param feature The {@link ImsFeature.FeatureType} associated with the event.
- */
- protected abstract void handleImsFeatureCreatedCallback(int slotId, int feature);
-
- /**
- * An ImsFeature has been removed for this FeatureConnection for the associated
- * {@link ImsFeature.FeatureType}.
- * @param slotId The slot ID associated with the event.
- * @param feature The {@link ImsFeature.FeatureType} associated with the event.
- */
- protected abstract void handleImsFeatureRemovedCallback(int slotId, int feature);
-
- /**
- * The status of an ImsFeature has changed for the associated {@link ImsFeature.FeatureType}.
- * @param slotId The slot ID associated with the event.
- * @param feature The {@link ImsFeature.FeatureType} associated with the event.
- * @param status The new {@link ImsFeature.ImsState} associated with the ImsFeature
- */
- protected abstract void handleImsStatusChangedCallback(int slotId, int feature, int status);
-
- /**
* Internal method used to retrieve the feature status from the corresponding ImsService.
*/
protected abstract Integer retrieveFeatureState();
- /**
- * @return The ImsRegistration instance associated with the FeatureConnection.
- */
- protected abstract IImsRegistration getRegistrationBinder();
-
- /**
- * @return The ImsRegistration instance associated with the FeatureConnection.
- */
- protected abstract IImsConfig getConfigBinder();
+ protected abstract void onFeatureCapabilitiesUpdated(long capabilities);
}
diff --git a/src/java/com/android/ims/FeatureConnector.java b/src/java/com/android/ims/FeatureConnector.java
index e7c1c74a..e3fbf73f 100644
--- a/src/java/com/android/ims/FeatureConnector.java
+++ b/src/java/com/android/ims/FeatureConnector.java
@@ -16,114 +16,221 @@
package com.android.ims;
+import android.annotation.IntDef;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.os.Handler;
-import android.os.Looper;
+import android.os.RemoteException;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsService;
import android.telephony.ims.feature.ImsFeature;
+import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.util.HandlerExecutor;
import com.android.telephony.Rlog;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.Executor;
/**
* Helper class for managing a connection to the ImsFeature manager.
*/
-public class FeatureConnector<T extends IFeatureConnector> extends Handler {
+public class FeatureConnector<U extends FeatureUpdates> {
private static final String TAG = "FeatureConnector";
private static final boolean DBG = false;
- // Initial condition for ims connection retry.
- private static final int IMS_RETRY_STARTING_TIMEOUT_MS = 500; // ms
+ /**
+ * This Connection has become unavailable due to the ImsService being disconnected due to
+ * an event such as SIM Swap, carrier configuration change, etc...
+ *
+ * {@link Listener#connectionReady} will be called when a new Manager is available.
+ */
+ public static final int UNAVAILABLE_REASON_DISCONNECTED = 0;
+
+ /**
+ * This Connection has become unavailable due to the ImsService moving to the NOT_READY state.
+ *
+ * {@link Listener#connectionReady} will be called when the manager moves back to ready.
+ */
+ public static final int UNAVAILABLE_REASON_NOT_READY = 1;
+
+ /**
+ * IMS is not supported on this device. This should be considered a permanent error and
+ * a Manager will never become available.
+ */
+ public static final int UNAVAILABLE_REASON_IMS_UNSUPPORTED = 2;
+
+ /**
+ * The server of this information has crashed or otherwise generated an error that will require
+ * a retry to connect. This is rare, however in this case, {@link #disconnect()} and
+ * {@link #connect()} will need to be called again to recreate the connection with the server.
+ * <p>
+ * Only applicable if this is used outside of the server's own process.
+ */
+ public static final int UNAVAILABLE_REASON_SERVER_UNAVAILABLE = 3;
- // Ceiling bitshift amount for service query timeout, calculated as:
- // 2^mImsServiceRetryCount * IMS_RETRY_STARTING_TIMEOUT_MS, where
- // mImsServiceRetryCount ∊ [0, CEILING_SERVICE_RETRY_COUNT].
- private static final int CEILING_SERVICE_RETRY_COUNT = 6;
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "UNAVAILABLE_REASON_", value = {
+ UNAVAILABLE_REASON_DISCONNECTED,
+ UNAVAILABLE_REASON_NOT_READY,
+ UNAVAILABLE_REASON_IMS_UNSUPPORTED,
+ UNAVAILABLE_REASON_SERVER_UNAVAILABLE
+ })
+ public @interface UnavailableReason {}
- public interface Listener<T> {
+ /**
+ * Factory used to create a new instance of the manager that this FeatureConnector is waiting
+ * to connect the FeatureConnection to.
+ * @param <U> The Manager that this FeatureConnector has been created for.
+ */
+ public interface ManagerFactory<U extends FeatureUpdates> {
/**
- * Get ImsFeature manager instance
+ * Create a manager instance, which will connect to the FeatureConnection.
*/
- T getFeatureManager();
+ U createManager(Context context, int phoneId);
+ }
+ /**
+ * Listener interface used by Listeners of FeatureConnector that are waiting for a Manager
+ * interface for a specific ImsFeature.
+ * @param <U> The Manager that the listener is listening for.
+ */
+ public interface Listener<U extends FeatureUpdates> {
/**
* ImsFeature manager is connected to the underlying IMS implementation.
*/
- void connectionReady(T manager) throws ImsException;
+ void connectionReady(U manager) throws ImsException;
/**
* The underlying IMS implementation is unavailable and can not be used to communicate.
*/
- void connectionUnavailable();
- }
-
- public interface RetryTimeout {
- int get();
+ void connectionUnavailable(@UnavailableReason int reason);
}
- protected final int mPhoneId;
- protected final Context mContext;
- protected final Executor mExecutor;
- protected final Object mLock = new Object();
- protected final String mLogPrefix;
+ private final IImsServiceFeatureCallback mCallback = new IImsServiceFeatureCallback.Stub() {
- @VisibleForTesting
- public Listener<T> mListener;
+ @Override
+ public void imsFeatureCreated(ImsFeatureContainer c) {
+ log("imsFeatureCreated: " + c);
+ synchronized (mLock) {
+ mManager.associate(c.imsFeature, c.imsConfig, c.imsRegistration);
+ mManager.updateFeatureCapabilities(c.getCapabilities());
+ mDisconnectedReason = null;
+ }
+ // Notifies executor, so notify outside of lock
+ imsStatusChanged(c.getState());
+ }
- // The IMS feature manager which interacts with ImsService
- @VisibleForTesting
- public T mManager;
+ @Override
+ public void imsFeatureRemoved(@UnavailableReason int reason) {
+ log("imsFeatureRemoved: reason=" + reason);
+ synchronized (mLock) {
+ // only generate new events if the disconnect event isn't the same as before.
+ if (mDisconnectedReason != null && mDisconnectedReason.equals(reason)) {
+ log("imsFeatureRemoved: ignore");
+ return;
+ }
+ mManager.invalidate();
+ mDisconnectedReason = reason;
+ // Ensure that we set ready state back to false so that we do not miss setting ready
+ // later if the initial state when recreated is READY.
+ mLastReadyState = false;
+ }
+ mExecutor.execute(() -> mListener.connectionUnavailable(reason));
+ }
- protected int mRetryCount = 0;
+ @Override
+ public void imsStatusChanged(int status) {
+ log("imsStatusChanged: status=" + ImsFeature.STATE_LOG_MAP.get(status));
+ synchronized (mLock) {
+ if (mDisconnectedReason != null) {
+ log("imsStatusChanged: ignore");
+ return;
+ }
+ mManager.updateFeatureState(status);
+ final U manager = mManager;
+ final boolean isReady = mReadyFilter.contains(status);
+ boolean didReadyChange = isReady ^ mLastReadyState;
+ mLastReadyState = isReady;
+ if (!didReadyChange) {
+ log("imsStatusChanged: ready didn't change, ignore");
+ return;
+ }
+ mExecutor.execute(() -> {
+ try {
+ if (isReady) {
+ notifyReady(manager);
+ } else {
+ notifyNotReady();
+ }
+ } catch (ImsException e) {
+ if (e.getCode()
+ == ImsReasonInfo.CODE_LOCAL_IMS_NOT_SUPPORTED_ON_DEVICE) {
+ mListener.connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+ } else {
+ notifyNotReady();
+ }
+ }
+ });
+ }
+ }
- @VisibleForTesting
- public RetryTimeout mRetryTimeout = () -> {
- synchronized (mLock) {
- int timeout = (1 << mRetryCount) * IMS_RETRY_STARTING_TIMEOUT_MS;
- if (mRetryCount <= CEILING_SERVICE_RETRY_COUNT) {
- mRetryCount++;
+ @Override
+ public void updateCapabilities(long caps) {
+ log("updateCapabilities: capabilities=" + ImsService.getCapabilitiesString(caps));
+ synchronized (mLock) {
+ if (mDisconnectedReason != null) {
+ log("updateCapabilities: ignore");
+ return;
+ }
+ mManager.updateFeatureCapabilities(caps);
}
- return timeout;
}
};
- public FeatureConnector(Context context, int phoneId, Listener<T> listener,
- String logPrefix) {
- mContext = context;
- mPhoneId = phoneId;
- mListener = listener;
- mExecutor = new HandlerExecutor(this);
- mLogPrefix = logPrefix;
- }
+ private final int mPhoneId;
+ private final Context mContext;
+ private final ManagerFactory<U> mFactory;
+ private final Listener<U> mListener;
+ private final Executor mExecutor;
+ private final Object mLock = new Object();
+ private final String mLogPrefix;
+ // A List of integers, each corresponding to an ImsFeature.ImsState, that the FeatureConnector
+ // will use to call Listener#connectionReady when the ImsFeature that this connector is waiting
+ // for changes into one of the states in this list.
+ private final List<Integer> mReadyFilter = new ArrayList<>();
+
+ private U mManager;
+ // Start in disconnected state;
+ private Integer mDisconnectedReason = UNAVAILABLE_REASON_DISCONNECTED;
+ // Record state to cut down on logging
+ private boolean mIsOneShot = false;
+ // Stop redundant connectionAvailable if the ready filter contains multiple states.
+ // Also, do not send the first unavailable until after we have moved to available once.
+ private boolean mLastReadyState = false;
+
- @VisibleForTesting
- public FeatureConnector(Context context, int phoneId, Listener<T> listener,
- Executor executor, String logPrefix) {
- mContext = context;
- mPhoneId = phoneId;
- mListener= listener;
- mExecutor = executor;
- mLogPrefix = logPrefix;
- }
@VisibleForTesting
- public FeatureConnector(Context context, int phoneId, Listener<T> listener,
- Executor executor, Looper looper) {
- super(looper);
+ public FeatureConnector(Context context, int phoneId, ManagerFactory<U> factory,
+ String logPrefix, List<Integer> readyFilter, Listener<U> listener, Executor executor) {
mContext = context;
mPhoneId = phoneId;
- mListener= listener;
+ mFactory = factory;
+ mLogPrefix = logPrefix;
+ mReadyFilter.addAll(readyFilter);
+ mListener = listener;
mExecutor = executor;
- mLogPrefix = "?";
}
/**
* Start the creation of a connection to the underlying ImsService implementation. When the
- * service is connected, {@link FeatureConnector.Listener#connectionReady(Object)} will be
+ * service is connected, {@link FeatureConnector.Listener#connectionReady} will be
* called with an active instance.
*
* If this device does not support an ImsStack (i.e. doesn't support
@@ -132,133 +239,58 @@ public class FeatureConnector<T extends IFeatureConnector> extends Handler {
public void connect() {
if (DBG) log("connect");
if (!isSupported()) {
+ mExecutor.execute(() -> mListener.connectionUnavailable(
+ UNAVAILABLE_REASON_IMS_UNSUPPORTED));
logw("connect: not supported.");
return;
}
- mRetryCount = 0;
+ synchronized (mLock) {
+ mIsOneShot = false;
+ if (mManager == null) {
+ mManager = mFactory.createManager(mContext, mPhoneId);
+ }
+ mManager.registerFeatureCallback(mPhoneId, mCallback, false /*oneShot*/);
+ }
+ }
- // Send a message to connect to the Ims Service and open a connection through
- // getImsService().
- post(mGetServiceRunnable);
+ public void connectForOneShot() {
+ if (DBG) log("connectForOneShot");
+ if (!isSupported()) {
+ mExecutor.execute(() -> mListener.connectionUnavailable(
+ UNAVAILABLE_REASON_IMS_UNSUPPORTED));
+ logw("connectForOneShot: not supported.");
+ return;
+ }
+ synchronized (mLock) {
+ mIsOneShot = true;
+ if (mManager == null) {
+ mManager = mFactory.createManager(mContext, mPhoneId);
+ }
+ mManager.registerFeatureCallback(mPhoneId, mCallback, true /*oneShot*/);
+ }
}
// Check if this ImsFeature is supported or not.
private boolean isSupported() {
- return ImsManager.isImsSupportedOnDevice(mContext);
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
}
/**
* Disconnect from the ImsService Implementation and clean up. When this is complete,
- * {@link FeatureConnector.Listener#connectionUnavailable()} will be called one last time.
+ * {@link FeatureConnector.Listener#connectionUnavailable(int)} will be called one last time.
*/
public void disconnect() {
if (DBG) log("disconnect");
- removeCallbacks(mGetServiceRunnable);
- synchronized (mLock) {
- if (mManager != null) {
- mManager.removeNotifyStatusChangedCallback(mNotifyStatusChangedCallback);
- }
- }
- notifyNotReady();
- }
-
- private final Runnable mGetServiceRunnable = () -> {
- try {
- createImsService();
- } catch (android.telephony.ims.ImsException e) {
- int errorCode = e.getCode();
- if (DBG) logw("Create IMS service error: " + errorCode);
- if (android.telephony.ims.ImsException.CODE_ERROR_UNSUPPORTED_OPERATION != errorCode) {
- // Retry when error is not CODE_ERROR_UNSUPPORTED_OPERATION
- retryGetImsService();
- }
- }
- };
-
- @VisibleForTesting
- public void createImsService() throws android.telephony.ims.ImsException {
synchronized (mLock) {
- if (DBG) log("createImsService");
- mManager = mListener.getFeatureManager();
- // Adding to set, will be safe adding multiple times. If the ImsService is not
- // active yet, this method will throw an ImsException.
- mManager.addNotifyStatusChangedCallbackIfAvailable(mNotifyStatusChangedCallback);
- }
- // Wait for ImsService.STATE_READY to start listening for calls.
- // Call the callback right away for compatibility with older devices that do not use
- // states.
- mNotifyStatusChangedCallback.notifyStateChanged();
- }
-
- /**
- * Remove callback and re-running mGetServiceRunnable
- */
- public void retryGetImsService() {
- if (mManager != null) {
- // remove callback so we do not receive updates from old ImsServiceProxy when
- // switching between ImsServices.
- mManager.removeNotifyStatusChangedCallback(mNotifyStatusChangedCallback);
- //Leave mImsManager as null, then CallStateException will be thrown when dialing
- mManager = null;
+ mManager.unregisterFeatureCallback(mCallback);
+ try {
+ mCallback.imsFeatureRemoved(UNAVAILABLE_REASON_DISCONNECTED);
+ } catch (RemoteException ignore) {} // local call
}
-
- // Exponential backoff during retry, limited to 32 seconds.
- removeCallbacks(mGetServiceRunnable);
- int timeout = mRetryTimeout.get();
- postDelayed(mGetServiceRunnable, timeout);
- if (DBG) log("retryGetImsService: unavailable, retrying in " + timeout + " ms");
}
- // Callback fires when IMS Feature changes state
- public FeatureConnection.IFeatureUpdate mNotifyStatusChangedCallback =
- new FeatureConnection.IFeatureUpdate() {
- @Override
- public void notifyStateChanged() {
- mExecutor.execute(() -> {
- try {
- int status = ImsFeature.STATE_UNAVAILABLE;
- synchronized (mLock) {
- if (mManager != null) {
- status = mManager.getImsServiceState();
- }
- }
- switch (status) {
- case ImsFeature.STATE_READY: {
- notifyReady();
- break;
- }
- case ImsFeature.STATE_INITIALIZING:
- // fall through
- case ImsFeature.STATE_UNAVAILABLE: {
- notifyNotReady();
- break;
- }
- default: {
- logw("Unexpected State! " + status);
- }
- }
- } catch (ImsException e) {
- // Could not get the ImsService, retry!
- notifyNotReady();
- retryGetImsService();
- }
- });
- }
-
- @Override
- public void notifyUnavailable() {
- mExecutor.execute(() -> {
- notifyNotReady();
- retryGetImsService();
- });
- }
- };
-
- private void notifyReady() throws ImsException {
- T manager;
- synchronized (mLock) {
- manager = mManager;
- }
+ // Should be called on executor
+ private void notifyReady(U manager) throws ImsException {
try {
if (DBG) log("notifyReady");
mListener.connectionReady(manager);
@@ -267,18 +299,19 @@ public class FeatureConnector<T extends IFeatureConnector> extends Handler {
if(DBG) log("notifyReady exception: " + e.getMessage());
throw e;
}
- // Only reset retry count if connectionReady does not generate an ImsException/
- synchronized (mLock) {
- mRetryCount = 0;
- }
}
- protected void notifyNotReady() {
+ // Should be called on executor.
+ private void notifyNotReady() {
if (DBG) log("notifyNotReady");
- mListener.connectionUnavailable();
+ mListener.connectionUnavailable(UNAVAILABLE_REASON_NOT_READY);
}
private final void log(String message) {
+ // cut down on log spam
+ synchronized (mLock) {
+ if (mIsOneShot) return;
+ }
Rlog.d(TAG, "[" + mLogPrefix + ", " + mPhoneId + "] " + message);
}
diff --git a/src/java/com/android/ims/FeatureUpdates.java b/src/java/com/android/ims/FeatureUpdates.java
new file mode 100644
index 00000000..29f9cd5e
--- /dev/null
+++ b/src/java/com/android/ims/FeatureUpdates.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2019 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;
+
+import android.os.IBinder;
+import android.telephony.ims.ImsService;
+import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IImsRegistration;
+import android.telephony.ims.feature.ImsFeature;
+
+import com.android.ims.internal.IImsServiceFeatureCallback;
+
+/**
+ * Interface used by Manager interfaces that will use a {@link FeatureConnector} to connect to
+ * remote ImsFeature Binder interfaces.
+ */
+public interface FeatureUpdates {
+ /**
+ * Register a calback for the slot specified so that the FeatureConnector can notify its
+ * listener of changes.
+ * @param slotId The slot the callback is registered for.
+ * @param cb The callback that the FeatureConnector will use to update its state and notify
+ * its callback of changes.
+ * @param oneShot True if this callback should only be registered for one update (feature is
+ * available or not), false if this listener should be persistent.
+ */
+ void registerFeatureCallback(int slotId, IImsServiceFeatureCallback cb, boolean oneShot);
+
+ /**
+ * Unregister a previously registered callback due to the FeatureConnector disconnecting.
+ * <p>
+ * This does not need to be called if the callback was previously registered for a one
+ * shot result.
+ * @param cb The callback to unregister.
+ */
+ void unregisterFeatureCallback(IImsServiceFeatureCallback cb);
+
+ /**
+ * Associate this Manager instance with the IMS Binder interfaces specified. This is usually
+ * done by creating a FeatureConnection instance with these interfaces.
+ */
+ void associate(IBinder feature, IImsConfig c, IImsRegistration r);
+
+ /**
+ * Invalidate the previously associated Binder interfaces set in {@link #associate}.
+ */
+ void invalidate();
+
+ /**
+ * Update the state of the remote ImsFeature associated with this Manager instance.
+ */
+ void updateFeatureState(@ImsFeature.ImsState int state);
+
+ /**
+ * Update the capabilities of the remove ImsFeature associated with this Manager instance.
+ */
+ void updateFeatureCapabilities(@ImsService.ImsServiceCapability long capabilities);
+} \ No newline at end of file
diff --git a/src/java/com/android/ims/IFeatureConnector.java b/src/java/com/android/ims/IFeatureConnector.java
deleted file mode 100644
index 66428ce7..00000000
--- a/src/java/com/android/ims/IFeatureConnector.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (c) 2019 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;
-
-public interface IFeatureConnector<T> {
- int getImsServiceState() throws ImsException;
- void addNotifyStatusChangedCallbackIfAvailable(FeatureConnection.IFeatureUpdate callback)
- throws android.telephony.ims.ImsException;
- void removeNotifyStatusChangedCallback(FeatureConnection.IFeatureUpdate callback);
-} \ No newline at end of file
diff --git a/src/java/com/android/ims/ImsFeatureBinderRepository.java b/src/java/com/android/ims/ImsFeatureBinderRepository.java
new file mode 100644
index 00000000..538e5cf1
--- /dev/null
+++ b/src/java/com/android/ims/ImsFeatureBinderRepository.java
@@ -0,0 +1,438 @@
+/*
+ * 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.ims;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.telephony.ims.ImsService;
+import android.telephony.ims.feature.ImsFeature;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.ims.internal.IImsServiceFeatureCallback;
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
+
+/**
+ * A repository of ImsFeature connections made available by an ImsService once it has been
+ * successfully bound.
+ *
+ * Provides the ability for listeners to register callbacks and the repository notify registered
+ * listeners when a connection has been created/removed for a specific connection type.
+ */
+public class ImsFeatureBinderRepository {
+
+ private static final String TAG = "ImsFeatureBinderRepo";
+
+ /**
+ * Internal class representing a listener that is listening for changes to specific
+ * ImsFeature instances.
+ */
+ private static class ListenerContainer {
+ private final IImsServiceFeatureCallback mCallback;
+ private final Executor mExecutor;
+
+ public ListenerContainer(@NonNull IImsServiceFeatureCallback c, @NonNull Executor e) {
+ mCallback = c;
+ mExecutor = e;
+ }
+
+ public void notifyFeatureCreatedOrRemoved(ImsFeatureContainer connector) {
+ if (connector == null) {
+ mExecutor.execute(() -> {
+ try {
+ mCallback.imsFeatureRemoved(
+ FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
+ } catch (RemoteException e) {
+ // This listener will eventually be caught and removed during stale checks.
+ }
+ });
+ }
+ else {
+ mExecutor.execute(() -> {
+ try {
+ mCallback.imsFeatureCreated(connector);
+ } catch (RemoteException e) {
+ // This listener will eventually be caught and removed during stale checks.
+ }
+ });
+ }
+ }
+
+ public void notifyStateChanged(int state) {
+ mExecutor.execute(() -> {
+ try {
+ mCallback.imsStatusChanged(state);
+ } catch (RemoteException e) {
+ // This listener will eventually be caught and removed during stale checks.
+ }
+ });
+ }
+
+ public void notifyUpdateCapabilties(long caps) {
+ mExecutor.execute(() -> {
+ try {
+ mCallback.updateCapabilities(caps);
+ } catch (RemoteException e) {
+ // This listener will eventually be caught and removed during stale checks.
+ }
+ });
+ }
+
+ public boolean isStale() {
+ return !mCallback.asBinder().isBinderAlive();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ListenerContainer that = (ListenerContainer) o;
+ // Do not count executor for equality.
+ return mCallback.equals(that.mCallback);
+ }
+
+ @Override
+ public int hashCode() {
+ // Do not use executor for hash.
+ return Objects.hash(mCallback);
+ }
+
+ @Override
+ public String toString() {
+ return "ListenerContainer{" + "cb=" + mCallback + '}';
+ }
+ }
+
+ /**
+ * Contains the mapping from ImsFeature type (MMTEL/RCS) to List of listeners listening for
+ * updates to the ImsFeature instance contained in the ImsFeatureContainer.
+ */
+ private static final class UpdateMapper {
+ public final int phoneId;
+ public final @ImsFeature.FeatureType int imsFeatureType;
+ private final List<ListenerContainer> mListeners = new ArrayList<>();
+ private ImsFeatureContainer mFeatureContainer;
+ private final Object mLock = new Object();
+
+
+ public UpdateMapper(int pId, @ImsFeature.FeatureType int t) {
+ phoneId = pId;
+ imsFeatureType = t;
+ }
+
+ public void addFeatureContainer(ImsFeatureContainer c) {
+ List<ListenerContainer> listeners;
+ synchronized (mLock) {
+ if (Objects.equals(c, mFeatureContainer)) return;
+ mFeatureContainer = c;
+ listeners = copyListenerList(mListeners);
+ }
+ listeners.forEach(l -> l.notifyFeatureCreatedOrRemoved(mFeatureContainer));
+ }
+
+ public ImsFeatureContainer removeFeatureContainer() {
+ ImsFeatureContainer oldContainer;
+ List<ListenerContainer> listeners;
+ synchronized (mLock) {
+ if (mFeatureContainer == null) return null;
+ oldContainer = mFeatureContainer;
+ mFeatureContainer = null;
+ listeners = copyListenerList(mListeners);
+ }
+ listeners.forEach(l -> l.notifyFeatureCreatedOrRemoved(mFeatureContainer));
+ return oldContainer;
+ }
+
+ public ImsFeatureContainer getFeatureContainer() {
+ synchronized(mLock) {
+ return mFeatureContainer;
+ }
+ }
+
+ public void addListener(ListenerContainer c) {
+ ImsFeatureContainer featureContainer;
+ synchronized (mLock) {
+ removeStaleListeners();
+ if (mListeners.contains(c)) {
+ return;
+ }
+ featureContainer = mFeatureContainer;
+ mListeners.add(c);
+ }
+ // Do not call back until the feature container has been set.
+ if (featureContainer != null) {
+ c.notifyFeatureCreatedOrRemoved(featureContainer);
+ }
+ }
+
+ public void removeListener(IImsServiceFeatureCallback callback) {
+ synchronized (mLock) {
+ removeStaleListeners();
+ List<ListenerContainer> oldListeners = mListeners.stream()
+ .filter((c) -> Objects.equals(c.mCallback, callback))
+ .collect(Collectors.toList());
+ mListeners.removeAll(oldListeners);
+ }
+ }
+
+ public void notifyStateUpdated(int newState) {
+ ImsFeatureContainer featureContainer;
+ List<ListenerContainer> listeners;
+ synchronized (mLock) {
+ removeStaleListeners();
+ featureContainer = mFeatureContainer;
+ listeners = copyListenerList(mListeners);
+ if (mFeatureContainer != null) {
+ if (mFeatureContainer.getState() != newState) {
+ mFeatureContainer.setState(newState);
+ }
+ }
+ }
+ // Only update if the feature container is set.
+ if (featureContainer != null) {
+ listeners.forEach(l -> l.notifyStateChanged(newState));
+ }
+ }
+
+ public void notifyUpdateCapabilities(long caps) {
+ ImsFeatureContainer featureContainer;
+ List<ListenerContainer> listeners;
+ synchronized (mLock) {
+ removeStaleListeners();
+ featureContainer = mFeatureContainer;
+ listeners = copyListenerList(mListeners);
+ if (mFeatureContainer != null) {
+ if (mFeatureContainer.getCapabilities() != caps) {
+ mFeatureContainer.setCapabilities(caps);
+ }
+ }
+ }
+ // Only update if the feature container is set.
+ if (featureContainer != null) {
+ listeners.forEach(l -> l.notifyUpdateCapabilties(caps));
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void removeStaleListeners() {
+ List<ListenerContainer> staleListeners = mListeners.stream().filter(
+ ListenerContainer::isStale)
+ .collect(Collectors.toList());
+ mListeners.removeAll(staleListeners);
+ }
+
+ @Override
+ public String toString() {
+ synchronized (mLock) {
+ return "UpdateMapper{" + "phoneId=" + phoneId + ", type="
+ + ImsFeature.FEATURE_LOG_MAP.get(imsFeatureType) + ", container="
+ + mFeatureContainer + '}';
+ }
+ }
+
+
+ private List<ListenerContainer> copyListenerList(List<ListenerContainer> listeners) {
+ return new ArrayList<>(listeners);
+ }
+ }
+
+ private final List<UpdateMapper> mFeatures = new ArrayList<>();
+ private final LocalLog mLocalLog = new LocalLog(50 /*lines*/);
+
+ public ImsFeatureBinderRepository() {
+ logInfoLineLocked(-1, "FeatureConnectionRepository - created");
+ }
+
+ /**
+ * Get the Container for a specific ImsFeature now if it exists.
+ *
+ * @param phoneId The phone ID that the connection is related to.
+ * @param type The ImsFeature type to get the cotnainr for (MMTEL/RCS).
+ * @return The Container containing the requested ImsFeature if it exists.
+ */
+ public Optional<ImsFeatureContainer> getIfExists(
+ int phoneId, @ImsFeature.FeatureType int type) {
+ if (type < 0 || type >= ImsFeature.FEATURE_MAX) {
+ throw new IllegalArgumentException("Incorrect feature type");
+ }
+ UpdateMapper m;
+ m = getUpdateMapper(phoneId, type);
+ ImsFeatureContainer c = m.getFeatureContainer();
+ logVerboseLineLocked(phoneId, "getIfExists, type= " + ImsFeature.FEATURE_LOG_MAP.get(type)
+ + ", result= " + c);
+ return Optional.ofNullable(c);
+ }
+
+ /**
+ * Register a callback that will receive updates when the requested ImsFeature type becomes
+ * available or unavailable for the specified phone ID.
+ * <p>
+ * This callback will not be called the first time until there is a valid ImsFeature.
+ * @param phoneId The phone ID that the connection will be related to.
+ * @param type The ImsFeature type to get (MMTEL/RCS).
+ * @param callback The callback that will be used to notify when the callback is
+ * available/unavailable.
+ * @param executor The executor that the callback will be run on.
+ */
+ public void registerForConnectionUpdates(int phoneId,
+ @ImsFeature.FeatureType int type, @NonNull IImsServiceFeatureCallback callback,
+ @NonNull Executor executor) {
+ if (type < 0 || type >= ImsFeature.FEATURE_MAX || callback == null || executor == null) {
+ throw new IllegalArgumentException("One or more invalid arguments have been passed in");
+ }
+ ListenerContainer container = new ListenerContainer(callback, executor);
+ logInfoLineLocked(phoneId, "registerForConnectionUpdates, type= "
+ + ImsFeature.FEATURE_LOG_MAP.get(type) +", conn= " + container);
+ UpdateMapper m = getUpdateMapper(phoneId, type);
+ m.addListener(container);
+ }
+
+ /**
+ * Unregister for updates on a previously registered callback.
+ *
+ * @param callback The callback to unregister.
+ */
+ public void unregisterForConnectionUpdates(@NonNull IImsServiceFeatureCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("this method does not accept null arguments");
+ }
+ logInfoLineLocked(-1, "unregisterForConnectionUpdates, callback= " + callback);
+ synchronized (mFeatures) {
+ for (UpdateMapper m : mFeatures) {
+ // warning: no callbacks should be called while holding locks
+ m.removeListener(callback);
+ }
+ }
+ }
+
+ /**
+ * Add a Container containing the IBinder interfaces associated with a specific ImsFeature type
+ * (MMTEL/RCS). If one already exists, it will be replaced. This will notify listeners of the
+ * change.
+ * @param phoneId The phone ID associated with this Container.
+ * @param type The ImsFeature type to get (MMTEL/RCS).
+ * @param newConnection A Container containing the IBinder interface connections associated with
+ * the ImsFeature type.
+ */
+ public void addConnection(int phoneId, @ImsFeature.FeatureType int type,
+ @Nullable ImsFeatureContainer newConnection) {
+ if (type < 0 || type >= ImsFeature.FEATURE_MAX) {
+ throw new IllegalArgumentException("The type must valid");
+ }
+ logInfoLineLocked(phoneId, "addConnection, type=" + ImsFeature.FEATURE_LOG_MAP.get(type)
+ + ", conn=" + newConnection);
+ UpdateMapper m = getUpdateMapper(phoneId, type);
+ m.addFeatureContainer(newConnection);
+ }
+
+ /**
+ * Remove the IBinder Container associated with a specific ImsService type. Listeners will be
+ * notified of this change.
+ * @param phoneId The phone ID associated with this connection.
+ * @param type The ImsFeature type to get (MMTEL/RCS).
+ */
+ public ImsFeatureContainer removeConnection(int phoneId, @ImsFeature.FeatureType int type) {
+ if (type < 0 || type >= ImsFeature.FEATURE_MAX) {
+ throw new IllegalArgumentException("The type must valid");
+ }
+ logInfoLineLocked(phoneId, "removeConnection, type="
+ + ImsFeature.FEATURE_LOG_MAP.get(type));
+ UpdateMapper m = getUpdateMapper(phoneId, type);
+ return m.removeFeatureContainer();
+ }
+
+ /**
+ * Notify listeners that the state of a specific ImsFeature that this repository is
+ * tracking has changed. Listeners will be notified of the change in the ImsFeature's state.
+ * @param phoneId The phoneId of the feature that has changed state.
+ * @param type The ImsFeature type to get (MMTEL/RCS).
+ * @param state The new state of the ImsFeature
+ */
+ public void notifyFeatureStateChanged(int phoneId, @ImsFeature.FeatureType int type,
+ @ImsFeature.ImsState int state) {
+ logInfoLineLocked(phoneId, "notifyFeatureStateChanged, type="
+ + ImsFeature.FEATURE_LOG_MAP.get(type) + ", state="
+ + ImsFeature.STATE_LOG_MAP.get(state));
+ UpdateMapper m = getUpdateMapper(phoneId, type);
+ m.notifyStateUpdated(state);
+ }
+
+ /**
+ * Notify listeners that the capabilities of a specific ImsFeature that this repository is
+ * tracking has changed. Listeners will be notified of the change in the ImsFeature's
+ * capabilities.
+ * @param phoneId The phoneId of the feature that has changed capabilities.
+ * @param type The ImsFeature type to get (MMTEL/RCS).
+ * @param capabilities The new capabilities of the ImsFeature
+ */
+ public void notifyFeatureCapabilitiesChanged(int phoneId, @ImsFeature.FeatureType int type,
+ @ImsService.ImsServiceCapability long capabilities) {
+ logInfoLineLocked(phoneId, "notifyFeatureCapabilitiesChanged, type="
+ + ImsFeature.FEATURE_LOG_MAP.get(type) + ", caps="
+ + ImsService.getCapabilitiesString(capabilities));
+ UpdateMapper m = getUpdateMapper(phoneId, type);
+ m.notifyUpdateCapabilities(capabilities);
+ }
+
+ /**
+ * Prints the dump of log events that have occurred on this repository.
+ */
+ public void dump(PrintWriter printWriter) {
+ synchronized (mLocalLog) {
+ mLocalLog.dump(printWriter);
+ }
+ }
+
+ private UpdateMapper getUpdateMapper(int phoneId, int type) {
+ synchronized (mFeatures) {
+ UpdateMapper mapper = mFeatures.stream()
+ .filter((c) -> ((c.phoneId == phoneId) && (c.imsFeatureType == type)))
+ .findFirst().orElse(null);
+ if (mapper == null) {
+ mapper = new UpdateMapper(phoneId, type);
+ mFeatures.add(mapper);
+ }
+ return mapper;
+ }
+ }
+
+ private void logVerboseLineLocked(int phoneId, String log) {
+ if (!Log.isLoggable(TAG, Log.VERBOSE)) return;
+ final String phoneIdPrefix = "[" + phoneId + "] ";
+ Log.v(TAG, phoneIdPrefix + log);
+ synchronized (mLocalLog) {
+ mLocalLog.log(phoneIdPrefix + log);
+ }
+ }
+
+ private void logInfoLineLocked(int phoneId, String log) {
+ final String phoneIdPrefix = "[" + phoneId + "] ";
+ Log.i(TAG, phoneIdPrefix + log);
+ synchronized (mLocalLog) {
+ mLocalLog.log(phoneIdPrefix + log);
+ }
+ }
+}
diff --git a/src/java/com/android/ims/ImsManager.java b/src/java/com/android/ims/ImsManager.java
index fcf40273..3d1804c0 100644
--- a/src/java/com/android/ims/ImsManager.java
+++ b/src/java/com/android/ims/ImsManager.java
@@ -23,10 +23,12 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.os.SystemProperties;
import android.provider.Settings;
import android.telecom.TelecomManager;
@@ -45,6 +47,8 @@ import android.telephony.ims.RegistrationManager;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IImsConfigCallback;
+import android.telephony.ims.aidl.IImsMmTelFeature;
+import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.aidl.IImsSmsListener;
import android.telephony.ims.feature.CapabilityChangeRequest;
@@ -53,8 +57,10 @@ import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.stub.ImsCallSessionImplBase;
import android.telephony.ims.stub.ImsConfigImplBase;
import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.Log;
import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.util.HandlerExecutor;
@@ -62,23 +68,23 @@ import com.android.telephony.Rlog;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.Set;
+import java.util.ArrayList;
import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
- * Provides APIs for IMS services, such as initiating IMS calls, and provides access to
- * the operator's IMS network. This class is the starting point for any IMS actions.
- * You can acquire an instance of it with {@link #getInstance getInstance()}.</p>
+ * Provides APIs for MMTEL IMS services, such as initiating IMS calls, and provides access to
+ * the operator's IMS network. This class is the starting point for any IMS MMTEL actions.
+ * You can acquire an instance of it with {@link #getInstance getInstance()}.
+ * {Use {@link RcsFeatureManager} for RCS services}.
* For internal use ONLY! Use {@link ImsMmTelManager} instead.
* @hide
*/
-public class ImsManager implements IFeatureConnector {
+public class ImsManager implements FeatureUpdates {
/*
* Debug flag to override configuration flag
@@ -198,6 +204,7 @@ public class ImsManager implements IFeatureConnector {
private static final boolean DBG = true;
private static final int RESPONSE_WAIT_TIME_MS = 3000;
+ private static final int GET_INSTANCE_TIMEOUT_MS = 500;
@VisibleForTesting
public interface ExecutorFactory {
@@ -228,7 +235,8 @@ public class ImsManager implements IFeatureConnector {
@VisibleForTesting
public interface MmTelFeatureConnectionFactory {
- MmTelFeatureConnection create(Context context, int phoneId);
+ MmTelFeatureConnection create(Context context, int phoneId, IImsMmTelFeature feature,
+ IImsConfig c, IImsRegistration r);
}
@VisibleForTesting
@@ -290,12 +298,9 @@ public class ImsManager implements IFeatureConnector {
private final ExecutorFactory mExecutorFactory;
// Replaced With mock for testing
private MmTelFeatureConnectionFactory mMmTelFeatureConnectionFactory =
- MmTelFeatureConnection::create;
+ MmTelFeatureConnection::new;
private SubscriptionManagerProxy mSubscriptionManagerProxy;
- private static HashMap<Integer, ImsManager> sImsManagerInstances =
- new HashMap<Integer, ImsManager>();
-
private Context mContext;
private CarrierConfigManager mConfigManager;
private int mPhoneId;
@@ -304,21 +309,17 @@ public class ImsManager implements IFeatureConnector {
private ImsConfigListener mImsConfigListener;
- //TODO: Move these caches into the MmTelFeature Connection and restrict their lifetimes to the
- // lifetime of the MmTelFeature.
- // Ut interface for the supplementary service configuration
- private ImsUt mUt = null;
- // ECBM interface
- private ImsEcbm mEcbm = null;
- private ImsMultiEndpoint mMultiEndpoint = null;
-
- private Set<FeatureConnection.IFeatureUpdate> mStatusCallbacks = new CopyOnWriteArraySet<>();
-
public static final String TRUE = "true";
public static final String FALSE = "false";
/**
- * Gets a manager instance.
+ * Gets a manager instance and blocks for a limited period of time, connecting to the
+ * corresponding ImsService MmTelFeature if it exists.
+ * <p>
+ * If the ImsService is unavailable or becomes unavailable, the associated methods will fail and
+ * a new ImsManager will need to be requested. Instead, a {@link FeatureConnector} can be
+ * requested using {@link #getConnector}, which will notify the caller when a new ImsManager is
+ * available.
*
* @param context application context for creating the manager object
* @param phoneId the phone ID for the IMS Service
@@ -326,21 +327,61 @@ public class ImsManager implements IFeatureConnector {
*/
@UnsupportedAppUsage
public static ImsManager getInstance(Context context, int phoneId) {
- synchronized (sImsManagerInstances) {
- if (sImsManagerInstances.containsKey(phoneId)) {
- ImsManager m = sImsManagerInstances.get(phoneId);
- // May be null for some tests
- if (m != null) {
- m.connectIfServiceIsAvailable();
- }
- return m;
- }
-
- ImsManager mgr = new ImsManager(context, phoneId);
- sImsManagerInstances.put(phoneId, mgr);
-
- return mgr;
+ CountDownLatch latch = new CountDownLatch(1);
+ final ImsManager[] mgr = new ImsManager[1];
+ // Include all states in the ready filter, as some APIs are available when the MmMTelFeature
+ // is not in the ready state yet.
+ ArrayList<Integer> readyFilter = new ArrayList<>();
+ readyFilter.add(ImsFeature.STATE_UNAVAILABLE);
+ readyFilter.add(ImsFeature.STATE_INITIALIZING);
+ readyFilter.add(ImsFeature.STATE_READY);
+ FeatureConnector<ImsManager> fc = new FeatureConnector<>(context, phoneId, ImsManager::new,
+ TAG, readyFilter, new FeatureConnector.Listener<ImsManager>() {
+ @Override
+ public void connectionReady(ImsManager manager) {
+ mgr[0] = manager;
+ latch.countDown();
+ }
+ @Override
+ public void connectionUnavailable(int reason) {
+ latch.countDown();
+ }
+ }, Runnable::run /* callback on binder thread (or current thread if local) */);
+ fc.connectForOneShot();
+ try {
+ latch.await(GET_INSTANCE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Log.w(TAG, "getInstance - exception: " + e);
+ }
+ // Create shell ImsManager in the case that the MmTelFeature is not available to be
+ // compatible with previous users of this API.
+ if (mgr[0] == null) {
+ mgr[0] = new ImsManager(context, phoneId);
+ mgr[0].associate(null, null, null);
}
+ return mgr[0];
+ }
+
+ /**
+ * Retrieve an FeatureConnector for ImsManager, which allows a Listener to listen for when
+ * the ImsManager becomes available or unavailable due to the ImsService MmTelFeature moving to
+ * the READY state or destroyed on a specific phone modem index.
+ *
+ * @param context The Context that will be used to connect the ImsManager.
+ * @param phoneId The modem phone ID that the ImsManager will be created for.
+ * @param logPrefix The log prefix used for debugging purposes.
+ * @param listener The Listener that will deliver ImsManager updates as it becomes available.
+ * @param executor The Executor that the Listener will be called on.
+ * @return
+ */
+ public static FeatureConnector<ImsManager> getConnector(Context context,
+ int phoneId, String logPrefix, FeatureConnector.Listener<ImsManager> listener,
+ Executor executor) {
+ // Only listen for the READY state from the MmTelFeature here.
+ ArrayList<Integer> readyFilter = new ArrayList<>();
+ readyFilter.add(ImsFeature.STATE_READY);
+ return new FeatureConnector<>(context, phoneId, ImsManager::new, logPrefix, readyFilter,
+ listener, executor);
}
public static boolean isImsSupportedOnDevice(Context context) {
@@ -1506,11 +1547,7 @@ public class ImsManager implements IFeatureConnector {
// Count as "provisioned" if we do not require provisioning.
boolean isProvisioned = true;
if (requiresProvisioning) {
- ITelephony telephony = ITelephony.Stub.asInterface(
- TelephonyFrameworkInitializer
- .getTelephonyServiceManager()
- .getTelephonyServiceRegisterer()
- .get());
+ ITelephony telephony = getITelephony();
// Only track UT over LTE, since we do not differentiate between UT over LTE and IWLAN
// currently.
try {
@@ -1550,7 +1587,6 @@ public class ImsManager implements IFeatureConnector {
mConfigManager = (CarrierConfigManager) context.getSystemService(
Context.CARRIER_CONFIG_SERVICE);
mExecutorFactory = new ImsExecutorFactory();
- createImsService();
}
/**
@@ -1567,16 +1603,12 @@ public class ImsManager implements IFeatureConnector {
Context.CARRIER_CONFIG_SERVICE);
// Do not multithread tests
mExecutorFactory = Runnable::run;
- createImsService();
}
/*
- * Returns a flag indicating whether the IMS service is available. If it is not available or
- * busy, it will try to connect before reporting failure.
+ * Returns a flag indicating whether the IMS service is available.
*/
public boolean isServiceAvailable() {
- connectIfServiceIsAvailable();
- // mImsServiceProxy will always create an ImsServiceProxy.
return mMmTelFeatureConnection.isBinderAlive();
}
@@ -1584,50 +1616,13 @@ public class ImsManager implements IFeatureConnector {
* Returns a flag indicating whether the IMS service is ready to send requests to lower layers.
*/
public boolean isServiceReady() {
- connectIfServiceIsAvailable();
return mMmTelFeatureConnection.isBinderReady();
}
- /**
- * If the service is available, try to reconnect.
- */
- public void connectIfServiceIsAvailable() {
- if (mMmTelFeatureConnection == null || !mMmTelFeatureConnection.isBinderAlive()) {
- createImsService();
- }
- }
-
public void setConfigListener(ImsConfigListener listener) {
mImsConfigListener = listener;
}
-
- /**
- * Adds a callback for status changed events if the binder is already available. If it is not,
- * this method will throw an ImsException.
- */
- @Override
- @VisibleForTesting
- public void addNotifyStatusChangedCallbackIfAvailable(FeatureConnection.IFeatureUpdate c)
- throws android.telephony.ims.ImsException {
- if (!mMmTelFeatureConnection.isBinderAlive()) {
- throw new android.telephony.ims.ImsException("Can not connect to ImsService",
- android.telephony.ims.ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
- }
- if (c != null) {
- mStatusCallbacks.add(c);
- }
- }
-
- @Override
- public void removeNotifyStatusChangedCallback(FeatureConnection.IFeatureUpdate c) {
- if (c != null) {
- mStatusCallbacks.remove(c);
- } else {
- logw("removeNotifyStatusChangedCallback: callback is null!");
- }
- }
-
/**
* Opens the IMS service for making calls and/or receiving generic IMS calls.
* The caller may make subsequent calls through {@link #makeCall}.
@@ -2241,11 +2236,24 @@ public class ImsManager implements IFeatureConnector {
}
}
- @Override
public int getImsServiceState() throws ImsException {
return mMmTelFeatureConnection.getFeatureState();
}
+ @Override
+ public void updateFeatureState(int state) {
+ if (mMmTelFeatureConnection != null) {
+ mMmTelFeatureConnection.updateFeatureState(state);
+ }
+ }
+
+ @Override
+ public void updateFeatureCapabilities(long capabilities) {
+ if (mMmTelFeatureConnection != null) {
+ mMmTelFeatureConnection.updateFeatureCapabilities(capabilities);
+ }
+ }
+
public void getImsServiceState(Consumer<Integer> result) {
mExecutorFactory.executeRunnable(() -> {
try {
@@ -2315,33 +2323,75 @@ public class ImsManager implements IFeatureConnector {
ImsReasonInfo.CODE_LOCAL_IMS_NOT_SUPPORTED_ON_DEVICE);
}
if (mMmTelFeatureConnection == null || !mMmTelFeatureConnection.isBinderAlive()) {
- createImsService();
+ throw new ImsException("Service is unavailable",
+ ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+ }
+ }
- if (mMmTelFeatureConnection == null) {
- throw new ImsException("Service is unavailable",
- ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
+ @Override
+ public void registerFeatureCallback(int slotId, IImsServiceFeatureCallback cb,
+ boolean oneShot) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ telephony.registerMmTelFeatureCallback(slotId, cb, oneShot);
+ } else {
+ cb.imsFeatureRemoved(FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE);
}
+ } catch (ServiceSpecificException e) {
+ try {
+ switch (e.errorCode) {
+ case android.telephony.ims.ImsException.CODE_ERROR_UNSUPPORTED_OPERATION:
+ cb.imsFeatureRemoved(FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+ break;
+ default: {
+ cb.imsFeatureRemoved(
+ FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE);
+ }
+ }
+ } catch (RemoteException ignore) {} // Already dead anyway if this happens.
+ } catch (RemoteException e) {
+ try {
+ cb.imsFeatureRemoved(FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE);
+ } catch (RemoteException ignore) {} // Already dead if this happens.
}
}
- /**
- * Creates a connection to the ImsService associated with this slot.
- */
- private void createImsService() {
- mMmTelFeatureConnection = mMmTelFeatureConnectionFactory.create(mContext, mPhoneId);
-
- // Forwarding interface to tell mStatusCallbacks that the Proxy is unavailable.
- mMmTelFeatureConnection.setStatusCallback(new FeatureConnection.IFeatureUpdate() {
- @Override
- public void notifyStateChanged() {
- mStatusCallbacks.forEach(FeatureConnection.IFeatureUpdate::notifyStateChanged);
+ @Override
+ public void unregisterFeatureCallback(IImsServiceFeatureCallback cb) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ telephony.unregisterImsFeatureCallback(cb);
}
+ } catch (RemoteException e) {
+ // This means that telephony died, so do not worry about it.
+ loge("unregisterImsFeatureCallback (MMTEL), RemoteException: " + e.getMessage());
+ }
+ }
- @Override
- public void notifyUnavailable() {
- mStatusCallbacks.forEach(FeatureConnection.IFeatureUpdate::notifyUnavailable);
- }
- });
+ @Override
+ public void associate(IBinder binder, IImsConfig c, IImsRegistration r) {
+ mMmTelFeatureConnection = mMmTelFeatureConnectionFactory.create(
+ mContext, mPhoneId, IImsMmTelFeature.Stub.asInterface(binder), c, r);
+ }
+
+ @Override
+ public void invalidate() {
+ // mMmTelFeatureConnection may be null in some cases where invalidate was called before
+ // associate.
+ if (mMmTelFeatureConnection != null) {
+ mMmTelFeatureConnection.closeConnection();
+ mMmTelFeatureConnection.setBinder(null);
+ }
+ }
+
+ private ITelephony getITelephony() {
+ return ITelephony.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyServiceRegisterer()
+ .get());
}
/**
diff --git a/src/java/com/android/ims/MmTelFeatureConnection.java b/src/java/com/android/ims/MmTelFeatureConnection.java
index 45ec984d..3329ce51 100644
--- a/src/java/com/android/ims/MmTelFeatureConnection.java
+++ b/src/java/com/android/ims/MmTelFeatureConnection.java
@@ -203,37 +203,11 @@ public class MmTelFeatureConnection extends FeatureConnection {
private final CapabilityCallbackManager mCapabilityCallbackManager;
private final ProvisioningCallbackManager mProvisioningCallbackManager;
- public static @NonNull MmTelFeatureConnection create(Context context , int slotId) {
- MmTelFeatureConnection serviceProxy = new MmTelFeatureConnection(context, slotId);
- if (!ImsManager.isImsSupportedOnDevice(context)) {
- // Return empty service proxy in the case that IMS is not supported.
- sImsSupportedOnDevice = false;
- return serviceProxy;
- }
-
- TelephonyManager tm = serviceProxy.getTelephonyManager();
- if (tm == null) {
- Rlog.w(TAG + " [" + slotId + "]", "create: TelephonyManager is null!");
- // Binder can be unset in this case because it will be torn down/recreated as part of
- // a retry mechanism until the serviceProxy binder is set successfully.
- return serviceProxy;
- }
-
- IImsMmTelFeature binder = tm.getImsMmTelFeatureAndListen(slotId,
- serviceProxy.getListener());
- if (binder != null) {
- serviceProxy.setBinder(binder.asBinder());
- // Trigger the cache to be updated for feature status.
- serviceProxy.getFeatureState();
- } else {
- Rlog.w(TAG + " [" + slotId + "]", "create: binder is null!");
- }
- return serviceProxy;
- }
-
- public MmTelFeatureConnection(Context context, int slotId) {
- super(context, slotId);
+ public MmTelFeatureConnection(Context context, int slotId, IImsMmTelFeature f,
+ IImsConfig c, IImsRegistration r) {
+ super(context, slotId, c, r);
+ setBinder((f != null) ? f.asBinder() : null);
mRegistrationCallbackManager = new ImsRegistrationCallbackAdapter(context, mLock);
mCapabilityCallbackManager = new CapabilityCallbackManager(context, mLock);
mProvisioningCallbackManager = new ProvisioningCallbackManager(context, mLock);
@@ -241,7 +215,6 @@ public class MmTelFeatureConnection extends FeatureConnection {
@Override
protected void onRemovedOrDied() {
- removeImsFeatureCallback();
synchronized (mLock) {
super.onRemovedOrDied();
mRegistrationCallbackManager.close();
@@ -250,74 +223,6 @@ public class MmTelFeatureConnection extends FeatureConnection {
}
}
- private void removeImsFeatureCallback() {
- TelephonyManager tm = getTelephonyManager();
- if (tm != null) {
- tm.unregisterImsFeatureCallback(mSlotId, ImsFeature.FEATURE_MMTEL, getListener());
- }
- }
-
- @Override
- protected void handleImsFeatureCreatedCallback(int slotId, int feature) {
- // The feature has been enabled. This happens when the feature is first created and
- // may happen when the feature is re-enabled.
- synchronized (mLock) {
- if(mSlotId != slotId) {
- return;
- }
- switch (feature) {
- case ImsFeature.FEATURE_MMTEL: {
- if (!mIsAvailable) {
- Log.i(TAG + " [" + mSlotId + "]", "MmTel enabled");
- mIsAvailable = true;
- }
- break;
- }
- case ImsFeature.FEATURE_EMERGENCY_MMTEL: {
- mSupportsEmergencyCalling = true;
- Log.i(TAG + " [" + mSlotId + "]", "Emergency calling enabled");
- break;
- }
- }
- }
- }
-
- @Override
- protected void handleImsFeatureRemovedCallback(int slotId, int feature) {
- synchronized (mLock) {
- if (mSlotId != slotId) {
- return;
- }
- switch (feature) {
- case ImsFeature.FEATURE_MMTEL: {
- Log.i(TAG + " [" + mSlotId + "]", "MmTel removed");
- onRemovedOrDied();
- break;
- }
- case ImsFeature.FEATURE_EMERGENCY_MMTEL: {
- mSupportsEmergencyCalling = false;
- Log.i(TAG + " [" + mSlotId + "]", "Emergency calling disabled");
- break;
- }
- }
- }
- }
-
- @Override
- protected void handleImsStatusChangedCallback(int slotId, int feature, int status) {
- synchronized (mLock) {
- Log.i(TAG + " [" + mSlotId + "]", "imsStatusChanged: slot: " + slotId + " feature: "
- + ImsFeature.FEATURE_LOG_MAP.get(feature) +
- " status: " + ImsFeature.STATE_LOG_MAP.get(status));
- if (mSlotId == slotId && feature == ImsFeature.FEATURE_MMTEL) {
- mFeatureStateCached = status;
- if (mStatusCallback != null) {
- mStatusCallback.notifyStateChanged();
- }
- }
- }
- }
-
public boolean isEmergencyMmTelAvailable() {
return mSupportsEmergencyCalling;
}
@@ -558,15 +463,12 @@ public class MmTelFeatureConnection extends FeatureConnection {
}
@Override
- protected IImsRegistration getRegistrationBinder() {
- TelephonyManager tm = getTelephonyManager();
- return tm != null ? tm.getImsRegistration(mSlotId, ImsFeature.FEATURE_MMTEL) : null;
- }
-
- @Override
- protected IImsConfig getConfigBinder() {
- TelephonyManager tm = getTelephonyManager();
- return tm != null ? tm.getImsConfig(mSlotId, ImsFeature.FEATURE_MMTEL) : null;
+ public void onFeatureCapabilitiesUpdated(long capabilities)
+ {
+ synchronized (mLock) {
+ mSupportsEmergencyCalling =
+ ((capabilities | ImsService.CAPABILITY_EMERGENCY_OVER_MMTEL) > 0);
+ }
}
private IImsMmTelFeature getServiceInterface(IBinder b) {
diff --git a/src/java/com/android/ims/RcsFeatureConnection.java b/src/java/com/android/ims/RcsFeatureConnection.java
index 62685436..b13b4027 100644
--- a/src/java/com/android/ims/RcsFeatureConnection.java
+++ b/src/java/com/android/ims/RcsFeatureConnection.java
@@ -111,45 +111,15 @@ public class RcsFeatureConnection extends FeatureConnection {
}
}
- public static @NonNull RcsFeatureConnection create(Context context , int slotId,
- IFeatureUpdate callback) {
-
- RcsFeatureConnection serviceProxy = new RcsFeatureConnection(context, slotId, callback);
-
- if (!ImsManager.isImsSupportedOnDevice(context)) {
- // Return empty service proxy in the case that IMS is not supported.
- sImsSupportedOnDevice = false;
- Rlog.w(TAG, "create: IMS is not supported");
- return serviceProxy;
- }
-
- TelephonyManager tm =
- (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- if (tm == null) {
- Rlog.w(TAG, "create: TelephonyManager is null");
- return serviceProxy;
- }
-
- IImsRcsFeature binder = tm.getImsRcsFeatureAndListen(slotId, serviceProxy.getListener());
- if (binder != null) {
- Rlog.d(TAG, "create: set binder");
- serviceProxy.setBinder(binder.asBinder());
- // Trigger the cache to be updated for feature status.
- serviceProxy.getFeatureState();
- } else {
- Rlog.i(TAG, "create: binder is null! Slot Id: " + slotId);
- }
- return serviceProxy;
- }
-
@VisibleForTesting
public AvailabilityCallbackManager mAvailabilityCallbackManager;
@VisibleForTesting
public RegistrationCallbackManager mRegistrationCallbackManager;
- private RcsFeatureConnection(Context context, int slotId, IFeatureUpdate callback) {
- super(context, slotId);
- setStatusCallback(callback);
+ public RcsFeatureConnection(Context context, int slotId, IImsRcsFeature feature, IImsConfig c,
+ IImsRegistration r) {
+ super(context, slotId, c, r);
+ setBinder(feature.asBinder());
mAvailabilityCallbackManager = new AvailabilityCallbackManager(mContext);
mRegistrationCallbackManager = new RegistrationCallbackManager(mContext);
}
@@ -162,68 +132,12 @@ public class RcsFeatureConnection extends FeatureConnection {
@Override
protected void onRemovedOrDied() {
- removeImsFeatureCallback();
super.onRemovedOrDied();
synchronized (mLock) {
close();
}
}
- private void removeImsFeatureCallback() {
- TelephonyManager tm = getTelephonyManager();
- if (tm != null) {
- tm.unregisterImsFeatureCallback(mSlotId, ImsFeature.FEATURE_RCS, getListener());
- }
- }
-
- @Override
- @VisibleForTesting
- public void handleImsFeatureCreatedCallback(int slotId, int feature) {
- logi("IMS feature created: slotId= " + slotId + ", feature=" + feature);
- if (!isUpdateForThisFeatureAndSlot(slotId, feature)) {
- return;
- }
- synchronized(mLock) {
- if (!mIsAvailable) {
- logi("RCS enabled on slotId: " + slotId);
- mIsAvailable = true;
- }
- }
- }
-
- @Override
- @VisibleForTesting
- public void handleImsFeatureRemovedCallback(int slotId, int feature) {
- logi("IMS feature removed: slotId= " + slotId + ", feature=" + feature);
- if (!isUpdateForThisFeatureAndSlot(slotId, feature)) {
- return;
- }
- synchronized(mLock) {
- logi("Rcs UCE removed on slotId: " + slotId);
- onRemovedOrDied();
- }
- }
-
- @Override
- @VisibleForTesting
- public void handleImsStatusChangedCallback(int slotId, int feature, int status) {
- logi("IMS status changed: slotId=" + slotId + ", feature=" + feature + ", status="
- + status);
- if (!isUpdateForThisFeatureAndSlot(slotId, feature)) {
- return;
- }
- synchronized(mLock) {
- mFeatureStateCached = status;
- }
- }
-
- private boolean isUpdateForThisFeatureAndSlot(int slotId, int feature) {
- if (mSlotId == slotId && feature == ImsFeature.FEATURE_RCS) {
- return true;
- }
- return false;
- }
-
public void setRcsFeatureListener(IRcsFeatureListener listener) throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
@@ -331,15 +245,9 @@ public class RcsFeatureConnection extends FeatureConnection {
}
@Override
- protected IImsRegistration getRegistrationBinder() {
- TelephonyManager tm = getTelephonyManager();
- return tm != null ? tm.getImsRegistration(mSlotId, ImsFeature.FEATURE_RCS) : null;
- }
-
- @Override
- protected IImsConfig getConfigBinder() {
- TelephonyManager tm = getTelephonyManager();
- return tm != null ? tm.getImsConfig(mSlotId, ImsFeature.FEATURE_RCS) : null;
+ public void onFeatureCapabilitiesUpdated(long capabilities)
+ {
+ // doesn't do anything for RCS yet.
}
@VisibleForTesting
diff --git a/src/java/com/android/ims/RcsFeatureManager.java b/src/java/com/android/ims/RcsFeatureManager.java
index fd9e2d9c..fe758e85 100644
--- a/src/java/com/android/ims/RcsFeatureManager.java
+++ b/src/java/com/android/ims/RcsFeatureManager.java
@@ -18,16 +18,25 @@ package com.android.ims;
import android.content.Context;
import android.net.Uri;
+import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
+import android.telephony.ims.ImsException;
import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.aidl.IImsCapabilityCallback;
+import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IImsRcsController;
+import android.telephony.ims.aidl.IImsRcsFeature;
+import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.aidl.IRcsFeatureListener;
import android.telephony.ims.feature.CapabilityChangeRequest;
+import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.RcsFeature;
import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities;
import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -36,14 +45,16 @@ import android.telephony.ims.stub.RcsPresenceExchangeImplBase;
import android.telephony.ims.stub.RcsSipOptionsImplBase;
import android.util.Log;
-import com.android.ims.FeatureConnection.IFeatureUpdate;
+import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.telephony.Rlog;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
@@ -53,7 +64,7 @@ import java.util.function.Consumer;
* - Registering/Unregistering availability/registration callbacks.
* - Querying Registration and Capability information.
*/
-public class RcsFeatureManager implements IFeatureConnector {
+public class RcsFeatureManager implements FeatureUpdates {
private static final String TAG = "RcsFeatureManager";
private static boolean DBG = true;
@@ -145,36 +156,26 @@ public class RcsFeatureManager implements IFeatureConnector {
private final int mSlotId;
private final Context mContext;
- @VisibleForTesting
- public final Set<IFeatureUpdate> mStatusCallbacks = new CopyOnWriteArraySet<>();
private final Set<RcsFeatureCallbacks> mRcsFeatureCallbacks = new CopyOnWriteArraySet<>();
@VisibleForTesting
public RcsFeatureConnection mRcsFeatureConnection;
- public RcsFeatureManager(Context context, int slotId) {
- mContext = context;
- mSlotId = slotId;
-
- createImsService();
+ public static FeatureConnector<RcsFeatureManager> getConnector(Context context, int slotId,
+ FeatureConnector.Listener<RcsFeatureManager> listener, Executor executor,
+ String logPrefix) {
+ ArrayList<Integer> filter = new ArrayList<>();
+ filter.add(ImsFeature.STATE_READY);
+ return new FeatureConnector<>(context, slotId, RcsFeatureManager::new, logPrefix, filter,
+ listener, executor);
}
- // Binds the IMS service to the RcsFeature instance.
- private void createImsService() {
- mRcsFeatureConnection = RcsFeatureConnection.create(mContext, mSlotId,
- new IFeatureUpdate() {
- @Override
- public void notifyStateChanged() {
- mStatusCallbacks.forEach(
- FeatureConnection.IFeatureUpdate::notifyStateChanged);
- }
- @Override
- public void notifyUnavailable() {
- logi("RcsFeature is unavailable");
- mStatusCallbacks.forEach(
- FeatureConnection.IFeatureUpdate::notifyUnavailable);
- }
- });
+ /**
+ * Use {@link #getConnector} to get an instance of this class.
+ */
+ private RcsFeatureManager(Context context, int slotId) {
+ mContext = context;
+ mSlotId = slotId;
}
/**
@@ -201,7 +202,6 @@ public class RcsFeatureManager implements IFeatureConnector {
} catch (RemoteException e){
// Connection may not be available at this point.
}
- mStatusCallbacks.clear();
mRcsFeatureConnection.close();
mRcsFeatureCallbacks.clear();
}
@@ -397,29 +397,6 @@ public class RcsFeatureManager implements IFeatureConnector {
}
/**
- * Adds a callback for status changed events if the binder is already available. If it is not,
- * this method will throw an ImsException.
- */
- @Override
- public void addNotifyStatusChangedCallbackIfAvailable(FeatureConnection.IFeatureUpdate c)
- throws android.telephony.ims.ImsException {
- if (!mRcsFeatureConnection.isBinderAlive()) {
- throw new android.telephony.ims.ImsException("Can not connect to service.",
- android.telephony.ims.ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
- }
- if (c != null) {
- mStatusCallbacks.add(c);
- }
- }
-
- @Override
- public void removeNotifyStatusChangedCallback(FeatureConnection.IFeatureUpdate c) {
- if (c != null) {
- mStatusCallbacks.remove(c);
- }
- }
-
- /**
* Add UCE capabilities with given type.
* @param capability the specific RCS UCE capability wants to enable
*/
@@ -507,8 +484,77 @@ public class RcsFeatureManager implements IFeatureConnector {
}
@Override
- public int getImsServiceState() throws ImsException {
- return mRcsFeatureConnection.getFeatureState();
+ public void registerFeatureCallback(int slotId, IImsServiceFeatureCallback cb,
+ boolean oneShot) {
+ IImsRcsController controller = getIImsRcsController();
+ try {
+ if (controller == null) {
+ Log.e(TAG, "registerRcsFeatureListener: IImsRcsController is null");
+ cb.imsFeatureRemoved(FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE);
+ }
+ controller.registerRcsFeatureCallback(slotId, cb, oneShot);
+ } catch (ServiceSpecificException e) {
+ try {
+ switch (e.errorCode) {
+ case ImsException.CODE_ERROR_UNSUPPORTED_OPERATION:
+ cb.imsFeatureRemoved(FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+ break;
+ default: {
+ cb.imsFeatureRemoved(
+ FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE);
+ }
+ }
+ } catch (RemoteException ignore) {} // Already dead anyway if this happens.
+ } catch (RemoteException e) {
+ try {
+ cb.imsFeatureRemoved(FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE);
+ } catch (RemoteException ignore) {} // Already dead if this happens.
+ }
+ }
+
+ @Override
+ public void unregisterFeatureCallback(IImsServiceFeatureCallback cb) {
+ try {
+ IImsRcsController imsRcsController = getIImsRcsController();
+ if (imsRcsController != null) {
+ imsRcsController.unregisterImsFeatureCallback(cb);
+ }
+ } catch (RemoteException e) {
+ // This means that telephony died, so do not worry about it.
+ Rlog.e(TAG, "unregisterImsFeatureCallback (RCS), RemoteException: "
+ + e.getMessage());
+ }
+ }
+
+ private IImsRcsController getIImsRcsController() {
+ IBinder binder = TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyImsServiceRegisterer()
+ .get();
+ IImsRcsController c = IImsRcsController.Stub.asInterface(binder);
+ return c;
+ }
+
+ @Override
+ public void associate(IBinder b, IImsConfig c, IImsRegistration r) {
+ IImsRcsFeature f = IImsRcsFeature.Stub.asInterface(b);
+ mRcsFeatureConnection = new RcsFeatureConnection(mContext, mSlotId, f, c, r);
+ }
+
+ @Override
+ public void invalidate() {
+ mRcsFeatureConnection.close();
+ mRcsFeatureConnection.setBinder(null);
+ }
+
+ @Override
+ public void updateFeatureState(int state) {
+ mRcsFeatureConnection.updateFeatureState(state);
+ }
+
+ @Override
+ public void updateFeatureCapabilities(long capabilities) {
+ mRcsFeatureConnection.updateFeatureCapabilities(capabilities);
}
/**
diff --git a/tests/src/com/android/ims/FeatureConnectionTest.java b/tests/src/com/android/ims/FeatureConnectionTest.java
index 8dba5e6f..16d659c4 100644
--- a/tests/src/com/android/ims/FeatureConnectionTest.java
+++ b/tests/src/com/android/ims/FeatureConnectionTest.java
@@ -19,8 +19,6 @@ package com.android.ims;
import junit.framework.AssertionFailedError;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
@@ -36,35 +34,26 @@ import android.telephony.ims.stub.ImsRegistrationImplBase;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.ims.internal.IImsServiceFeatureCallback;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import java.util.concurrent.Executor;
@RunWith(AndroidJUnit4.class)
public class FeatureConnectionTest extends ImsTestBase {
- private Executor mSimpleExecutor = new Executor() {
- @Override
- public void execute(Runnable r) {
- r.run();
- }
- };
-
private class TestFeatureConnection extends FeatureConnection {
private Integer mFeatureState = ImsFeature.STATE_READY;
public boolean isFeatureCreatedCalled = false;
public boolean isFeatureRemovedCalled = false;
public int mNewStatus = ImsFeature.STATE_UNAVAILABLE;
+ public long mCapabilities;
TestFeatureConnection(Context context, int slotId) {
- super(context, slotId);
+ super(context, slotId, mConfigBinder, mRegistrationBinder);
if (!ImsManager.isImsSupportedOnDevice(context)) {
sImsSupportedOnDevice = false;
}
@@ -76,33 +65,13 @@ public class FeatureConnectionTest extends ImsTestBase {
}
@Override
- protected void handleImsFeatureCreatedCallback(int slotId, int feature) {
- isFeatureCreatedCalled = true;
- }
-
- @Override
- protected void handleImsFeatureRemovedCallback(int slotId, int feature) {
- isFeatureRemovedCalled = true;
- }
-
- @Override
- protected void handleImsStatusChangedCallback(int slotId, int feature, int status) {
- mNewStatus = status;
- }
-
- @Override
protected Integer retrieveFeatureState() {
return mFeatureState;
}
@Override
- protected IImsRegistration getRegistrationBinder() {
- return getTestRegistrationBinder();
- }
-
- @Override
- protected IImsConfig getConfigBinder() {
- return getTestConfigBinder();
+ protected void onFeatureCapabilitiesUpdated(long capabilities) {
+ mCapabilities = capabilities;
}
public void setFeatureState(int state) {
@@ -125,7 +94,6 @@ public class FeatureConnectionTest extends ImsTestBase {
mContextFixture.addSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
mTestFeatureConnection = new TestFeatureConnection(mContext, PHONE_ID);
- mTestFeatureConnection.mExecutor = mSimpleExecutor;
mTestFeatureConnection.setBinder(mBinder);
}
@@ -193,40 +161,15 @@ public class FeatureConnectionTest extends ImsTestBase {
}
/**
- * Test callback is called when IMS feature created/removed/changed.
+ * Test registration tech callbacks.
*/
@Test
@SmallTest
- public void testListenerCallback() {
- IImsServiceFeatureCallback featureCallback = mTestFeatureConnection.getListener();
-
- try {
- featureCallback.imsFeatureCreated(anyInt(), anyInt());
- assertTrue(mTestFeatureConnection.isFeatureCreatedCalled);
- } catch (RemoteException e) {
- throw new AssertionFailedError("testListenerCallback(Created): " + e);
- }
-
- try {
- featureCallback.imsFeatureRemoved(anyInt(), anyInt());
- assertTrue(mTestFeatureConnection.isFeatureRemovedCalled);
- } catch (RemoteException e) {
- throw new AssertionFailedError("testListenerCallback(Removed): " + e);
- }
-
- try {
- featureCallback.imsStatusChanged(anyInt(), anyInt(), ImsFeature.STATE_READY);
- assertEquals(mTestFeatureConnection.mNewStatus, ImsFeature.STATE_READY);
- } catch (RemoteException e) {
- throw new AssertionFailedError("testListenerCallback(Changed): " + e);
- }
- }
-
- private IImsConfig getTestConfigBinder() {
- return mConfigBinder;
- }
+ public void testUpdateCapabilities() throws Exception {
+ long testCaps = 1;
+ assertEquals(0 /*base state*/, mTestFeatureConnection.mCapabilities);
+ mTestFeatureConnection.updateFeatureCapabilities(testCaps);
+ assertEquals(testCaps, mTestFeatureConnection.mCapabilities);
- private IImsRegistration getTestRegistrationBinder() {
- return mRegistrationBinder;
}
}
diff --git a/tests/src/com/android/ims/FeatureConnectorTest.java b/tests/src/com/android/ims/FeatureConnectorTest.java
index adbab645..8e8fac47 100644
--- a/tests/src/com/android/ims/FeatureConnectorTest.java
+++ b/tests/src/com/android/ims/FeatureConnectorTest.java
@@ -16,132 +16,376 @@
package com.android.ims;
-import junit.framework.AssertionFailedError;
-
-import static org.mockito.ArgumentMatchers.anyObject;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.Context;
import android.content.pm.PackageManager;
-import android.os.HandlerThread;
+import android.os.IBinder;
+import android.telephony.ims.ImsService;
+import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.feature.ImsFeature;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.ims.internal.IImsServiceFeatureCallback;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import java.util.concurrent.Executor;
+import java.util.ArrayList;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
public class FeatureConnectorTest extends ImsTestBase {
- private Executor mExecutor = new Executor() {
+ private static class TestFeatureConnection extends FeatureConnection {
+
+ public TestFeatureConnection(Context context, int slotId, IImsConfig c,
+ IImsRegistration r) {
+ super(context, slotId, c, r);
+ }
+
+ @Override
+ protected Integer retrieveFeatureState() {
+ return null;
+ }
+
+ @Override
+ protected void onFeatureCapabilitiesUpdated(long capabilities) {
+ }
+ }
+
+ private static class TestManager implements FeatureUpdates {
+
+ public IImsServiceFeatureCallback callback;
+ public TestFeatureConnection connection;
+ public boolean oneShot;
+ private Context mContext;
+ private int mPhoneId;
+
+
+ public TestManager(Context context, int phoneId) {
+ mContext = context;
+ mPhoneId = phoneId;
+ }
+
+ @Override
+ public void registerFeatureCallback(int slotId, IImsServiceFeatureCallback cb,
+ boolean os) {
+ callback = cb;
+ oneShot = os;
+ }
+
+ @Override
+ public void unregisterFeatureCallback(IImsServiceFeatureCallback cb) {
+ callback = null;
+ }
+
+ @Override
+ public void associate(IBinder f, IImsConfig c, IImsRegistration r) {
+ connection = new TestFeatureConnection(mContext, mPhoneId, c, r);
+ connection.setBinder(f);
+ }
+
+ @Override
+ public void invalidate() {
+ connection = null;
+ }
+
+ @Override
+ public void updateFeatureState(int state) {
+ assertNotNull(connection);
+ connection.updateFeatureState(state);
+ }
+
@Override
- public void execute(Runnable r) {
- r.run();
+ public void updateFeatureCapabilities(long capabilities) {
+ connection.updateFeatureCapabilities(capabilities);
}
- };
+ }
- private HandlerThread mHandlerThread;
- private FeatureConnector<ImsManager> mFeatureConnector;
- @Mock
- ImsManager mImsManager;
- @Mock
- FeatureConnector.Listener<ImsManager> mListener;
- @Mock
- FeatureConnector.RetryTimeout mRetryTimeout;
+ private FeatureConnector<TestManager> mFeatureConnector;
+ private TestManager mTestManager;
+ @Mock private FeatureConnector.Listener<TestManager> mListener;
+ @Mock private IBinder feature;
+ @Mock private IImsRegistration reg;
+ @Mock private IImsConfig config;
private static final int PHONE_ID = 1;
+ private static final long TEST_CAPS = ImsService.CAPABILITY_EMERGENCY_OVER_MMTEL;
@Before
public void setUp() throws Exception {
super.setUp();
-
- mHandlerThread = new HandlerThread("ConnectorHandlerThread");
- mHandlerThread.start();
-
- mFeatureConnector = new FeatureConnector<>(mContext, PHONE_ID,
- mListener, mExecutor, mHandlerThread.getLooper());
- mFeatureConnector.mListener = mListener;
+ setImsSupportedFeature(true);
+ mTestManager = new TestManager(mContext, PHONE_ID);
+ when(feature.isBinderAlive()).thenReturn(true);
}
@After
public void tearDown() throws Exception {
- mHandlerThread.quit();
super.tearDown();
}
@Test
@SmallTest
- public void testConnect() {
- // ImsManager is supported on device
- setImsSupportedFeature(true);
- when(mListener.getFeatureManager()).thenReturn(mImsManager);
-
+ public void testConnect() throws Exception {
+ createFeatureConnector();
mFeatureConnector.connect();
- waitForHandlerAction(mFeatureConnector, 1000);
+ assertNotNull("connect should trigger the callback registration", mTestManager.callback);
+ assertFalse("connect should not be one shot", mTestManager.oneShot);
+
+ // simulate callback from ImsResolver
+ mTestManager.callback.imsFeatureCreated(createContainer());
+ assertNotNull(mTestManager.connection);
+ assertEquals(TEST_CAPS, mTestManager.connection.getFeatureCapabilties());
+ verify(mListener, never()).connectionReady(any());
+ verify(mListener, never()).connectionUnavailable(anyInt());
+
+ // simulate callback from ImsResolver
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_READY);
+ verify(mListener).connectionReady(mTestManager);
+ verify(mListener, never()).connectionUnavailable(anyInt());
+ }
- // Verify that mListener will retrieve feature manager
- verify(mListener).getFeatureManager();
+ @Test
+ @SmallTest
+ public void testConnectOneShot() throws Exception {
+ createFeatureConnector();
+ mFeatureConnector.connectForOneShot();
+ assertNotNull("connect should trigger the callback registration", mTestManager.callback);
+ assertTrue("connectForOneShot should be one shot", mTestManager.oneShot);
+
+ // simulate callback from ImsResolver
+ mTestManager.callback.imsFeatureCreated(createContainer());
+ assertNotNull(mTestManager.connection);
+ assertEquals(TEST_CAPS, mTestManager.connection.getFeatureCapabilties());
+ verify(mListener, never()).connectionReady(any());
+ verify(mListener, never()).connectionUnavailable(anyInt());
- reset(mListener);
+ // simulate callback from ImsResolver
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_READY);
+ verify(mListener).connectionReady(mTestManager);
+ verify(mListener, never()).connectionUnavailable(anyInt());
+ }
- // ImsManager is NOT supported on device
+ @Test
+ @SmallTest
+ public void testConnectNotSupported() {
+ createFeatureConnector();
+ // set not supported
setImsSupportedFeature(false);
- when(mListener.getFeatureManager()).thenReturn(mImsManager);
mFeatureConnector.connect();
- waitForHandlerAction(mFeatureConnector, 1000);
+ assertNull("connect should not the callback registration if not supported",
+ mTestManager.callback);
+ verify(mListener).connectionUnavailable(
+ FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED);
+ }
+
+ @Test
+ @SmallTest
+ public void testConnectReadyNotReady() throws Exception {
+ createFeatureConnector();
+ mFeatureConnector.connect();
+ assertNotNull("connect should trigger the callback registration", mTestManager.callback);
+ // simulate callback from ImsResolver
+ mTestManager.callback.imsFeatureCreated(createContainer());
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_READY);
+ verify(mListener).connectionReady(mTestManager);
+ verify(mListener, never()).connectionUnavailable(anyInt());
- // Verify that mListener won't retrieve feature manager
- verify(mListener, never()).getFeatureManager();
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_UNAVAILABLE);
+ assertNotNull("When not ready, the callback should still be registered",
+ mTestManager.callback);
+ assertNotNull("Do not invalidate the connection if not ready", mTestManager.connection);
+ verify(mListener).connectionReady(mTestManager);
+ verify(mListener).connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_NOT_READY);
}
@Test
@SmallTest
- public void testDisconnect() {
- // Verify mListener will call connectionUnavailable if disconnect() is called.
- mFeatureConnector.disconnect();
- verify(mListener).connectionUnavailable();
+ public void testConnectReadyAndInitializing() throws Exception {
+ ArrayList<Integer> filterList = new ArrayList<>();
+ filterList.add(ImsFeature.STATE_READY);
+ filterList.add(ImsFeature.STATE_INITIALIZING);
+ createFeatureConnector(filterList);
+ mFeatureConnector.connect();
+ assertNotNull("connect should trigger the callback registration", mTestManager.callback);
+ // simulate callback from ImsResolver
+ mTestManager.callback.imsFeatureCreated(createContainer());
+ verify(mListener, never()).connectionReady(mTestManager);
+ verify(mListener, never()).connectionUnavailable(anyInt());
+
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_INITIALIZING);
+ assertNotNull("When not ready, the callback should still be registered",
+ mTestManager.callback);
+ assertNotNull("Do not invalidate the connection if not ready", mTestManager.connection);
+ verify(mListener).connectionReady(mTestManager);
+ verify(mListener, never()).connectionUnavailable(anyInt());
+
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_READY);
+ assertNotNull("When not ready, the callback should still be registered",
+ mTestManager.callback);
+ assertNotNull("Do not invalidate the connection if not ready", mTestManager.connection);
+ // Should not notify ready multiple times
+ verify(mListener).connectionReady(mTestManager);
+ verify(mListener, never()).connectionUnavailable(anyInt());
}
@Test
@SmallTest
- public void testNotifyStateChanged() {
- try {
- mFeatureConnector.mManager = mImsManager;
- when(mImsManager.getImsServiceState()).thenReturn(ImsFeature.STATE_READY);
- // Trigger status changed
- mFeatureConnector.mNotifyStatusChangedCallback.notifyStateChanged();
- // Verify NotifyReady is called
- verify(mListener).connectionReady(anyObject());
- } catch (ImsException e) {
- throw new AssertionFailedError("Exception in testNotifyStateChanged: " + e);
- }
+ public void testConnectReadyAndUnavailable() throws Exception {
+ ArrayList<Integer> filterList = new ArrayList<>();
+ filterList.add(ImsFeature.STATE_READY);
+ filterList.add(ImsFeature.STATE_INITIALIZING);
+ filterList.add(ImsFeature.STATE_UNAVAILABLE);
+ createFeatureConnector(filterList);
+ mFeatureConnector.connect();
+ assertNotNull("connect should trigger the callback registration", mTestManager.callback);
+ // simulate callback from ImsResolver
+ mTestManager.callback.imsFeatureCreated(createContainer());
+ verify(mListener).connectionReady(mTestManager);
+ verify(mListener, never()).connectionUnavailable(anyInt());
+
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_UNAVAILABLE);
+ assertNotNull("When not ready, the callback should still be registered",
+ mTestManager.callback);
+ assertNotNull("Do not invalidate the connection if not ready", mTestManager.connection);
+ verify(mListener).connectionReady(mTestManager);
+ verify(mListener, never()).connectionUnavailable(anyInt());
+
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_INITIALIZING);
+ assertNotNull("When not ready, the callback should still be registered",
+ mTestManager.callback);
+ assertNotNull("Do not invalidate the connection if not ready", mTestManager.connection);
+ // Should not notify ready multiple times
+ verify(mListener).connectionReady(mTestManager);
+ verify(mListener, never()).connectionUnavailable(anyInt());
+
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_READY);
+ // Should not notify ready multiple times
+ verify(mListener).connectionReady(mTestManager);
+ verify(mListener, never()).connectionUnavailable(anyInt());
+ }
+
+ @Test
+ @SmallTest
+ public void testConnectReadyRemovedReady() throws Exception {
+ createFeatureConnector();
+ mFeatureConnector.connect();
+ assertNotNull("connect should trigger the callback registration", mTestManager.callback);
+ // simulate callback from ImsResolver
+ mTestManager.callback.imsFeatureCreated(createContainer());
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_READY);
+ verify(mListener).connectionReady(mTestManager);
+ verify(mListener, never()).connectionUnavailable(anyInt());
+
+ mTestManager.callback.imsFeatureRemoved(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
+ assertNotNull("When not ready, the callback should still be registered",
+ mTestManager.callback);
+ verify(mListener).connectionReady(mTestManager);
+ verify(mListener).connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
+
+ mTestManager.callback.imsFeatureCreated(createContainer());
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_READY);
+ verify(mListener, times(2)).connectionReady(mTestManager);
+ verify(mListener).connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
+ }
+
+ @Test
+ @SmallTest
+ public void testConnectDisconnect() throws Exception {
+ createFeatureConnector();
+ mFeatureConnector.connect();
+ assertNotNull("connect should trigger the callback registration", mTestManager.callback);
+ // simulate callback from ImsResolver
+ mTestManager.callback.imsFeatureCreated(createContainer());
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_READY);
+ IImsServiceFeatureCallback oldCb = mTestManager.callback;
+ TestFeatureConnection testFc = mTestManager.connection;
+
+ mFeatureConnector.disconnect();
+ assertNull(mTestManager.callback);
+ assertNull(mTestManager.connection);
+ verify(mListener).connectionReady(mTestManager);
+ verify(mListener).connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
+
+ // make sure status/caps updates do not trigger more events after disconnect
+ oldCb.imsStatusChanged(ImsFeature.STATE_READY);
+ oldCb.imsStatusChanged(ImsFeature.STATE_UNAVAILABLE);
+ oldCb.updateCapabilities(0);
+ assertEquals(TEST_CAPS, testFc.getFeatureCapabilties());
+ verify(mListener).connectionReady(mTestManager);
+ verify(mListener).connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
}
@Test
@SmallTest
- public void testRetryGetImsService() {
- mFeatureConnector.mManager = mImsManager;
- mFeatureConnector.mRetryTimeout = mRetryTimeout;
+ public void testConnectDisconnectConnect() throws Exception {
+ createFeatureConnector();
+ mFeatureConnector.connect();
+ assertNotNull("connect should trigger the callback registration", mTestManager.callback);
+ // simulate callback from ImsResolver
+ mTestManager.callback.imsFeatureCreated(createContainer());
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_READY);
- when(mRetryTimeout.get()).thenReturn(1);
- when(mListener.getFeatureManager()).thenReturn(mImsManager);
+ mFeatureConnector.disconnect();
+ assertNull(mTestManager.callback);
+ assertNull(mTestManager.connection);
+ verify(mListener).connectionReady(mTestManager);
+ verify(mListener).connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
+
+ mFeatureConnector.connect();
+ assertNotNull(mTestManager.callback);
+ // simulate callback from ImsResolver
+ mTestManager.callback.imsFeatureCreated(createContainer());
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_READY);
+ assertNotNull(mTestManager.connection);
+ verify(mListener, times(2)).connectionReady(mTestManager);
+ verify(mListener).connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
+ }
- mFeatureConnector.retryGetImsService();
- waitForHandlerAction(mFeatureConnector, 2000);
+ @Test
+ @SmallTest
+ public void testUpdateCapabilities() throws Exception {
+ createFeatureConnector();
+ mFeatureConnector.connect();
+ assertNotNull("connect should trigger the callback registration", mTestManager.callback);
+ // simulate callback from ImsResolver
+ mTestManager.callback.imsFeatureCreated(createContainer());
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_READY);
+ assertEquals(TEST_CAPS, mTestManager.connection.getFeatureCapabilties());
+ mTestManager.callback.updateCapabilities(0);
+ assertEquals(0, mTestManager.connection.getFeatureCapabilties());
+ }
- // Verify removeNotifyStatusChangedCallback will be called if ImsManager is not null.
- verify(mImsManager).removeNotifyStatusChangedCallback(anyObject());
+ @Test
+ @SmallTest
+ public void testUpdateStatus() throws Exception {
+ createFeatureConnector();
+ mFeatureConnector.connect();
+ assertNotNull("connect should trigger the callback registration", mTestManager.callback);
+ // simulate callback from ImsResolver
+ mTestManager.callback.imsFeatureCreated(createContainer());
+ mTestManager.callback.imsStatusChanged(ImsFeature.STATE_READY);
+ assertEquals(ImsFeature.STATE_READY, mTestManager.connection.getFeatureState());
}
private void setImsSupportedFeature(boolean isSupported) {
@@ -151,4 +395,21 @@ public class FeatureConnectorTest extends ImsTestBase {
mContextFixture.removeSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
}
}
+
+ private ImsFeatureContainer createContainer() {
+ ImsFeatureContainer c = new ImsFeatureContainer(feature, config, reg, TEST_CAPS);
+ c.setState(ImsFeature.STATE_UNAVAILABLE);
+ return c;
+ }
+
+ private void createFeatureConnector() {
+ ArrayList<Integer> filter = new ArrayList<>();
+ filter.add(ImsFeature.STATE_READY);
+ createFeatureConnector(filter);
+ }
+
+ private void createFeatureConnector(List<Integer> featureReadyFilter) {
+ mFeatureConnector = new FeatureConnector<>(mContext, PHONE_ID,
+ (c, p) -> mTestManager, "Test", featureReadyFilter, mListener, Runnable::run);
+ }
}
diff --git a/tests/src/com/android/ims/ImsFeatureBinderRepositoryTest.java b/tests/src/com/android/ims/ImsFeatureBinderRepositoryTest.java
new file mode 100644
index 00000000..055fc111
--- /dev/null
+++ b/tests/src/com/android/ims/ImsFeatureBinderRepositoryTest.java
@@ -0,0 +1,361 @@
+/*
+ * 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.ims;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.IBinder;
+import android.telephony.ims.ImsService;
+import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IImsRegistration;
+import android.telephony.ims.feature.ImsFeature;
+
+import androidx.test.filters.SmallTest;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.ims.internal.IImsServiceFeatureCallback;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+@RunWith(AndroidJUnit4.class)
+public class ImsFeatureBinderRepositoryTest extends ImsTestBase {
+
+ private static final int TEST_PHONE_ID_1 = 1;
+ private static final int TEST_PHONE_ID_2 = 2;
+ private static final long TEST_SERVICE_CAPS = ImsService.CAPABILITY_EMERGENCY_OVER_MMTEL;
+
+ @Mock IBinder mMockMmTelFeatureA;
+ @Mock IBinder mMockMmTelFeatureB;
+ @Mock IBinder mMockRcsFeatureA;
+ @Mock IImsConfig mMockImsConfig;
+ @Mock IImsRegistration mMockImsRegistration;
+
+ @Mock IImsServiceFeatureCallback mConnectionCallback;
+ @Mock IBinder mConnectionCallbackBinder;
+ @Mock IImsServiceFeatureCallback mConnectionCallback2;
+ @Mock IBinder mConnectionCallback2Binder;
+
+ private ImsFeatureBinderRepository mRepository;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mRepository = new ImsFeatureBinderRepository();
+ when(mConnectionCallbackBinder.isBinderAlive()).thenReturn(true);
+ when(mConnectionCallback2Binder.isBinderAlive()).thenReturn(true);
+ when(mConnectionCallback.asBinder()).thenReturn(mConnectionCallbackBinder);
+ when(mConnectionCallback2.asBinder()).thenReturn(mConnectionCallback2Binder);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ @SmallTest
+ public void testGetInterfaceExists() throws Exception {
+ ImsFeatureContainer fc =
+ getFeatureContainer(mMockMmTelFeatureA, TEST_SERVICE_CAPS);
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fc);
+ ImsFeatureContainer resultFc =
+ mRepository.getIfExists(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL).orElse(null);
+ assertNotNull("returned connection should not be null!", resultFc);
+ assertEquals("returned connection does not match the set connection",
+ fc, resultFc);
+ }
+
+ @Test
+ @SmallTest
+ public void testGetInterfaceDoesntExist() throws Exception {
+ ImsFeatureContainer fc =
+ mRepository.getIfExists(TEST_PHONE_ID_1,
+ ImsFeature.FEATURE_MMTEL).orElse(null);
+ assertNull("returned connection should be null!", fc);
+ }
+
+ @Test
+ @SmallTest
+ public void testGetInterfaceRemoveDoesntExist() throws Exception {
+ ImsFeatureContainer fc =
+ getFeatureContainer(mMockMmTelFeatureA, TEST_SERVICE_CAPS);
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fc);
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, null);
+ ImsFeatureContainer resultFc =
+ mRepository.getIfExists(TEST_PHONE_ID_1,
+ ImsFeature.FEATURE_MMTEL).orElse(null);
+ assertNull("returned connection should be null!", resultFc);
+ }
+
+ @Test
+ @SmallTest
+ public void testGetInterfaceUpdateExists() throws Exception {
+ ImsFeatureContainer fcA =
+ getFeatureContainer(mMockMmTelFeatureA, TEST_SERVICE_CAPS);
+ ImsFeatureContainer fcB =
+ getFeatureContainer(mMockMmTelFeatureA, TEST_SERVICE_CAPS);
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fcA);
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fcB);
+ ImsFeatureContainer resultFc =
+ mRepository.getIfExists(TEST_PHONE_ID_1,
+ ImsFeature.FEATURE_MMTEL).orElse(null);
+ assertNotNull("returned connection should not be null!", resultFc);
+ assertEquals("returned connection does not match the set connection",
+ fcB, resultFc);
+ }
+
+ @Test
+ @SmallTest
+ public void testGetMultipleInterfacesExists() throws Exception {
+ ImsFeatureContainer fcA =
+ getFeatureContainer(mMockMmTelFeatureA, TEST_SERVICE_CAPS);
+ ImsFeatureContainer fcB =
+ getFeatureContainer(mMockRcsFeatureA, TEST_SERVICE_CAPS);
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fcA);
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_RCS, fcB);
+ ImsFeatureContainer resultFcA =
+ mRepository.getIfExists(TEST_PHONE_ID_1,
+ ImsFeature.FEATURE_MMTEL).orElse(null);
+ ImsFeatureContainer resultFcB =
+ mRepository.getIfExists(TEST_PHONE_ID_1,
+ ImsFeature.FEATURE_RCS).orElse(null);
+ assertNotNull("returned connection should not be null!", resultFcA);
+ assertNotNull("returned connection should not be null!", resultFcB);
+ assertEquals("returned connection does not match the set connection",
+ fcA, resultFcA);
+ assertEquals("returned connection does not match the set connection",
+ fcB, resultFcB);
+ }
+
+ @Test
+ @SmallTest
+ public void testListenForUpdate() throws Exception {
+ ImsFeatureContainer fcA =
+ getFeatureContainer(mMockMmTelFeatureA, TEST_SERVICE_CAPS);
+ mRepository.registerForConnectionUpdates(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL,
+ mConnectionCallback, Runnable::run);
+ verify(mConnectionCallback, never()).imsFeatureRemoved(anyInt());
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fcA);
+ verifyFeatureCreatedCalled(1 /*times*/, mConnectionCallback, fcA);
+ verify(mConnectionCallback, never()).imsFeatureRemoved(anyInt());
+ }
+
+ @Test
+ @SmallTest
+ public void testListenNoUpdateForStaleListener() throws Exception {
+ ImsFeatureContainer fcA =
+ getFeatureContainer(mMockMmTelFeatureA, TEST_SERVICE_CAPS);
+ mRepository.registerForConnectionUpdates(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL,
+ mConnectionCallback, Runnable::run);
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fcA);
+ verifyFeatureCreatedCalled(1 /*times*/, mConnectionCallback, fcA);
+ verify(mConnectionCallback, never()).imsFeatureRemoved(anyInt());
+
+ when(mConnectionCallbackBinder.isBinderAlive()).thenReturn(false);
+ // Listener is "dead", so we should not get this update
+ mRepository.notifyFeatureStateChanged(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL,
+ ImsFeature.STATE_READY);
+ verify(mConnectionCallback, never()).imsStatusChanged(ImsFeature.STATE_READY);
+ }
+
+ @Test
+ @SmallTest
+ public void testListenForUpdateStateChanged() throws Exception {
+ ImsFeatureContainer fcA =
+ getFeatureContainer(mMockMmTelFeatureA, TEST_SERVICE_CAPS);
+ mRepository.registerForConnectionUpdates(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL,
+ mConnectionCallback, Runnable::run);
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fcA);
+ ImsFeatureContainer resultFc =
+ mRepository.getIfExists(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL).orElse(null);
+ assertNotNull(resultFc);
+ assertEquals(ImsFeature.STATE_UNAVAILABLE, resultFc.getState());
+ verifyFeatureCreatedCalled(1 /*times*/, mConnectionCallback, fcA);
+ verify(mConnectionCallback, never()).imsFeatureRemoved(anyInt());
+ verify(mConnectionCallback, never()).imsStatusChanged(anyInt());
+
+ mRepository.notifyFeatureStateChanged(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL,
+ ImsFeature.STATE_READY);
+ verify(mConnectionCallback).imsStatusChanged(ImsFeature.STATE_READY);
+ assertEquals(ImsFeature.STATE_READY, resultFc.getState());
+ }
+
+ @Test
+ @SmallTest
+ public void testListenForUpdateCapsChanged() throws Exception {
+ ImsFeatureContainer fcA =
+ getFeatureContainer(mMockMmTelFeatureA, TEST_SERVICE_CAPS);
+ mRepository.registerForConnectionUpdates(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL,
+ mConnectionCallback, Runnable::run);
+
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fcA);
+ ImsFeatureContainer resultFc =
+ mRepository.getIfExists(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL).orElse(null);
+ assertNotNull(resultFc);
+ assertEquals(TEST_SERVICE_CAPS, resultFc.getCapabilities());
+ verify(mConnectionCallback, never()).imsFeatureRemoved(anyInt());
+
+ mRepository.notifyFeatureCapabilitiesChanged(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, 0);
+ verify(mConnectionCallback).updateCapabilities(0);
+ assertEquals(0, resultFc.getCapabilities());
+ }
+
+
+ @Test
+ @SmallTest
+ public void testRemoveCallback() throws Exception {
+ ImsFeatureContainer fcA =
+ getFeatureContainer(mMockMmTelFeatureA, TEST_SERVICE_CAPS);
+ ImsFeatureContainer fcB =
+ getFeatureContainer(mMockMmTelFeatureB, TEST_SERVICE_CAPS);
+ mRepository.registerForConnectionUpdates(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL,
+ mConnectionCallback, Runnable::run);
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fcA);
+ mRepository.unregisterForConnectionUpdates(mConnectionCallback);
+
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fcB);
+ verifyFeatureCreatedCalled(1 /*times*/, mConnectionCallback, fcA);
+ verifyFeatureCreatedCalled(0 /*times*/, mConnectionCallback, fcB);
+ verify(mConnectionCallback, never()).imsFeatureRemoved(anyInt());
+ }
+
+ @Test
+ @SmallTest
+ public void testAddSameCallback() throws Exception {
+ ImsFeatureContainer fcA =
+ getFeatureContainer(mMockMmTelFeatureA, TEST_SERVICE_CAPS);
+ mRepository.registerForConnectionUpdates(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL,
+ mConnectionCallback, Runnable::run);
+ mRepository.registerForConnectionUpdates(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL,
+ mConnectionCallback, Runnable::run);
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fcA);
+ verifyFeatureCreatedCalled(1 /*times*/, mConnectionCallback, fcA);
+ verify(mConnectionCallback, never()).imsFeatureRemoved(anyInt());
+ }
+
+ @Test
+ @SmallTest
+ public void testListenAfterUpdate() throws Exception {
+ ImsFeatureContainer fcA =
+ getFeatureContainer(mMockMmTelFeatureA, TEST_SERVICE_CAPS);
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fcA);
+ mRepository.registerForConnectionUpdates(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL,
+ mConnectionCallback, Runnable::run);
+ verifyFeatureCreatedCalled(1 /*times*/, mConnectionCallback, fcA);
+ verify(mConnectionCallback, never()).imsFeatureRemoved(anyInt());
+ }
+
+ @Test
+ @SmallTest
+ public void testListenNoUpdate() throws Exception {
+ mRepository.registerForConnectionUpdates(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL,
+ mConnectionCallback, Runnable::run);
+ verify(mConnectionCallback, never()).imsFeatureCreated(any());
+ verify(mConnectionCallback, never()).imsFeatureRemoved(anyInt());
+ }
+
+ @Test
+ @SmallTest
+ public void testListenNull() throws Exception {
+ ImsFeatureContainer fcA =
+ getFeatureContainer(mMockMmTelFeatureA, TEST_SERVICE_CAPS);
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fcA);
+ mRepository.registerForConnectionUpdates(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL,
+ mConnectionCallback, Runnable::run);
+ mRepository.removeConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL);
+ verifyFeatureCreatedCalled(1 /*times*/, mConnectionCallback, fcA);
+ verify(mConnectionCallback).imsFeatureRemoved(
+ FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
+ }
+
+ @Test
+ @SmallTest
+ public void testMultipleListeners() throws Exception {
+ ImsFeatureContainer fcA =
+ getFeatureContainer(mMockMmTelFeatureA, TEST_SERVICE_CAPS);
+ ImsFeatureContainer fcB =
+ getFeatureContainer(mMockRcsFeatureA, TEST_SERVICE_CAPS);
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fcA);
+ mRepository.registerForConnectionUpdates(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL,
+ mConnectionCallback, Runnable::run);
+ verifyFeatureCreatedCalled(1 /*times*/, mConnectionCallback, fcA);
+ verify(mConnectionCallback, never()).imsFeatureRemoved(anyInt());
+
+ mRepository.registerForConnectionUpdates(TEST_PHONE_ID_1, ImsFeature.FEATURE_RCS,
+ mConnectionCallback2, Runnable::run);
+ verifyFeatureCreatedCalled(1 /*times*/, mConnectionCallback, fcA);
+ verify(mConnectionCallback, never()).imsFeatureRemoved(anyInt());
+ verify(mConnectionCallback2, never()).imsFeatureCreated(any());
+ verify(mConnectionCallback2, never()).imsFeatureRemoved(anyInt());
+
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_RCS, fcB);
+ verifyFeatureCreatedCalled(1 /*times*/, mConnectionCallback, fcA);
+ verify(mConnectionCallback, never()).imsFeatureRemoved(anyInt());
+ verifyFeatureCreatedCalled(1 /*times*/, mConnectionCallback2, fcB);
+ verify(mConnectionCallback2, never()).imsFeatureRemoved(anyInt());
+ }
+
+ @Test
+ @SmallTest
+ public void testMultiplePhones() throws Exception {
+ ImsFeatureContainer fcA =
+ getFeatureContainer(mMockMmTelFeatureA, TEST_SERVICE_CAPS);
+ ImsFeatureContainer fcB =
+ getFeatureContainer(mMockRcsFeatureA, TEST_SERVICE_CAPS);
+ mRepository.addConnection(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL, fcA);
+ mRepository.registerForConnectionUpdates(TEST_PHONE_ID_1, ImsFeature.FEATURE_MMTEL,
+ mConnectionCallback, Runnable::run);
+ verifyFeatureCreatedCalled(1 /*times*/, mConnectionCallback, fcA);
+ verify(mConnectionCallback, never()).imsFeatureRemoved(anyInt());
+
+ mRepository.registerForConnectionUpdates(TEST_PHONE_ID_2, ImsFeature.FEATURE_RCS,
+ mConnectionCallback2, Runnable::run);
+ verifyFeatureCreatedCalled(1 /*times*/, mConnectionCallback, fcA);
+ verify(mConnectionCallback, never()).imsFeatureRemoved(anyInt());
+ verify(mConnectionCallback2, never()).imsFeatureCreated(any());
+ verify(mConnectionCallback2, never()).imsFeatureRemoved(anyInt());
+
+ mRepository.addConnection(TEST_PHONE_ID_2, ImsFeature.FEATURE_RCS, fcB);
+ verifyFeatureCreatedCalled(1 /*times*/, mConnectionCallback, fcA);
+ verify(mConnectionCallback, never()).imsFeatureRemoved(anyInt());
+ verifyFeatureCreatedCalled(1 /*times*/, mConnectionCallback2, fcB);
+ verify(mConnectionCallback2, never()).imsFeatureRemoved(anyInt());
+ }
+
+ private void verifyFeatureCreatedCalled(int timesCalled, IImsServiceFeatureCallback cb,
+ ImsFeatureContainer fc) throws Exception {
+ verify(cb, times(timesCalled)).imsFeatureCreated(fc);
+ }
+
+ private ImsFeatureContainer getFeatureContainer(IBinder feature, long caps) {
+ return new ImsFeatureContainer(feature, mMockImsConfig,
+ mMockImsRegistration, caps);
+ }
+}
diff --git a/tests/src/com/android/ims/ImsFeatureContainerTest.java b/tests/src/com/android/ims/ImsFeatureContainerTest.java
new file mode 100644
index 00000000..5272d84e
--- /dev/null
+++ b/tests/src/com/android/ims/ImsFeatureContainerTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.ims;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.telephony.ims.ImsService;
+import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.ImsConfigImplBase;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ImsFeatureContainerTest {
+
+ // Use real objects here as I'm not sure how mock IBinders/IInterfaces would parcel.
+ private MmTelFeature mMmTelFeature = new MmTelFeature();
+ private ImsConfigImplBase mImsConfig = new ImsConfigImplBase();
+ private ImsRegistrationImplBase mImsReg = new ImsRegistrationImplBase();
+
+ @Test
+ @SmallTest
+ public void testParcelUnparcel() throws Exception {
+ final int state = ImsFeature.STATE_READY;
+ final long caps = ImsService.CAPABILITY_EMERGENCY_OVER_MMTEL;
+ ImsFeatureContainer c = new ImsFeatureContainer(mMmTelFeature.getBinder().asBinder(),
+ mImsConfig.getIImsConfig(), mImsReg.getBinder(), caps);
+ c.setState(state);
+
+ ImsFeatureContainer result = parcelUnparcel(c);
+
+ assertEquals(mMmTelFeature.getBinder().asBinder(), result.imsFeature);
+ assertEquals(mImsConfig.getIImsConfig(), result.imsConfig);
+ assertEquals(mImsReg.getBinder(), result.imsRegistration);
+ assertEquals(state, result.getState());
+ assertEquals(caps, result.getCapabilities());
+
+ assertEquals(c, result);
+ }
+
+ public ImsFeatureContainer parcelUnparcel(ImsFeatureContainer data) {
+ Parcel parcel = Parcel.obtain();
+ data.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ImsFeatureContainer unparceledData =
+ ImsFeatureContainer.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+ return unparceledData;
+ }
+}
diff --git a/tests/src/com/android/ims/ImsManagerTest.java b/tests/src/com/android/ims/ImsManagerTest.java
index 43ba11c5..db90c8a0 100644
--- a/tests/src/com/android/ims/ImsManagerTest.java
+++ b/tests/src/com/android/ims/ImsManagerTest.java
@@ -27,11 +27,15 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.pm.PackageManager;
+import android.os.IBinder;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.ims.ImsMmTelManager;
import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IImsMmTelFeature;
+import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.stub.ImsConfigImplBase;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -68,6 +72,9 @@ public class ImsManagerTest extends ImsTestBase {
Hashtable<Integer, Integer> mProvisionedIntVals = new Hashtable<>();
ImsConfigImplBase.ImsConfigStub mImsConfigStub;
@Mock MmTelFeatureConnection mMmTelFeatureConnection;
+ @Mock IBinder mMmTelFeature;
+ @Mock IImsConfig mImsConfig;
+ @Mock IImsRegistration mImsReg;
@Mock ImsManager.SubscriptionManagerProxy mSubscriptionManagerProxy;
private final int[] mSubId = {0};
@@ -818,9 +825,11 @@ public class ImsManagerTest extends ImsTestBase {
mImsConfigStub = new ImsConfigImplBase.ImsConfigStub(mImsConfigImplBaseMock);
doReturn(mImsConfigStub).when(mMmTelFeatureConnection).getConfig();
-
- return new ImsManager(mContext, mPhoneId,
- (context, phoneId) -> mMmTelFeatureConnection, mSubscriptionManagerProxy);
+ ImsManager mgr = new ImsManager(mContext, mPhoneId,
+ (context, phoneId, feature, c, r) -> mMmTelFeatureConnection,
+ mSubscriptionManagerProxy);
+ mgr.associate(mMmTelFeature, mImsConfig, mImsReg);
+ return mgr;
}
// If the value is ever set, return the set value. If not, return a constant value 1000.