blob: 50dc91edb77ab5877b4b4067db992424c4246739 [file] [log] [blame]
/*
* Copyright (C) 2017 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.content.Context;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.telephony.Rlog;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsCallProfile;
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;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.telephony.ims.stub.ImsSmsImplBase;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsEcbm;
import com.android.ims.internal.IImsMultiEndpoint;
import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.ims.internal.IImsUt;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
/**
* A container of the IImsServiceController binder, which implements all of the ImsFeatures that
* the platform currently supports: MMTel and RCS.
* @hide
*/
public class MmTelFeatureConnection {
protected static final String TAG = "MmTelFeatureConnection";
// Manages callbacks to the associated MmTelFeature in mMmTelFeatureConnection.
@VisibleForTesting
public static abstract class CallbackAdapterManager<T extends IInterface> {
private static final String TAG = "CallbackAdapterManager";
private final Context mContext;
private final Object mLock;
// Map of sub id -> List<callbacks> for sub id linked callbacks.
private final SparseArray<Set<T>> mCallbackSubscriptionMap = new SparseArray<>();
// List of all active callbacks to ImsService
private final RemoteCallbackList<T> mRemoteCallbacks = new RemoteCallbackList<>();
@VisibleForTesting
public SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener;
public CallbackAdapterManager(Context context, Object lock) {
mContext = context;
mLock = lock;
if (Looper.myLooper() == null) {
Looper.prepare();
}
// Must be created after Looper.prepare() is called, or else we will get an exception.
mSubChangedListener = new SubscriptionManager.OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
SubscriptionManager manager = mContext.getSystemService(
SubscriptionManager.class);
if (manager == null) {
Log.w(TAG, "onSubscriptionsChanged: could not find SubscriptionManager.");
return;
}
List<SubscriptionInfo> subInfos = manager.getActiveSubscriptionInfoList(false);
if (subInfos == null) {
subInfos = Collections.emptyList();
}
Set<Integer> newSubIds = subInfos.stream()
.map(SubscriptionInfo::getSubscriptionId)
.collect(Collectors.toSet());
synchronized (mLock) {
Set<Integer> storedSubIds = new ArraySet<>(mCallbackSubscriptionMap.size());
for (int keyIndex = 0; keyIndex < mCallbackSubscriptionMap.size();
keyIndex++) {
storedSubIds.add(mCallbackSubscriptionMap.keyAt(keyIndex));
}
// Get the set of sub ids that are in storedSubIds that are not in newSubIds.
// This is the set of sub ids that need to be removed.
storedSubIds.removeAll(newSubIds);
for (Integer subId : storedSubIds) {
removeCallbacksForSubscription(subId);
}
}
}
};
}
// Add a callback to the MmTelFeature associated with this manager (independent of the)
// current subscription.
public final void addCallback(T localCallback) {
synchronized (mLock) {
// Skip registering to callback subscription map here, because we are registering
// for the slot, independent of subscription (deprecated behavior).
// Throws a IllegalStateException if this registration fails.
registerCallback(localCallback);
Log.i(TAG, "Local callback added: " + localCallback);
mRemoteCallbacks.register(localCallback);
}
}
// Add a callback to be associated with a subscription. If that subscription is removed,
// remove the callback and notify the callback that the subscription has been removed.
public final void addCallbackForSubscription(T localCallback, int subId) {
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
return;
}
synchronized (mLock) {
addCallback(localCallback);
linkCallbackToSubscription(localCallback, subId);
}
}
// Removes a callback associated with the MmTelFeature.
public final void removeCallback(T localCallback) {
Log.i(TAG, "Local callback removed: " + localCallback);
synchronized (mLock) {
if (mRemoteCallbacks.unregister(localCallback)) {
// Will only occur if we have record of this callback in mRemoteCallbacks.
unregisterCallback(localCallback);
}
}
}
// Remove an existing callback that has been linked to a subscription.
public final void removeCallbackForSubscription(T localCallback, int subId) {
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
return;
}
synchronized (mLock) {
removeCallback(localCallback);
unlinkCallbackFromSubscription(localCallback, subId);
}
}
// Links a callback to be tracked by a subscription. If it goes away, emove.
private void linkCallbackToSubscription(T callback, int subId) {
synchronized (mLock) {
if (mCallbackSubscriptionMap.size() == 0) {
// we are about to add the first entry to the map, register for subscriptions
//changed listener.
registerForSubscriptionsChanged();
}
Set<T> callbacksPerSub = mCallbackSubscriptionMap.get(subId);
if (callbacksPerSub == null) {
// the callback list has not been created yet for this subscription.
callbacksPerSub = new ArraySet<>();
mCallbackSubscriptionMap.put(subId, callbacksPerSub);
}
callbacksPerSub.add(callback);
}
}
// Unlink the callback from the associated subscription.
private void unlinkCallbackFromSubscription(T callback, int subId) {
synchronized (mLock) {
Set<T> callbacksPerSub = mCallbackSubscriptionMap.get(subId);
if (callbacksPerSub != null) {
callbacksPerSub.remove(callback);
if (callbacksPerSub.isEmpty()) {
mCallbackSubscriptionMap.remove(subId);
}
}
if (mCallbackSubscriptionMap.size() == 0) {
unregisterForSubscriptionsChanged();
}
}
}
// Removes all of the callbacks that have been registered to the subscription specified.
// This happens when Telephony sends an indication that the subscriptions have changed.
private void removeCallbacksForSubscription(int subId) {
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
return;
}
synchronized (mLock) {
Set<T> callbacksPerSub = mCallbackSubscriptionMap.get(subId);
if (callbacksPerSub == null) {
// no callbacks registered for this subscription.
return;
}
// clear all registered callbacks in the subscription map for this subscription.
mCallbackSubscriptionMap.remove(subId);
for (T callback : callbacksPerSub) {
removeCallback(callback);
}
// If there are no more callbacks being tracked, remove subscriptions changed
// listener.
if (mCallbackSubscriptionMap.size() == 0) {
unregisterForSubscriptionsChanged();
}
}
}
// Clear the Subscription -> Callback map because the ImsService connection is no longer
// current.
private void clearCallbacksForAllSubscriptions() {
synchronized (mLock) {
List<Integer> keys = new ArrayList<>();
for (int keyIndex = 0; keyIndex < mCallbackSubscriptionMap.size(); keyIndex++) {
keys.add(mCallbackSubscriptionMap.keyAt(keyIndex));
}
keys.forEach(this::removeCallbacksForSubscription);
}
}
private void registerForSubscriptionsChanged() {
SubscriptionManager manager = mContext.getSystemService(SubscriptionManager.class);
if (manager != null) {
manager.addOnSubscriptionsChangedListener(mSubChangedListener);
} else {
Log.w(TAG, "registerForSubscriptionsChanged: could not find SubscriptionManager.");
}
}
private void unregisterForSubscriptionsChanged() {
SubscriptionManager manager = mContext.getSystemService(SubscriptionManager.class);
if (manager != null) {
manager.removeOnSubscriptionsChangedListener(mSubChangedListener);
} else {
Log.w(TAG, "unregisterForSubscriptionsChanged: could not find"
+ " SubscriptionManager.");
}
}
// The ImsService these callbacks are registered to has become unavailable or crashed, or
// the ImsResolver has switched to a new ImsService. In these cases, clean up all existing
// callbacks.
public final void close() {
synchronized (mLock) {
final int lastCallbackIndex = mRemoteCallbacks.getRegisteredCallbackCount() - 1;
for(int ii = lastCallbackIndex; ii >= 0; ii --) {
T callbackItem = mRemoteCallbacks.getRegisteredCallbackItem(ii);
unregisterCallback(callbackItem);
mRemoteCallbacks.unregister(callbackItem);
}
clearCallbacksForAllSubscriptions();
Log.i(TAG, "Closing connection and clearing callbacks");
}
}
// A callback has been registered. Register that callback with the MmTelFeature.
public abstract void registerCallback(T localCallback);
// A callback has been removed, unregister that callback with the MmTelFeature.
public abstract void unregisterCallback(T localCallback);
}
private class ImsRegistrationCallbackAdapter extends
CallbackAdapterManager<IImsRegistrationCallback> {
public ImsRegistrationCallbackAdapter(Context context, Object lock) {
super(context, lock);
}
@Override
public void registerCallback(IImsRegistrationCallback localCallback) {
IImsRegistration imsRegistration = getRegistration();
if (imsRegistration != null) {
try {
imsRegistration.addRegistrationCallback(localCallback);
} catch (RemoteException e) {
throw new IllegalStateException("ImsRegistrationCallbackAdapter: MmTelFeature"
+ " binder is dead.");
}
} else {
Log.e(TAG, "ImsRegistrationCallbackAdapter: ImsRegistration is null");
throw new IllegalStateException("ImsRegistrationCallbackAdapter: MmTelFeature is"
+ "not available!");
}
}
@Override
public void unregisterCallback(IImsRegistrationCallback localCallback) {
IImsRegistration imsRegistration = getRegistration();
if (imsRegistration != null) {
try {
imsRegistration.removeRegistrationCallback(localCallback);
} catch (RemoteException e) {
Log.w(TAG, "ImsRegistrationCallbackAdapter - unregisterCallback: couldn't"
+ " remove registration callback");
}
} else {
Log.e(TAG, "ImsRegistrationCallbackAdapter: ImsRegistration is null");
}
}
}
private class CapabilityCallbackManager extends CallbackAdapterManager<IImsCapabilityCallback> {
public CapabilityCallbackManager(Context context, Object lock) {
super(context, lock);
}
@Override
public void registerCallback(IImsCapabilityCallback localCallback) {
IImsMmTelFeature binder;
synchronized (mLock) {
try {
checkServiceIsReady();
binder = getServiceInterface(mBinder);
} catch (RemoteException e) {
throw new IllegalStateException("CapabilityCallbackManager - MmTelFeature"
+ " binder is dead.");
}
}
if (binder != null) {
try {
binder.addCapabilityCallback(localCallback);
} catch (RemoteException e) {
throw new IllegalStateException(" CapabilityCallbackManager - MmTelFeature"
+ " binder is null.");
}
} else {
Log.w(TAG, "CapabilityCallbackManager, register: Couldn't get binder");
throw new IllegalStateException("CapabilityCallbackManager: MmTelFeature is"
+ " not available!");
}
}
@Override
public void unregisterCallback(IImsCapabilityCallback localCallback) {
IImsMmTelFeature binder;
synchronized (mLock) {
try {
checkServiceIsReady();
binder = getServiceInterface(mBinder);
} catch (RemoteException e) {
// binder is null
Log.w(TAG, "CapabilityCallbackManager, unregister: couldn't get binder.");
return;
}
}
if (binder != null) {
try {
binder.removeCapabilityCallback(localCallback);
} catch (RemoteException e) {
Log.w(TAG, "CapabilityCallbackManager, unregister: Binder is dead.");
}
} else {
Log.w(TAG, "CapabilityCallbackManager, unregister: binder is null.");
}
}
}
private class ProvisioningCallbackManager extends CallbackAdapterManager<IImsConfigCallback> {
public ProvisioningCallbackManager (Context context, Object lock) {
super(context, lock);
}
@Override
public void registerCallback(IImsConfigCallback localCallback) {
IImsConfig binder = getConfigInterface();
if (binder == null) {
// Config interface is not currently available.
Log.w(TAG, "ProvisioningCallbackManager - couldn't register, binder is null.");
throw new IllegalStateException("ImsConfig is not available!");
}
try {
binder.addImsConfigCallback(localCallback);
}catch (RemoteException e) {
throw new IllegalStateException("ImsService is not available!");
}
}
@Override
public void unregisterCallback(IImsConfigCallback localCallback) {
IImsConfig binder = getConfigInterface();
if (binder == null) {
Log.w(TAG, "ProvisioningCallbackManager - couldn't unregister, binder is null.");
return;
}
try {
binder.removeImsConfigCallback(localCallback);
} catch (RemoteException e) {
Log.w(TAG, "ProvisioningCallbackManager - couldn't unregister, binder is dead.");
}
}
}
protected final int mSlotId;
protected IBinder mBinder;
private Context mContext;
private Executor mExecutor;
private volatile boolean mIsAvailable = false;
// ImsFeature Status from the ImsService. Cached.
private Integer mFeatureStateCached = null;
private IFeatureUpdate mStatusCallback;
private final Object mLock = new Object();
// Updated by IImsServiceFeatureCallback when FEATURE_EMERGENCY_MMTEL is sent.
private boolean mSupportsEmergencyCalling = false;
private static boolean sImsSupportedOnDevice = true;
// Cache the Registration and Config interfaces as long as the MmTel feature is connected. If
// it becomes disconnected, invalidate.
private IImsRegistration mRegistrationBinder;
private IImsConfig mConfigBinder;
private final IBinder.DeathRecipient mDeathRecipient = () -> {
Log.w(TAG, "DeathRecipient triggered, binder died.");
if (mContext != null && Looper.getMainLooper() != null) {
// Move this signal to the main thread, notifying ImsManager of the Binder
// death on another thread may lead to deadlocks.
mContext.getMainExecutor().execute(this::onRemovedOrDied);
return;
}
// No choice - execute on the current Binder thread.
onRemovedOrDied();
};
private final ImsRegistrationCallbackAdapter mRegistrationCallbackManager;
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 = getTelephonyManager(context);
if (tm == null) {
Rlog.w(TAG, "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, "create: binder is null! Slot Id: " + slotId);
}
return serviceProxy;
}
public static TelephonyManager getTelephonyManager(Context context) {
return (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
}
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();
}
private final IImsServiceFeatureCallback mListenerBinder =
new IImsServiceFeatureCallback.Stub() {
@Override
public void imsFeatureCreated(int slotId, int feature) {
mExecutor.execute(() -> {
// 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, "MmTel enabled on slotId: " + slotId);
mIsAvailable = true;
}
break;
}
case ImsFeature.FEATURE_EMERGENCY_MMTEL: {
mSupportsEmergencyCalling = true;
Log.i(TAG, "Emergency calling enabled on slotId: " + slotId);
break;
}
}
}
});
}
@Override
public void imsFeatureRemoved(int slotId, int feature) {
mExecutor.execute(() -> {
synchronized (mLock) {
if (mSlotId != slotId) {
return;
}
switch (feature) {
case ImsFeature.FEATURE_MMTEL: {
Log.i(TAG, "MmTel removed on slotId: " + slotId);
onRemovedOrDied();
break;
}
case ImsFeature.FEATURE_EMERGENCY_MMTEL: {
mSupportsEmergencyCalling = false;
Log.i(TAG, "Emergency calling disabled on slotId: " + slotId);
break;
}
}
}
});
}
@Override
public void imsStatusChanged(int slotId, int feature, int status) {
mExecutor.execute(() -> {
synchronized (mLock) {
Log.i(TAG, "imsStatusChanged: slot: " + slotId + " feature: " + feature +
" status: " + status);
if (mSlotId == slotId && feature == ImsFeature.FEATURE_MMTEL) {
mFeatureStateCached = status;
if (mStatusCallback != null) {
mStatusCallback.notifyStateChanged();
}
}
}
});
}
};
public MmTelFeatureConnection(Context context, int slotId) {
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()));
}
mRegistrationCallbackManager = new ImsRegistrationCallbackAdapter(context, mLock);
mCapabilityCallbackManager = new CapabilityCallbackManager(context, mLock);
mProvisioningCallbackManager = new ProvisioningCallbackManager(context, mLock);
}
/**
* Called when the MmTelFeature has either been removed by Telephony or crashed.
*/
private void onRemovedOrDied() {
synchronized (mLock) {
mRegistrationCallbackManager.close();
mCapabilityCallbackManager.close();
mProvisioningCallbackManager.close();
if (mIsAvailable) {
mIsAvailable = false;
// invalidate caches.
mRegistrationBinder = null;
mConfigBinder = null;
if (mBinder != null) {
mBinder.unlinkToDeath(mDeathRecipient, 0);
}
if (mStatusCallback != null) {
mStatusCallback.notifyUnavailable();
}
}
}
}
private @Nullable IImsRegistration getRegistration() {
synchronized (mLock) {
// null if cache is invalid;
if (mRegistrationBinder != null) {
return mRegistrationBinder;
}
}
TelephonyManager tm = getTelephonyManager(mContext);
// We don't want to synchronize on a binder call to another process.
IImsRegistration regBinder = tm != null
? tm.getImsRegistration(mSlotId, ImsFeature.FEATURE_MMTEL) : null;
synchronized (mLock) {
// mRegistrationBinder may have changed while we tried to get the registration
// interface.
if (mRegistrationBinder == null) {
mRegistrationBinder = regBinder;
}
}
return mRegistrationBinder;
}
private IImsConfig getConfig() {
synchronized (mLock) {
// null if cache is invalid;
if (mConfigBinder != null) {
return mConfigBinder;
}
}
TelephonyManager tm = getTelephonyManager(mContext);
IImsConfig configBinder = tm != null
? tm.getImsConfig(mSlotId, ImsFeature.FEATURE_MMTEL) : null;
synchronized (mLock) {
// mConfigBinder may have changed while we tried to get the config interface.
if (mConfigBinder == null) {
mConfigBinder = configBinder;
}
}
return mConfigBinder;
}
public boolean isEmergencyMmTelAvailable() {
return mSupportsEmergencyCalling;
}
public IImsServiceFeatureCallback getListener() {
return mListenerBinder;
}
public void setBinder(IBinder binder) {
synchronized (mLock) {
mBinder = binder;
try {
if (mBinder != null) {
mBinder.linkToDeath(mDeathRecipient, 0);
}
} catch (RemoteException e) {
// No need to do anything if the binder is already dead.
}
}
}
/**
* Opens the connection to the {@link MmTelFeature} and establishes a listener back to the
* framework. Calling this method multiple times will reset the listener attached to the
* {@link MmTelFeature}.
* @param listener A {@link MmTelFeature.Listener} that will be used by the {@link MmTelFeature}
* to notify the framework of updates.
*/
public void openConnection(MmTelFeature.Listener listener) throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
getServiceInterface(mBinder).setListener(listener);
}
}
public void closeConnection() {
mRegistrationCallbackManager.close();
mCapabilityCallbackManager.close();
mProvisioningCallbackManager.close();
try {
synchronized (mLock) {
if (isBinderAlive()) {
getServiceInterface(mBinder).setListener(null);
}
}
} catch (RemoteException e) {
Log.w(TAG, "closeConnection: couldn't remove listener!");
}
}
public void addRegistrationCallback(IImsRegistrationCallback callback) {
mRegistrationCallbackManager.addCallback(callback);
}
public void addRegistrationCallbackForSubscription(IImsRegistrationCallback callback,
int subId) {
mRegistrationCallbackManager.addCallbackForSubscription(callback , subId);
}
public void removeRegistrationCallback(IImsRegistrationCallback callback) {
mRegistrationCallbackManager.removeCallback(callback);
}
public void removeRegistrationCallbackForSubscription(IImsRegistrationCallback callback,
int subId) {
mRegistrationCallbackManager.removeCallbackForSubscription(callback, subId);
}
public void addCapabilityCallback(IImsCapabilityCallback callback) {
mCapabilityCallbackManager.addCallback(callback);
}
public void addCapabilityCallbackForSubscription(IImsCapabilityCallback callback,
int subId) {
mCapabilityCallbackManager.addCallbackForSubscription(callback, subId);
}
public void removeCapabilityCallback(IImsCapabilityCallback callback) {
mCapabilityCallbackManager.removeCallback(callback);
}
public void removeCapabilityCallbackForSubscription(IImsCapabilityCallback callback,
int subId) {
mCapabilityCallbackManager.removeCallbackForSubscription(callback , subId);
}
public void addProvisioningCallbackForSubscription(IImsConfigCallback callback,
int subId) {
mProvisioningCallbackManager.addCallbackForSubscription(callback, subId);
}
public void removeProvisioningCallbackForSubscription(IImsConfigCallback callback,
int subId) {
mProvisioningCallbackManager.removeCallbackForSubscription(callback , subId);
}
public void changeEnabledCapabilities(CapabilityChangeRequest request,
IImsCapabilityCallback callback) throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
getServiceInterface(mBinder).changeCapabilitiesConfiguration(request, callback);
}
}
public void queryEnabledCapabilities(int capability, int radioTech,
IImsCapabilityCallback callback) throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
getServiceInterface(mBinder).queryCapabilityConfiguration(capability, radioTech,
callback);
}
}
public MmTelFeature.MmTelCapabilities queryCapabilityStatus() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
return new MmTelFeature.MmTelCapabilities(
getServiceInterface(mBinder).queryCapabilityStatus());
}
}
public ImsCallProfile createCallProfile(int callServiceType, int callType)
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
return getServiceInterface(mBinder).createCallProfile(callServiceType, callType);
}
}
public IImsCallSession createCallSession(ImsCallProfile profile)
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
return getServiceInterface(mBinder).createCallSession(profile);
}
}
public IImsUt getUtInterface() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
return getServiceInterface(mBinder).getUtInterface();
}
}
public IImsConfig getConfigInterface() {
return getConfig();
}
public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTech()
throws RemoteException {
IImsRegistration registration = getRegistration();
if (registration != null) {
return registration.getRegistrationTechnology();
} else {
return ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
}
}
public IImsEcbm getEcbmInterface() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
return getServiceInterface(mBinder).getEcbmInterface();
}
}
public void setUiTTYMode(int uiTtyMode, Message onComplete)
throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
getServiceInterface(mBinder).setUiTtyMode(uiTtyMode, onComplete);
}
}
public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
return getServiceInterface(mBinder).getMultiEndpointInterface();
}
}
public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
byte[] pdu) throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
getServiceInterface(mBinder).sendSms(token, messageRef, format, smsc, isRetry,
pdu);
}
}
public void acknowledgeSms(int token, int messageRef,
@ImsSmsImplBase.SendStatusResult int result) throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
getServiceInterface(mBinder).acknowledgeSms(token, messageRef, result);
}
}
public void acknowledgeSmsReport(int token, int messageRef,
@ImsSmsImplBase.StatusReportResult int result) throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
getServiceInterface(mBinder).acknowledgeSmsReport(token, messageRef, result);
}
}
public String getSmsFormat() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
return getServiceInterface(mBinder).getSmsFormat();
}
}
public void onSmsReady() throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
getServiceInterface(mBinder).onSmsReady();
}
}
public void setSmsListener(IImsSmsListener listener) throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
getServiceInterface(mBinder).setSmsListener(listener);
}
}
public @MmTelFeature.ProcessCallResult int shouldProcessCall(boolean isEmergency,
String[] numbers) throws RemoteException {
if (isEmergency && !isEmergencyMmTelAvailable()) {
// Don't query the ImsService if emergency calling is not available on the ImsService.
Log.i(TAG, "MmTel does not support emergency over IMS, fallback to CS.");
return MmTelFeature.PROCESS_CALL_CSFB;
}
synchronized (mLock) {
checkServiceIsReady();
return getServiceInterface(mBinder).shouldProcessCall(numbers);
}
}
/**
* @return an integer describing the current Feature Status, defined in
* {@link ImsFeature.ImsState}.
*/
public int getFeatureState() {
synchronized (mLock) {
if (isBinderAlive() && mFeatureStateCached != null) {
return mFeatureStateCached;
}
}
// Don't synchronize on Binder call.
Integer status = retrieveFeatureState();
synchronized (mLock) {
if (status == null) {
return ImsFeature.STATE_UNAVAILABLE;
}
// Cache only non-null value for feature status.
mFeatureStateCached = status;
}
Log.i(TAG, "getFeatureState - returning " + status);
return status;
}
/**
* Internal method used to retrieve the feature status from the corresponding ImsService.
*/
private Integer retrieveFeatureState() {
if (mBinder != null) {
try {
return getServiceInterface(mBinder).getFeatureState();
} catch (RemoteException e) {
// Status check failed, don't update cache
}
}
return null;
}
/**
* @param c Callback that will fire when the feature status has changed.
*/
public void setStatusCallback(IFeatureUpdate c) {
mStatusCallback = c;
}
/**
* @return Returns true if the ImsService is ready to take commands, false otherwise. If this
* method returns false, it doesn't mean that the Binder connection is not available (use
* {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands
* at this time.
*
* For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take
* commands at a time, so the other slot must stay at {@link ImsFeature#STATE_UNAVAILABLE}.
*/
public boolean isBinderReady() {
return isBinderAlive() && getFeatureState() == ImsFeature.STATE_READY;
}
/**
* @return false if the binder connection is no longer alive.
*/
public boolean isBinderAlive() {
return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
}
private void checkServiceIsReady() throws RemoteException {
if (!sImsSupportedOnDevice) {
throw new RemoteException("IMS is not supported on this device.");
}
if (!isBinderReady()) {
throw new RemoteException("ImsServiceProxy is not ready to accept commands.");
}
}
private IImsMmTelFeature getServiceInterface(IBinder b) {
return IImsMmTelFeature.Stub.asInterface(b);
}
}