blob: 8eec9dbce4213d3d987d733bbe4e0954738608e2 [file] [log] [blame]
/*
* Copyright (C) 2018 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.internal.telephony.ims;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Message;
import android.os.RemoteException;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsCallProfile;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.feature.CapabilityChangeRequest;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;
import com.android.ims.ImsConfigListener;
import com.android.ims.ImsManager;
import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsConfig;
import com.android.ims.internal.IImsEcbm;
import com.android.ims.internal.IImsMultiEndpoint;
import com.android.ims.internal.IImsRegistrationListener;
import com.android.ims.internal.IImsUt;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class MmTelFeatureCompatAdapter extends MmTelFeature {
private static final String TAG = "MmTelFeatureCompat";
public static final String ACTION_IMS_INCOMING_CALL = "com.android.ims.IMS_INCOMING_CALL";
private static final int WAIT_TIMEOUT_MS = 2000;
private final MmTelInterfaceAdapter mCompatFeature;
private ImsRegistrationCompatAdapter mRegCompatAdapter;
private int mSessionId = -1;
private static final Map<Integer, Integer> REG_TECH_TO_NET_TYPE = new HashMap<>(2);
static {
REG_TECH_TO_NET_TYPE.put(ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
TelephonyManager.NETWORK_TYPE_LTE);
REG_TECH_TO_NET_TYPE.put(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
TelephonyManager.NETWORK_TYPE_IWLAN);
}
// Feature Type for compatibility with old "feature" updates
public static final int FEATURE_TYPE_UNKNOWN = -1;
public static final int FEATURE_TYPE_VOICE_OVER_LTE = 0;
public static final int FEATURE_TYPE_VIDEO_OVER_LTE = 1;
public static final int FEATURE_TYPE_VOICE_OVER_WIFI = 2;
public static final int FEATURE_TYPE_VIDEO_OVER_WIFI = 3;
public static final int FEATURE_TYPE_UT_OVER_LTE = 4;
public static final int FEATURE_TYPE_UT_OVER_WIFI = 5;
public static final int FEATURE_UNKNOWN = -1;
public static final int FEATURE_DISABLED = 0;
public static final int FEATURE_ENABLED = 1;
private static class ConfigListener extends ImsConfigListener.Stub {
private final int mCapability;
private final int mTech;
private final CountDownLatch mLatch;
public ConfigListener(int capability, int tech, CountDownLatch latch) {
mCapability = capability;
mTech = tech;
mLatch = latch;
}
@Override
public void onGetFeatureResponse(int feature, int network, int value, int status)
throws RemoteException {
if (feature == mCapability && network == mTech) {
mLatch.countDown();
getFeatureValueReceived(value);
} else {
Log.i(TAG, "onGetFeatureResponse: response different than requested: feature="
+ feature + " and network=" + network);
}
}
@Override
public void onSetFeatureResponse(int feature, int network, int value, int status)
throws RemoteException {
if (feature == mCapability && network == mTech) {
mLatch.countDown();
setFeatureValueReceived(value);
} else {
Log.i(TAG, "onSetFeatureResponse: response different than requested: feature="
+ feature + " and network=" + network);
}
}
@Override
public void onGetVideoQuality(int status, int quality) throws RemoteException {
}
@Override
public void onSetVideoQuality(int status) throws RemoteException {
}
public void getFeatureValueReceived(int value) {
}
public void setFeatureValueReceived(int value) {
}
}
// Trampolines "old" listener events to the new interface.
private final IImsRegistrationListener mListener = new IImsRegistrationListener.Stub() {
@Override
public void registrationConnected() throws RemoteException {
// Implemented in the Registration Adapter
}
@Override
public void registrationProgressing() throws RemoteException {
// Implemented in the Registration Adapter
}
@Override
public void registrationConnectedWithRadioTech(int imsRadioTech) throws RemoteException {
// Implemented in the Registration Adapter
}
@Override
public void registrationProgressingWithRadioTech(int imsRadioTech) throws RemoteException {
// Implemented in the Registration Adapter
}
@Override
public void registrationDisconnected(ImsReasonInfo imsReasonInfo) throws RemoteException {
// Implemented in the Registration Adapter
}
@Override
public void registrationResumed() throws RemoteException {
// Don't care
}
@Override
public void registrationSuspended() throws RemoteException {
// Don't care
}
@Override
public void registrationServiceCapabilityChanged(int serviceClass, int event)
throws RemoteException {
// Don't care
}
@Override
public void registrationFeatureCapabilityChanged(int serviceClass, int[] enabledFeatures,
int[] disabledFeatures) throws RemoteException {
notifyCapabilitiesStatusChanged(convertCapabilities(enabledFeatures));
}
@Override
public void voiceMessageCountUpdate(int count) throws RemoteException {
notifyVoiceMessageCountUpdate(count);
}
@Override
public void registrationAssociatedUriChanged(Uri[] uris) throws RemoteException {
// Implemented in the Registration Adapter
}
@Override
public void registrationChangeFailed(int targetAccessTech, ImsReasonInfo imsReasonInfo)
throws RemoteException {
// Implemented in the Registration Adapter
}
};
/**
* Stub implementation of the "old" Registration listener interface that provides no
* functionality. Instead, it is used to ensure compatibility with older devices that require
* a listener on startSession. The actual Registration Listener Interface is added separately
* in ImsRegistration.
*/
private class ImsRegistrationListenerBase extends IImsRegistrationListener.Stub {
@Override
public void registrationConnected() throws RemoteException {
}
@Override
public void registrationProgressing() throws RemoteException {
}
@Override
public void registrationConnectedWithRadioTech(int imsRadioTech) throws RemoteException {
}
@Override
public void registrationProgressingWithRadioTech(int imsRadioTech) throws RemoteException {
}
@Override
public void registrationDisconnected(ImsReasonInfo imsReasonInfo) throws RemoteException {
}
@Override
public void registrationResumed() throws RemoteException {
}
@Override
public void registrationSuspended() throws RemoteException {
}
@Override
public void registrationServiceCapabilityChanged(int serviceClass, int event)
throws RemoteException {
}
@Override
public void registrationFeatureCapabilityChanged(int serviceClass, int[] enabledFeatures,
int[] disabledFeatures) throws RemoteException {
}
@Override
public void voiceMessageCountUpdate(int count) throws RemoteException {
}
@Override
public void registrationAssociatedUriChanged(Uri[] uris) throws RemoteException {
}
@Override
public void registrationChangeFailed(int targetAccessTech, ImsReasonInfo imsReasonInfo)
throws RemoteException {
}
}
// Handle Incoming Call as PendingIntent, the old method
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceive");
if (intent.getAction().equals(ACTION_IMS_INCOMING_CALL)) {
Log.i(TAG, "onReceive : incoming call intent.");
String callId = intent.getStringExtra("android:imsCallID");
try {
IImsCallSession session = mCompatFeature.getPendingCallSession(mSessionId,
callId);
notifyIncomingCallSession(session, intent.getExtras());
} catch (RemoteException e) {
Log.w(TAG, "onReceive: Couldn't get Incoming call session.");
}
}
}
};
public MmTelFeatureCompatAdapter(Context context, int slotId,
MmTelInterfaceAdapter compatFeature) {
initialize(context, slotId);
mCompatFeature = compatFeature;
}
@Override
public boolean queryCapabilityConfiguration(int capability, int radioTech) {
int capConverted = convertCapability(capability, radioTech);
// Wait for the result from the ImsService
CountDownLatch latch = new CountDownLatch(1);
final int[] returnValue = new int[1];
returnValue[0] = FEATURE_UNKNOWN;
int regTech = REG_TECH_TO_NET_TYPE.getOrDefault(radioTech,
ImsRegistrationImplBase.REGISTRATION_TECH_NONE);
try {
mCompatFeature.getConfigInterface().getFeatureValue(capConverted, regTech,
new ConfigListener(capConverted, regTech, latch) {
@Override
public void getFeatureValueReceived(int value) {
returnValue[0] = value;
}
});
} catch (RemoteException e) {
Log.w(TAG, "queryCapabilityConfiguration");
}
try {
latch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Log.w(TAG, "queryCapabilityConfiguration - error waiting: " + e.getMessage());
}
return returnValue[0] == FEATURE_ENABLED;
}
@Override
public void changeEnabledCapabilities(CapabilityChangeRequest request,
CapabilityCallbackProxy c) {
if (request == null) {
return;
}
try {
IImsConfig imsConfig = mCompatFeature.getConfigInterface();
// Disable Capabilities
for (CapabilityChangeRequest.CapabilityPair cap : request.getCapabilitiesToDisable()) {
CountDownLatch latch = new CountDownLatch(1);
int capConverted = convertCapability(cap.getCapability(), cap.getRadioTech());
int radioTechConverted = REG_TECH_TO_NET_TYPE.getOrDefault(cap.getRadioTech(),
ImsRegistrationImplBase.REGISTRATION_TECH_NONE);
Log.i(TAG, "changeEnabledCapabilities - cap: " + capConverted + " radioTech: "
+ radioTechConverted + " disabled");
imsConfig.setFeatureValue(capConverted, radioTechConverted, FEATURE_DISABLED,
new ConfigListener(capConverted, radioTechConverted, latch) {
@Override
public void setFeatureValueReceived(int value) {
if (value != FEATURE_DISABLED) {
if (c == null) {
return;
}
c.onChangeCapabilityConfigurationError(cap.getCapability(),
cap.getRadioTech(), CAPABILITY_ERROR_GENERIC);
}
Log.i(TAG, "changeEnabledCapabilities - setFeatureValueReceived"
+ " with value " + value);
}
});
latch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
// Enable Capabilities
for (CapabilityChangeRequest.CapabilityPair cap : request.getCapabilitiesToEnable()) {
CountDownLatch latch = new CountDownLatch(1);
int capConverted = convertCapability(cap.getCapability(), cap.getRadioTech());
int radioTechConverted = REG_TECH_TO_NET_TYPE.getOrDefault(cap.getRadioTech(),
ImsRegistrationImplBase.REGISTRATION_TECH_NONE);
Log.i(TAG, "changeEnabledCapabilities - cap: " + capConverted + " radioTech: "
+ radioTechConverted + " enabled");
imsConfig.setFeatureValue(capConverted, radioTechConverted, FEATURE_ENABLED,
new ConfigListener(capConverted, radioTechConverted, latch) {
@Override
public void setFeatureValueReceived(int value) {
if (value != FEATURE_ENABLED) {
if (c == null) {
return;
}
c.onChangeCapabilityConfigurationError(cap.getCapability(),
cap.getRadioTech(), CAPABILITY_ERROR_GENERIC);
}
Log.i(TAG, "changeEnabledCapabilities - setFeatureValueReceived"
+ " with value " + value);
}
});
latch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
} catch (RemoteException | InterruptedException e) {
Log.w(TAG, "changeEnabledCapabilities: Error processing: " + e.getMessage());
}
}
@Override
public ImsCallProfile createCallProfile(int callSessionType, int callType) {
try {
return mCompatFeature.createCallProfile(mSessionId, callSessionType, callType);
} catch (RemoteException e) {
throw new RuntimeException(e.getMessage());
}
}
@Override
public IImsCallSession createCallSessionInterface(ImsCallProfile profile)
throws RemoteException {
return mCompatFeature.createCallSession(mSessionId, profile);
}
@Override
public IImsUt getUtInterface() throws RemoteException {
return mCompatFeature.getUtInterface();
}
@Override
public IImsEcbm getEcbmInterface() throws RemoteException {
return mCompatFeature.getEcbmInterface();
}
@Override
public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
return mCompatFeature.getMultiEndpointInterface();
}
@Override
public int getFeatureState() {
try {
return mCompatFeature.getFeatureState();
} catch (RemoteException e) {
throw new RuntimeException(e.getMessage());
}
}
@Override
public void setUiTtyMode(int mode, Message onCompleteMessage) {
try {
mCompatFeature.setUiTTYMode(mode, onCompleteMessage);
} catch (RemoteException e) {
throw new RuntimeException(e.getMessage());
}
}
@Override
public void onFeatureRemoved() {
mContext.unregisterReceiver(mReceiver);
try {
mCompatFeature.endSession(mSessionId);
mCompatFeature.removeRegistrationListener(mListener);
if (mRegCompatAdapter != null) {
mCompatFeature.removeRegistrationListener(
mRegCompatAdapter.getRegistrationListener());
}
} catch (RemoteException e) {
Log.w(TAG, "onFeatureRemoved: Couldn't end session: " + e.getMessage());
}
}
@Override
public void onFeatureReady() {
Log.i(TAG, "onFeatureReady called!");
// This gets called when MmTelFeature.setListener is called. We need to use this time to
// call openSession on the old MMTelFeature implementation.
IntentFilter intentFilter = new IntentFilter(ImsManager.ACTION_IMS_INCOMING_CALL);
mContext.registerReceiver(mReceiver, intentFilter);
try {
mSessionId = mCompatFeature.startSession(createIncomingCallPendingIntent(),
new ImsRegistrationListenerBase());
mCompatFeature.addRegistrationListener(mListener);
mCompatFeature.addRegistrationListener(mRegCompatAdapter.getRegistrationListener());
} catch (RemoteException e) {
Log.e(TAG, "Couldn't start compat feature: " + e.getMessage());
}
}
public void enableIms() throws RemoteException {
mCompatFeature.turnOnIms();
}
public void disableIms() throws RemoteException {
mCompatFeature.turnOffIms();
}
public IImsConfig getOldConfigInterface() {
try {
return mCompatFeature.getConfigInterface();
} catch (RemoteException e) {
Log.w(TAG, "getOldConfigInterface(): " + e.getMessage());
return null;
}
}
public void addRegistrationAdapter(ImsRegistrationCompatAdapter regCompat)
throws RemoteException {
mRegCompatAdapter = regCompat;
}
private MmTelCapabilities convertCapabilities(int[] enabledFeatures) {
boolean[] featuresEnabled = new boolean[enabledFeatures.length];
for (int i = FEATURE_TYPE_VOICE_OVER_LTE; i <= FEATURE_TYPE_UT_OVER_WIFI
&& i < enabledFeatures.length; i++) {
if (enabledFeatures[i] == i) {
featuresEnabled[i] = true;
} else if (enabledFeatures[i] == FEATURE_TYPE_UNKNOWN) {
// FEATURE_TYPE_UNKNOWN indicates that a feature is disabled.
featuresEnabled[i] = false;
}
}
MmTelCapabilities capabilities = new MmTelCapabilities();
if (featuresEnabled[FEATURE_TYPE_VOICE_OVER_LTE]
|| featuresEnabled[FEATURE_TYPE_VOICE_OVER_WIFI]) {
// voice is enabled
capabilities.addCapabilities(MmTelCapabilities.CAPABILITY_TYPE_VOICE);
}
if (featuresEnabled[FEATURE_TYPE_VIDEO_OVER_LTE]
|| featuresEnabled[FEATURE_TYPE_VIDEO_OVER_WIFI]) {
// video is enabled
capabilities.addCapabilities(MmTelCapabilities.CAPABILITY_TYPE_VIDEO);
}
if (featuresEnabled[FEATURE_TYPE_UT_OVER_LTE]
|| featuresEnabled[FEATURE_TYPE_UT_OVER_WIFI]) {
// ut is enabled
capabilities.addCapabilities(MmTelCapabilities.CAPABILITY_TYPE_UT);
}
Log.i(TAG, "convertCapabilities - capabilities: " + capabilities);
return capabilities;
}
private PendingIntent createIncomingCallPendingIntent() {
Intent intent = new Intent(ImsManager.ACTION_IMS_INCOMING_CALL);
intent.setPackage(TelephonyManager.PHONE_PROCESS_NAME);
return PendingIntent.getBroadcast(mContext, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
private int convertCapability(int capability, int radioTech) {
int capConverted = FEATURE_TYPE_UNKNOWN;
if (radioTech == ImsRegistrationImplBase.REGISTRATION_TECH_LTE) {
switch (capability) {
case MmTelCapabilities.CAPABILITY_TYPE_VOICE:
capConverted = FEATURE_TYPE_VOICE_OVER_LTE;
break;
case MmTelCapabilities.CAPABILITY_TYPE_VIDEO:
capConverted = FEATURE_TYPE_VIDEO_OVER_LTE;
break;
case MmTelCapabilities.CAPABILITY_TYPE_UT:
capConverted = FEATURE_TYPE_UT_OVER_LTE;
break;
}
} else if (radioTech == ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN) {
switch (capability) {
case MmTelCapabilities.CAPABILITY_TYPE_VOICE:
capConverted = FEATURE_TYPE_VOICE_OVER_WIFI;
break;
case MmTelCapabilities.CAPABILITY_TYPE_VIDEO:
capConverted = FEATURE_TYPE_VIDEO_OVER_WIFI;
break;
case MmTelCapabilities.CAPABILITY_TYPE_UT:
capConverted = FEATURE_TYPE_UT_OVER_WIFI;
break;
}
}
return capConverted;
}
}