/* * 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.content.Context; import android.os.Binder; import android.os.IBinder; import android.os.IInterface; import android.os.Message; import android.os.RemoteException; import android.telephony.SubscriptionManager; import android.telephony.ims.ImsCallProfile; import android.telephony.ims.ImsService; import android.telephony.ims.MediaQualityStatus; import android.telephony.ims.MediaThreshold; import android.telephony.ims.RtpHeaderExtensionType; 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.aidl.ISipTransport; import android.telephony.ims.aidl.ISrvccStartedCallback; import android.telephony.ims.feature.CapabilityChangeRequest; import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.stub.ImsEcbmImplBase; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.telephony.ims.stub.ImsSmsImplBase; 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.IImsUt; import java.util.ArrayList; import java.util.HashMap; import java.util.Set; /** * A container of the IImsServiceController binder, which implements all of the ImsFeatures that * the platform currently supports: MMTel */ public class MmTelFeatureConnection extends FeatureConnection { protected static final String TAG = "MmTelFeatureConn"; private class ImsRegistrationCallbackAdapter extends ImsCallbackAdapterManager { public ImsRegistrationCallbackAdapter(Context context, Object lock) { super(context, lock, mSlotId, mSubId); } @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 + " [" + mSlotId + "]", "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 | IllegalStateException e) { Log.w(TAG + " [" + mSlotId + "]", "ImsRegistrationCallbackAdapter -" + " unregisterCallback: couldn't remove registration callback" + " Exception: " + e.getMessage()); } } else { Log.e(TAG + " [" + mSlotId + "]", "ImsRegistrationCallbackAdapter: ImsRegistration" + " is null"); } } } private class CapabilityCallbackManager extends ImsCallbackAdapterManager { public CapabilityCallbackManager(Context context, Object lock) { super(context, lock, mSlotId, mSubId); } @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 + " [" + mSlotId + "]", "CapabilityCallbackManager, register: Couldn't" + " get binder"); throw new IllegalStateException("CapabilityCallbackManager: MmTelFeature is" + " not available!"); } } @Override public void unregisterCallback(IImsCapabilityCallback localCallback) { IImsMmTelFeature binder; synchronized (mLock) { if (!isBinderAlive()) { Log.w(TAG + " [" + mSlotId + "]", "CapabilityCallbackManager, unregister:" + " binder is not alive"); return; } binder = getServiceInterface(mBinder); } if (binder != null) { try { binder.removeCapabilityCallback(localCallback); } catch (RemoteException | IllegalStateException e) { Log.w(TAG + " [" + mSlotId + "]", "CapabilityCallbackManager, unregister:" + " Binder is dead. Exception: " + e.getMessage()); } } else { Log.w(TAG + " [" + mSlotId + "]", "CapabilityCallbackManager, unregister:" + " binder is null."); } } } private class ProvisioningCallbackManager extends ImsCallbackAdapterManager { public ProvisioningCallbackManager (Context context, Object lock) { super(context, lock, mSlotId, mSubId); } @Override public void registerCallback(IImsConfigCallback localCallback) { IImsConfig binder = getConfig(); if (binder == null) { // Config interface is not currently available. Log.w(TAG + " [" + mSlotId + "]", "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 = getConfig(); if (binder == null) { Log.w(TAG + " [" + mSlotId + "]", "ProvisioningCallbackManager - couldn't" + " unregister, binder is null."); return; } try { binder.removeImsConfigCallback(localCallback); } catch (RemoteException | IllegalStateException e) { Log.w(TAG + " [" + mSlotId + "]", "ProvisioningCallbackManager - couldn't" + " unregister, binder is dead. Exception: " + e.getMessage()); } } } private static final class BinderAccessState { /** * We have not tried to get the interface yet. */ static final int STATE_NOT_SET = 0; /** * We have tried to get the interface, but it is not supported. */ static final int STATE_NOT_SUPPORTED = 1; /** * The interface is available from the service. */ static final int STATE_AVAILABLE = 2; public static BinderAccessState of(T value) { return new BinderAccessState<>(value); } private final int mState; private final T mInterface; public BinderAccessState(int state) { mState = state; mInterface = null; } public BinderAccessState(T binderInterface) { mState = STATE_AVAILABLE; mInterface = binderInterface; } public int getState() { return mState; } public T getInterface() { return mInterface; } } // Updated by IImsServiceFeatureCallback when FEATURE_EMERGENCY_MMTEL is sent. private boolean mSupportsEmergencyCalling = false; private BinderAccessState mEcbm = new BinderAccessState<>(BinderAccessState.STATE_NOT_SET); private BinderAccessState mMultiEndpoint = new BinderAccessState<>(BinderAccessState.STATE_NOT_SET); private MmTelFeature.Listener mMmTelFeatureListener; private ImsUt mUt; private final ImsRegistrationCallbackAdapter mRegistrationCallbackManager; private final CapabilityCallbackManager mCapabilityCallbackManager; private final ProvisioningCallbackManager mProvisioningCallbackManager; public MmTelFeatureConnection(Context context, int slotId, int subId, IImsMmTelFeature f, IImsConfig c, IImsRegistration r, ISipTransport s) { super(context, slotId, subId, c, r, s); setBinder((f != null) ? f.asBinder() : null); mRegistrationCallbackManager = new ImsRegistrationCallbackAdapter(context, mLock); mCapabilityCallbackManager = new CapabilityCallbackManager(context, mLock); mProvisioningCallbackManager = new ProvisioningCallbackManager(context, mLock); } @Override protected void onRemovedOrDied() { // Release all callbacks being tracked and unregister them from the connected MmTelFeature. mRegistrationCallbackManager.close(); mCapabilityCallbackManager.close(); mProvisioningCallbackManager.close(); // Close mUt interface separately from other listeners, as it is not tied directly to // calling. There is still a limitation currently that only one UT listener can be set // (through ImsPhoneCallTracker), but this could be relaxed in the future via the ability // to register multiple callbacks. synchronized (mLock) { if (mUt != null) { mUt.close(); mUt = null; } closeConnection(); super.onRemovedOrDied(); } } public boolean isEmergencyMmTelAvailable() { return mSupportsEmergencyCalling; } /** * 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 mmTelListener A {@link MmTelFeature.Listener} that will be used by the * {@link MmTelFeature} to notify the framework of mmtel calling updates. * @param ecbmListener Listener used to listen for ECBM updates from {@link ImsEcbmImplBase} * implementation. */ public void openConnection(MmTelFeature.Listener mmTelListener, ImsEcbmStateListener ecbmListener, ImsExternalCallStateListener multiEndpointListener) throws RemoteException { synchronized (mLock) { checkServiceIsReady(); mMmTelFeatureListener = mmTelListener; getServiceInterface(mBinder).setListener(mmTelListener); setEcbmInterface(ecbmListener); setMultiEndpointInterface(multiEndpointListener); } } /** * Closes the connection to the {@link MmTelFeature} if it was previously opened via * {@link #openConnection} by removing all listeners. */ public void closeConnection() { synchronized (mLock) { if (!isBinderAlive()) return; try { if (mMmTelFeatureListener != null) { mMmTelFeatureListener = null; getServiceInterface(mBinder).setListener(null); } if (mEcbm.getState() == BinderAccessState.STATE_AVAILABLE) { mEcbm.getInterface().setEcbmStateListener(null); mEcbm = new BinderAccessState<>(BinderAccessState.STATE_NOT_SET); } if (mMultiEndpoint.getState() == BinderAccessState.STATE_AVAILABLE) { mMultiEndpoint.getInterface().setExternalCallStateListener(null); mMultiEndpoint = new BinderAccessState<>(BinderAccessState.STATE_NOT_SET); } } catch (RemoteException | IllegalStateException e) { Log.w(TAG + " [" + mSlotId + "]", "closeConnection: couldn't remove listeners!" + " Exception: " + e.getMessage()); } } } 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.removeCallback(callback); } 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.removeCallback(callback); } public void addProvisioningCallbackForSubscription(IImsConfigCallback callback, int subId) { mProvisioningCallbackManager.addCallbackForSubscription(callback, subId); } public void removeProvisioningCallbackForSubscription(IImsConfigCallback callback, int subId) { mProvisioningCallbackManager.removeCallback(callback); } public void setMediaThreshold(@MediaQualityStatus.MediaSessionType int sessionType, MediaThreshold threshold) throws RemoteException { synchronized (mLock) { checkServiceIsReady(); getServiceInterface(mBinder).setMediaQualityThreshold(sessionType, threshold); } } public MediaQualityStatus queryMediaQualityStatus( @MediaQualityStatus.MediaSessionType int sessionType) throws RemoteException { synchronized (mLock) { checkServiceIsReady(); return getServiceInterface(mBinder).queryMediaQualityStatus(sessionType); } } 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 void changeOfferedRtpHeaderExtensionTypes(Set types) throws RemoteException { synchronized (mLock) { checkServiceIsReady(); getServiceInterface(mBinder).changeOfferedRtpHeaderExtensionTypes( new ArrayList<>(types)); } } public IImsCallSession createCallSession(ImsCallProfile profile) throws RemoteException { synchronized (mLock) { checkServiceIsReady(); return getServiceInterface(mBinder).createCallSession(profile); } } public ImsUt createOrGetUtInterface() throws RemoteException { synchronized (mLock) { if (mUt != null) return mUt; checkServiceIsReady(); IImsUt imsUt = getServiceInterface(mBinder).getUtInterface(); // This will internally set up a listener on the ImsUtImplBase interface, and there is // a limitation that there can only be one. If multiple connections try to create this // UT interface, it will throw an IllegalStateException. mUt = (imsUt != null) ? new ImsUt(imsUt, mContext.getMainExecutor()) : null; return mUt; } } private void setEcbmInterface(ImsEcbmStateListener ecbmListener) throws RemoteException { synchronized (mLock) { if (mEcbm.getState() != BinderAccessState.STATE_NOT_SET) { throw new IllegalStateException("ECBM interface already open"); } checkServiceIsReady(); IImsEcbm imsEcbm = getServiceInterface(mBinder).getEcbmInterface(); mEcbm = (imsEcbm != null) ? BinderAccessState.of(new ImsEcbm(imsEcbm)) : new BinderAccessState<>(BinderAccessState.STATE_NOT_SUPPORTED); if (mEcbm.getState() == BinderAccessState.STATE_AVAILABLE) { // May throw an IllegalStateException if a listener already exists. mEcbm.getInterface().setEcbmStateListener(ecbmListener); } } } public ImsEcbm getEcbmInterface() { synchronized (mLock) { if (mEcbm.getState() == BinderAccessState.STATE_NOT_SET) { throw new IllegalStateException("ECBM interface has not been opened"); } return mEcbm.getState() == BinderAccessState.STATE_AVAILABLE ? mEcbm.getInterface() : null; } } public void setUiTTYMode(int uiTtyMode, Message onComplete) throws RemoteException { synchronized (mLock) { checkServiceIsReady(); getServiceInterface(mBinder).setUiTtyMode(uiTtyMode, onComplete); } } private void setMultiEndpointInterface(ImsExternalCallStateListener listener) throws RemoteException { synchronized (mLock) { if (mMultiEndpoint.getState() != BinderAccessState.STATE_NOT_SET) { throw new IllegalStateException("multiendpoint interface is already open"); } checkServiceIsReady(); IImsMultiEndpoint imEndpoint = getServiceInterface(mBinder).getMultiEndpointInterface(); mMultiEndpoint = (imEndpoint != null) ? BinderAccessState.of(new ImsMultiEndpoint(imEndpoint)) : new BinderAccessState<>(BinderAccessState.STATE_NOT_SUPPORTED); if (mMultiEndpoint.getState() == BinderAccessState.STATE_AVAILABLE) { // May throw an IllegalStateException if a listener already exists. mMultiEndpoint.getInterface().setExternalCallStateListener(listener); } } } 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 onMemoryAvailable(int token) throws RemoteException { synchronized (mLock) { checkServiceIsReady(); getServiceInterface(mBinder).onMemoryAvailable(token); } } public void acknowledgeSms(int token, int messageRef, @ImsSmsImplBase.SendStatusResult int result) throws RemoteException { synchronized (mLock) { checkServiceIsReady(); getServiceInterface(mBinder).acknowledgeSms(token, messageRef, result); } } public void acknowledgeSms(int token, int messageRef, @ImsSmsImplBase.SendStatusResult int result, byte[] pdu) throws RemoteException { synchronized (mLock) { checkServiceIsReady(); getServiceInterface(mBinder).acknowledgeSmsWithPdu(token, messageRef, result, pdu); } } 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 void notifySrvccStarted(ISrvccStartedCallback cb) throws RemoteException { synchronized (mLock) { checkServiceIsReady(); getServiceInterface(mBinder).notifySrvccStarted(cb); } } public void notifySrvccCompleted() throws RemoteException { synchronized (mLock) { checkServiceIsReady(); getServiceInterface(mBinder).notifySrvccCompleted(); } } public void notifySrvccFailed() throws RemoteException { synchronized (mLock) { checkServiceIsReady(); getServiceInterface(mBinder).notifySrvccFailed(); } } public void notifySrvccCanceled() throws RemoteException { synchronized (mLock) { checkServiceIsReady(); getServiceInterface(mBinder).notifySrvccCanceled(); } } public void triggerDeregistration(@ImsRegistrationImplBase.ImsDeregistrationReason int reason) throws RemoteException { IImsRegistration registration = getRegistration(); if (registration != null) { registration.triggerDeregistration(reason); } else { Log.e(TAG + " [" + mSlotId + "]", "triggerDeregistration IImsRegistration is null"); } } 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 + " [" + mSlotId + "]", "MmTel does not support emergency over IMS, fallback" + " to CS."); return MmTelFeature.PROCESS_CALL_CSFB; } synchronized (mLock) { checkServiceIsReady(); return getServiceInterface(mBinder).shouldProcessCall(numbers); } } @Override protected Integer retrieveFeatureState() { if (mBinder != null) { try { return getServiceInterface(mBinder).getFeatureState(); } catch (RemoteException e) { // Status check failed, don't update cache } } return null; } @Override public void onFeatureCapabilitiesUpdated(long capabilities) { synchronized (mLock) { mSupportsEmergencyCalling = ((capabilities | ImsService.CAPABILITY_EMERGENCY_OVER_MMTEL) > 0); } } /** * Notifies the MmTelFeature of the enablement status of terminal based call waiting * * @param enabled indicates whether the user setting for call waiting is enabled or not. */ public void setTerminalBasedCallWaitingStatus(boolean enabled) throws RemoteException { synchronized (mLock) { checkServiceIsReady(); getServiceInterface(mBinder).setTerminalBasedCallWaitingStatus(enabled); } } private IImsMmTelFeature getServiceInterface(IBinder b) { return IImsMmTelFeature.Stub.asInterface(b); } }