diff options
-rw-r--r-- | src/java/com/android/ims/FeatureConnection.java | 175 | ||||
-rw-r--r-- | src/java/com/android/ims/FeatureConnector.java | 381 | ||||
-rw-r--r-- | src/java/com/android/ims/FeatureUpdates.java | 72 | ||||
-rw-r--r-- | src/java/com/android/ims/IFeatureConnector.java | 24 | ||||
-rw-r--r-- | src/java/com/android/ims/ImsFeatureBinderRepository.java | 438 | ||||
-rw-r--r-- | src/java/com/android/ims/ImsManager.java | 262 | ||||
-rw-r--r-- | src/java/com/android/ims/MmTelFeatureConnection.java | 118 | ||||
-rw-r--r-- | src/java/com/android/ims/RcsFeatureConnection.java | 106 | ||||
-rw-r--r-- | src/java/com/android/ims/RcsFeatureManager.java | 148 | ||||
-rw-r--r-- | tests/src/com/android/ims/FeatureConnectionTest.java | 77 | ||||
-rw-r--r-- | tests/src/com/android/ims/FeatureConnectorTest.java | 389 | ||||
-rw-r--r-- | tests/src/com/android/ims/ImsFeatureBinderRepositoryTest.java | 361 | ||||
-rw-r--r-- | tests/src/com/android/ims/ImsFeatureContainerTest.java | 71 | ||||
-rw-r--r-- | tests/src/com/android/ims/ImsManagerTest.java | 15 |
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. |