| /* |
| * 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; |
| } |
| } |