blob: d6954f3f6960487fda0203f3c9c5cff977d2a52f [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.Nullable;
import android.content.Context;
import android.net.Uri;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.telephony.Rlog;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsCallProfile;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.aidl.IImsConfig;
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.Log;
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 java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 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";
protected final int mSlotId;
protected IBinder mBinder;
private Context mContext;
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;
// 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 IBinder.DeathRecipient mDeathRecipient = () -> {
Log.w(TAG, "DeathRecipient triggered, binder died.");
onRemovedOrDied();
};
private abstract class CallbackAdapterManager<T> {
private static final String TAG = "CallbackAdapterManager";
protected final Set<T> mLocalCallbacks =
Collections.newSetFromMap(new ConcurrentHashMap<>());
private boolean mHasConnected = false;
public void addCallback(T localCallback) throws RemoteException {
// We only one one binding to the ImsService per process.
// Store any more locally.
synchronized (mLock) {
if (!mHasConnected) {
// throws a RemoteException if a connection can not be established.
if (createConnection()) {
mHasConnected = true;
} else {
throw new RemoteException("Can not create connection!");
}
}
}
Log.i(TAG, "Local callback added: " + localCallback);
mLocalCallbacks.add(localCallback);
}
public void removeCallback(T localCallback) {
// We only maintain one binding to the ImsService per process.
Log.i(TAG, "Local callback removed: " + localCallback);
mLocalCallbacks.remove(localCallback);
synchronized (mLock) {
// If we have removed all local callbacks, remove callback to ImsService.
if(mHasConnected) {
if (mLocalCallbacks.isEmpty()) {
removeConnection();
mHasConnected = false;
}
}
}
}
public void close() {
synchronized (mLock) {
if (mHasConnected) {
removeConnection();
// Still mark the connection as disconnected, even if this fails.
mHasConnected = false;
}
}
Log.i(TAG, "Closing connection and clearing callbacks");
mLocalCallbacks.clear();
}
abstract boolean createConnection() throws RemoteException;
abstract void removeConnection();
}
private ImsRegistrationCallbackAdapter mRegistrationCallbackManager
= new ImsRegistrationCallbackAdapter();
private class ImsRegistrationCallbackAdapter
extends CallbackAdapterManager<ImsRegistrationImplBase.Callback> {
private final RegistrationCallbackAdapter mRegistrationCallbackAdapter
= new RegistrationCallbackAdapter();
private class RegistrationCallbackAdapter extends IImsRegistrationCallback.Stub {
@Override
public void onRegistered(int imsRadioTech) {
Log.i(TAG, "onRegistered ::");
mLocalCallbacks.forEach(l -> l.onRegistered(imsRadioTech));
}
@Override
public void onRegistering(int imsRadioTech) {
Log.i(TAG, "onRegistering ::");
mLocalCallbacks.forEach(l -> l.onRegistering(imsRadioTech));
}
@Override
public void onDeregistered(ImsReasonInfo imsReasonInfo) {
Log.i(TAG, "onDeregistered ::");
mLocalCallbacks.forEach(l -> l.onDeregistered(imsReasonInfo));
}
@Override
public void onTechnologyChangeFailed(int targetRadioTech, ImsReasonInfo imsReasonInfo) {
Log.i(TAG, "onTechnologyChangeFailed :: targetAccessTech=" + targetRadioTech +
", imsReasonInfo=" + imsReasonInfo);
mLocalCallbacks.forEach(l -> l.onTechnologyChangeFailed(targetRadioTech,
imsReasonInfo));
}
@Override
public void onSubscriberAssociatedUriChanged(Uri[] uris) {
Log.i(TAG, "onSubscriberAssociatedUriChanged");
mLocalCallbacks.forEach(l -> l.onSubscriberAssociatedUriChanged(uris));
}
}
@Override
boolean createConnection() throws RemoteException {
IImsRegistration imsRegistration = getRegistration();
if (imsRegistration != null) {
getRegistration().addRegistrationCallback(mRegistrationCallbackAdapter);
return true;
} else {
Log.e(TAG, "ImsRegistration is null");
return false;
}
}
@Override
void removeConnection() {
IImsRegistration imsRegistration = getRegistration();
if (imsRegistration != null) {
try {
getRegistration().removeRegistrationCallback(mRegistrationCallbackAdapter);
} catch (RemoteException e) {
Log.w(TAG, "removeConnection: couldn't remove registration callback");
}
} else {
Log.e(TAG, "ImsRegistration is null");
}
}
}
private final CapabilityCallbackManager mCapabilityCallbackManager
= new CapabilityCallbackManager();
private class CapabilityCallbackManager
extends CallbackAdapterManager<ImsFeature.CapabilityCallback> {
private final CapabilityCallbackAdapter mCallbackAdapter = new CapabilityCallbackAdapter();
private class CapabilityCallbackAdapter extends ImsFeature.CapabilityCallback {
// Called when the Capabilities Status on this connection have changed.
@Override
public void onCapabilitiesStatusChanged(ImsFeature.Capabilities config) {
mLocalCallbacks.forEach(
callback -> callback.onCapabilitiesStatusChanged(config));
}
}
@Override
boolean createConnection() throws RemoteException {
IImsMmTelFeature binder;
synchronized (mLock) {
checkServiceIsReady();
binder = getServiceInterface(mBinder);
}
if (binder != null) {
binder.addCapabilityCallback(mCallbackAdapter);
return true;
} else {
Log.w(TAG, "create: Couldn't get IImsMmTelFeature binder");
return false;
}
}
@Override
void removeConnection() {
IImsMmTelFeature binder = null;
synchronized (mLock) {
try {
checkServiceIsReady();
binder = getServiceInterface(mBinder);
} catch (RemoteException e) {
// binder is null
}
}
if (binder != null) {
try {
binder.removeCapabilityCallback(mCallbackAdapter);
} catch (RemoteException e) {
Log.w(TAG, "remove: IImsMmTelFeature binder is dead");
}
} else {
Log.w(TAG, "remove: Couldn't get IImsMmTelFeature binder");
}
}
}
public static MmTelFeatureConnection create(Context context , int slotId) {
MmTelFeatureConnection serviceProxy = new MmTelFeatureConnection(context, slotId);
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) throws RemoteException {
// 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) throws RemoteException {
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) throws RemoteException {
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;
}
/**
* Called when the MmTelFeature has either been removed by Telephony or crashed.
*/
private void onRemovedOrDied() {
synchronized (mLock) {
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();
try {
synchronized (mLock) {
if (isBinderAlive()) {
getServiceInterface(mBinder).setListener(null);
}
}
} catch (RemoteException e) {
Log.w(TAG, "closeConnection: couldn't remove listener!");
}
}
public void addRegistrationCallback(ImsRegistrationImplBase.Callback callback)
throws RemoteException {
mRegistrationCallbackManager.addCallback(callback);
}
public void removeRegistrationCallback(ImsRegistrationImplBase.Callback callback)
throws RemoteException {
mRegistrationCallbackManager.removeCallback(callback);
}
public void addCapabilityCallback(ImsFeature.CapabilityCallback callback)
throws RemoteException {
mCapabilityCallbackManager.addCallback(callback);
}
public void removeCapabilityCallback(ImsFeature.CapabilityCallback callback)
throws RemoteException {
mCapabilityCallbackManager.removeCallback(callback);
}
public void changeEnabledCapabilities(CapabilityChangeRequest request,
ImsFeature.CapabilityCallback callback) throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
getServiceInterface(mBinder).changeCapabilitiesConfiguration(request, callback);
}
}
public void queryEnabledCapabilities(int capability, int radioTech,
ImsFeature.CapabilityCallback 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() throws RemoteException {
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();
}
protected void checkServiceIsReady() throws RemoteException {
if (!isBinderReady()) {
throw new RemoteException("ImsServiceProxy is not ready to accept commands.");
}
}
private IImsMmTelFeature getServiceInterface(IBinder b) {
return IImsMmTelFeature.Stub.asInterface(b);
}
protected void checkBinderConnection() throws RemoteException {
if (!isBinderAlive()) {
throw new RemoteException("ImsServiceProxy is not available for that feature.");
}
}
}