blob: 5a9a13229dd0fb893cafbe1e877415a2287abac9 [file] [log] [blame]
/*
* Copyright (C) 2021 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.phone;
import static android.telephony.ims.ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED;
import static android.telephony.ims.ImsStateCallback.REASON_IMS_SERVICE_NOT_READY;
import static android.telephony.ims.ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED;
import static android.telephony.ims.ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE;
import static android.telephony.ims.ImsStateCallback.REASON_UNKNOWN_PERMANENT_ERROR;
import static android.telephony.ims.ImsStateCallback.REASON_UNKNOWN_TEMPORARY_ERROR;
import static android.telephony.ims.feature.ImsFeature.FEATURE_MMTEL;
import static android.telephony.ims.feature.ImsFeature.FEATURE_RCS;
import static android.telephony.ims.feature.ImsFeature.STATE_READY;
import static android.telephony.ims.feature.ImsFeature.STATE_UNAVAILABLE;
import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED;
import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED;
import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_NOT_READY;
import static com.android.ims.FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyRegistryManager;
import android.telephony.ims.feature.ImsFeature;
import android.util.Log;
import android.util.SparseArray;
import com.android.ims.FeatureConnector;
import com.android.ims.ImsManager;
import com.android.ims.RcsFeatureManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IImsStateCallback;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.ims.ImsResolver;
import com.android.internal.telephony.util.HandlerExecutor;
import com.android.services.telephony.rcs.RcsFeatureController;
import com.android.telephony.Rlog;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.Executor;
/**
* Implementation of the controller managing {@link ImsStateCallback}s
*/
public class ImsStateCallbackController {
private static final String TAG = "ImsStateCallbackController";
private static final boolean VDBG = false;
/**
* Create a FeatureConnector for this class to use to connect to an ImsManager.
*/
@VisibleForTesting
public interface MmTelFeatureConnectorFactory {
/**
* Create a FeatureConnector for this class to use to connect to an ImsManager.
* @param listener will receive ImsManager instance.
* @param executor that the Listener callbacks will be called on.
* @return A FeatureConnector
*/
FeatureConnector<ImsManager> create(Context context, int slotId,
String logPrefix, FeatureConnector.Listener<ImsManager> listener,
Executor executor);
}
/**
* Create a FeatureConnector for this class to use to connect to an RcsFeatureManager.
*/
@VisibleForTesting
public interface RcsFeatureConnectorFactory {
/**
* Create a FeatureConnector for this class to use to connect to an RcsFeatureManager.
* @param listener will receive RcsFeatureManager instance.
* @param executor that the Listener callbacks will be called on.
* @return A FeatureConnector
*/
FeatureConnector<RcsFeatureManager> create(Context context, int slotId,
FeatureConnector.Listener<RcsFeatureManager> listener,
Executor executor, String logPrefix);
}
/** The unavailable reason of ImsFeature is not initialized */
private static final int NOT_INITIALIZED = -1;
/** The ImsFeature is available. */
private static final int AVAILABLE = 0;
private static final int EVENT_SUB_CHANGED = 1;
private static final int EVENT_REGISTER_CALLBACK = 2;
private static final int EVENT_UNREGISTER_CALLBACK = 3;
private static final int EVENT_CARRIER_CONFIG_CHANGED = 4;
private static ImsStateCallbackController sInstance;
/**
* get the instance
*/
public static ImsStateCallbackController getInstance() {
synchronized (ImsStateCallbackController.class) {
return sInstance;
}
}
private final PhoneGlobals mApp;
private final Handler mHandler;
private final ImsResolver mImsResolver;
private final SparseArray<MmTelFeatureListener> mMmTelFeatureListeners = new SparseArray<>();
private final SparseArray<RcsFeatureListener> mRcsFeatureListeners = new SparseArray<>();
private final SubscriptionManager mSubscriptionManager;
private final TelephonyRegistryManager mTelephonyRegistryManager;
private MmTelFeatureConnectorFactory mMmTelFeatureFactory;
private RcsFeatureConnectorFactory mRcsFeatureFactory;
private HashMap<IImsStateCallback, CallbackWrapper> mWrappers = new HashMap<>();
private int mNumSlots;
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) {
return;
}
if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
Bundle bundle = intent.getExtras();
if (bundle == null) {
return;
}
int slotId = bundle.getInt(CarrierConfigManager.EXTRA_SLOT_INDEX,
SubscriptionManager.INVALID_PHONE_INDEX);
int subId = bundle.getInt(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
if (slotId <= SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
loge("onReceive ACTION_CARRIER_CONFIG_CHANGED invalid slotId");
return;
}
if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
loge("onReceive ACTION_CARRIER_CONFIG_CHANGED invalid subId");
//subscription changed will be notified by mSubChangedListener
return;
}
notifyCarrierConfigChanged(slotId);
}
}
};
private final SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener =
new SubscriptionManager.OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
if (!mHandler.hasMessages(EVENT_SUB_CHANGED)) {
mHandler.sendEmptyMessage(EVENT_SUB_CHANGED);
}
}
};
private final class MyHandler extends Handler {
MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
logv("handleMessage: " + msg);
switch (msg.what) {
case EVENT_SUB_CHANGED:
onSubChanged();
break;
case EVENT_REGISTER_CALLBACK:
onRegisterCallback((ImsStateCallbackController.CallbackWrapper) msg.obj);
break;
case EVENT_UNREGISTER_CALLBACK:
onUnregisterCallback((IImsStateCallback) msg.obj);
break;
case EVENT_CARRIER_CONFIG_CHANGED:
onCarrierConfigChanged(msg.arg1);
break;
default:
loge("Unhandled event " + msg.what);
}
}
}
private final class MmTelFeatureListener implements FeatureConnector.Listener<ImsManager> {
private FeatureConnector<ImsManager> mConnector;
private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private int mState = STATE_UNAVAILABLE;
private int mReason = REASON_IMS_SERVICE_DISCONNECTED;
/**
* Remember the last return of verifyImsMmTelConfigured().
* true means ImsResolver found an IMS package for FEATURE_MMTEL.
*/
private boolean mHasConfig = true;
private int mSlotId = -1;
private String mLogPrefix = "";
MmTelFeatureListener(int slotId) {
mLogPrefix = "[MMTEL, " + slotId + "] ";
logv(mLogPrefix + "create");
mConnector = mMmTelFeatureFactory.create(
mApp, slotId, TAG, this, new HandlerExecutor(mHandler));
mConnector.connect();
}
void setSubId(int subId) {
logv(mLogPrefix + "setSubId mSubId=" + mSubId + ", subId=" + subId);
if (mSubId == subId) return;
logd(mLogPrefix + "setSubId subId changed");
mSubId = subId;
}
void destroy() {
logv(mLogPrefix + "destroy");
mConnector.disconnect();
mConnector = null;
}
@Override
public void connectionReady(ImsManager manager) {
logd(mLogPrefix + "connectionReady");
mState = STATE_READY;
mReason = AVAILABLE;
mHasConfig = true;
onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
}
@Override
public void connectionUnavailable(int reason) {
logd(mLogPrefix + "connectionUnavailable reason=" + connectorReasonToString(reason));
reason = convertReasonType(reason);
if (mReason == reason) return;
mState = STATE_UNAVAILABLE;
/* If having no IMS package for MMTEL,
* dicard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
if (!mHasConfig && reason != REASON_NO_IMS_SERVICE_CONFIGURED) return;
mReason = reason;
onFeatureStateChange(mSubId, FEATURE_MMTEL, mState, mReason);
}
void notifyConfigChanged(boolean hasConfig) {
if (mHasConfig == hasConfig) return;
logd(mLogPrefix + "notifyConfigChanged " + hasConfig);
mHasConfig = hasConfig;
if (hasConfig) {
// REASON_NO_IMS_SERVICE_CONFIGURED is already reported to the clients,
// since there is no configuration of IMS package for MMTEL.
// Now, a carrier configuration change is notified and
// mHasConfig is changed from false to true.
// In this case, notify clients the reason, REASON_DISCONNCTED,
// to update the state.
if (mState != STATE_READY && mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
}
} else {
// FeatureConnector doesn't report UNAVAILABLE_REASON_IMS_UNSUPPORTED,
// so report the reason here.
connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
}
}
// called from onRegisterCallback
boolean notifyState(CallbackWrapper wrapper) {
logv(mLogPrefix + "notifyState subId=" + wrapper.mSubId);
return wrapper.notifyState(mSubId, FEATURE_MMTEL, mState, mReason);
}
}
private final class RcsFeatureListener implements FeatureConnector.Listener<RcsFeatureManager> {
private FeatureConnector<RcsFeatureManager> mConnector;
private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private int mState = STATE_UNAVAILABLE;
private int mReason = REASON_IMS_SERVICE_DISCONNECTED;
/**
* Remember the last return of verifyImsRcsConfigured().
* true means ImsResolver found an IMS package for FEATURE_RCS.
*/
private boolean mHasConfig = true;
private int mSlotId = -1;
private String mLogPrefix = "";
RcsFeatureListener(int slotId) {
mLogPrefix = "[RCS, " + slotId + "] ";
logv(mLogPrefix + "create");
mConnector = mRcsFeatureFactory.create(
mApp, slotId, this, new HandlerExecutor(mHandler), TAG);
mConnector.connect();
}
void setSubId(int subId) {
logv(mLogPrefix + "setSubId mSubId=" + mSubId + ", subId=" + subId);
if (mSubId == subId) return;
logd(mLogPrefix + "setSubId subId changed");
mSubId = subId;
}
void destroy() {
logv(mLogPrefix + "destroy");
mConnector.disconnect();
mConnector = null;
}
@Override
public void connectionReady(RcsFeatureManager manager) {
logd(mLogPrefix + "connectionReady");
mState = STATE_READY;
mReason = AVAILABLE;
mHasConfig = true;
onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
}
@Override
public void connectionUnavailable(int reason) {
logd(mLogPrefix + "connectionUnavailable reason=" + connectorReasonToString(reason));
reason = convertReasonType(reason);
if (mReason == reason) return;
mState = STATE_UNAVAILABLE;
/* If having no IMS package for RCS,
* dicard the reason except REASON_NO_IMS_SERVICE_CONFIGURED. */
if (!mHasConfig && reason != REASON_NO_IMS_SERVICE_CONFIGURED) return;
mReason = reason;
onFeatureStateChange(mSubId, FEATURE_RCS, mState, mReason);
}
void notifyConfigChanged(boolean hasConfig) {
if (mHasConfig == hasConfig) return;
logd(mLogPrefix + "notifyConfigChanged " + hasConfig);
mHasConfig = hasConfig;
if (hasConfig) {
// REASON_NO_IMS_SERVICE_CONFIGURED is already reported to the clients,
// since there is no configuration of IMS package for RCS.
// Now, a carrier configuration change is notified and
// mHasConfig is changed from false to true.
// In this case, notify clients the reason, REASON_DISCONNCTED,
// to update the state.
if (mState != STATE_READY && mReason == REASON_NO_IMS_SERVICE_CONFIGURED) {
connectionUnavailable(UNAVAILABLE_REASON_DISCONNECTED);
}
} else {
// FeatureConnector doesn't report UNAVAILABLE_REASON_IMS_UNSUPPORTED,
// so report the reason here.
connectionUnavailable(UNAVAILABLE_REASON_IMS_UNSUPPORTED);
}
}
// called from onRegisterCallback
boolean notifyState(CallbackWrapper wrapper) {
logv(mLogPrefix + "notifyState subId=" + wrapper.mSubId);
return wrapper.notifyState(mSubId, FEATURE_RCS, mState, mReason);
}
}
/**
* A wrapper class for the callback registered
*/
private static class CallbackWrapper {
private final int mSubId;
private final int mRequiredFeature;
private final IImsStateCallback mCallback;
CallbackWrapper(int subId, int feature, IImsStateCallback callback) {
mSubId = subId;
mRequiredFeature = feature;
mCallback = callback;
}
/**
* @return false when accessing callback binder throws an Exception.
* That means the callback binder is not valid any longer.
* The death of remote process can cause this.
* This instance shall be removed from the list.
*/
boolean notifyState(int subId, int feature, int state, int reason) {
logv("CallbackWrapper notifyState subId=" + subId
+ ", feature=" + ImsFeature.FEATURE_LOG_MAP.get(feature)
+ ", state=" + ImsFeature.STATE_LOG_MAP.get(state)
+ ", reason=" + imsStateReasonToString(reason));
try {
if (state == STATE_READY) {
mCallback.onAvailable();
} else {
mCallback.onUnavailable(reason);
}
} catch (Exception e) {
loge("CallbackWrapper notifyState e=" + e);
return false;
}
return true;
}
void notifyInactive() {
logv("CallbackWrapper notifyInactive subId=" + mSubId);
try {
mCallback.onUnavailable(REASON_SUBSCRIPTION_INACTIVE);
} catch (Exception e) {
// ignored
}
}
}
/**
* create an instance
*/
public static ImsStateCallbackController make(PhoneGlobals app, int numSlots) {
synchronized (ImsStateCallbackController.class) {
if (sInstance == null) {
logd("ImsStateCallbackController created");
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
sInstance = new ImsStateCallbackController(app, handlerThread.getLooper(), numSlots,
ImsManager::getConnector, RcsFeatureManager::getConnector,
ImsResolver.getInstance());
}
}
return sInstance;
}
@VisibleForTesting
public ImsStateCallbackController(PhoneGlobals app, Looper looper, int numSlots,
MmTelFeatureConnectorFactory mmTelFactory, RcsFeatureConnectorFactory rcsFactory,
ImsResolver imsResolver) {
mApp = app;
mHandler = new MyHandler(looper);
mImsResolver = imsResolver;
mSubscriptionManager = mApp.getSystemService(SubscriptionManager.class);
mTelephonyRegistryManager = mApp.getSystemService(TelephonyRegistryManager.class);
mMmTelFeatureFactory = mmTelFactory;
mRcsFeatureFactory = rcsFactory;
updateFeatureControllerSize(numSlots);
mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
mSubChangedListener, mSubChangedListener.getHandlerExecutor());
mApp.registerReceiver(mReceiver, new IntentFilter(
CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
onSubChanged();
}
/**
* Update the number of {@link RcsFeatureController}s that are created based on the number of
* active slots on the device.
*/
@VisibleForTesting
public void updateFeatureControllerSize(int newNumSlots) {
if (mNumSlots != newNumSlots) {
Log.d(TAG, "updateFeatures: oldSlots=" + mNumSlots
+ ", newNumSlots=" + newNumSlots);
if (mNumSlots < newNumSlots) {
for (int i = mNumSlots; i < newNumSlots; i++) {
MmTelFeatureListener m = new MmTelFeatureListener(i);
mMmTelFeatureListeners.put(i, m);
RcsFeatureListener r = new RcsFeatureListener(i);
mRcsFeatureListeners.put(i, r);
}
} else {
for (int i = (mNumSlots - 1); i > (newNumSlots - 1); i--) {
MmTelFeatureListener m = mMmTelFeatureListeners.get(i);
if (m != null) {
mMmTelFeatureListeners.remove(i);
m.destroy();
}
RcsFeatureListener r = mRcsFeatureListeners.get(i);
if (r != null) {
mRcsFeatureListeners.remove(i);
r.destroy();
}
}
}
}
mNumSlots = newNumSlots;
}
/**
* Dependencies for testing.
*/
@VisibleForTesting
public void onSubChanged() {
logv("onSubChanged size=" + mWrappers.size());
for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
MmTelFeatureListener l = mMmTelFeatureListeners.valueAt(i);
l.setSubId(getSubId(i));
}
for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
RcsFeatureListener l = mRcsFeatureListeners.valueAt(i);
l.setSubId(getSubId(i));
}
if (mWrappers.size() == 0) return;
ArrayList<IImsStateCallback> inactiveCallbacks = new ArrayList<>();
final int[] activeSubs = mSubscriptionManager.getActiveSubscriptionIdList();
logv("onSubChanged activeSubs=" + Arrays.toString(activeSubs));
// Remove callbacks for inactive subscriptions
for (IImsStateCallback cb : mWrappers.keySet()) {
CallbackWrapper wrapper = mWrappers.get(cb);
if (wrapper != null) {
if (!isActive(activeSubs, wrapper.mSubId)) {
// inactive subscription
inactiveCallbacks.add(cb);
}
} else {
// unexpected, remove it
inactiveCallbacks.add(cb);
}
}
removeInactiveCallbacks(inactiveCallbacks, "onSubChanged");
}
private void onFeatureStateChange(int subId, int feature, int state, int reason) {
logv("onFeatureStateChange subId=" + subId
+ ", feature=" + ImsFeature.FEATURE_LOG_MAP.get(feature)
+ ", state=" + ImsFeature.STATE_LOG_MAP.get(state)
+ ", reason=" + imsStateReasonToString(reason));
ArrayList<IImsStateCallback> inactiveCallbacks = new ArrayList<>();
mWrappers.values().forEach(wrapper -> {
if (subId == wrapper.mSubId
&& feature == wrapper.mRequiredFeature
&& !wrapper.notifyState(subId, feature, state, reason)) {
// callback has exception, remove it
inactiveCallbacks.add(wrapper.mCallback);
}
});
removeInactiveCallbacks(inactiveCallbacks, "onFeatureStateChange");
}
private void onRegisterCallback(CallbackWrapper wrapper) {
if (wrapper == null) return;
logv("onRegisterCallback before size=" + mWrappers.size());
logv("onRegisterCallback subId=" + wrapper.mSubId
+ ", feature=" + wrapper.mRequiredFeature);
// Not sure the following case can happen or not:
// step1) Subscription changed
// step2) ImsStateCallbackController not processed onSubChanged yet
// step3) Client registers with a strange subId
// The validity of the subId is checked PhoneInterfaceManager#registerImsStateCallback.
// So, register the wrapper here before trying to notifyState.
// TODO: implement the recovery for this case, notifying the current reson, in onSubChanged
mWrappers.put(wrapper.mCallback, wrapper);
if (wrapper.mRequiredFeature == FEATURE_MMTEL) {
for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
MmTelFeatureListener l = mMmTelFeatureListeners.valueAt(i);
if (l.mSubId == wrapper.mSubId
&& !l.notifyState(wrapper)) {
mWrappers.remove(wrapper.mCallback);
break;
}
}
} else if (wrapper.mRequiredFeature == FEATURE_RCS) {
for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
RcsFeatureListener l = mRcsFeatureListeners.valueAt(i);
if (l.mSubId == wrapper.mSubId
&& !l.notifyState(wrapper)) {
mWrappers.remove(wrapper.mCallback);
break;
}
}
}
logv("onRegisterCallback after size=" + mWrappers.size());
}
private void onUnregisterCallback(IImsStateCallback cb) {
if (cb == null) return;
mWrappers.remove(cb);
}
private void onCarrierConfigChanged(int slotId) {
if (slotId >= mNumSlots) {
logd("onCarrierConfigChanged invalid slotId "
+ slotId + ", mNumSlots=" + mNumSlots);
return;
}
logd("onCarrierConfigChanged slotId=" + slotId);
boolean hasConfig = verifyImsMmTelConfigured(slotId);
if (slotId < mMmTelFeatureListeners.size()) {
MmTelFeatureListener listener = mMmTelFeatureListeners.valueAt(slotId);
listener.notifyConfigChanged(hasConfig);
}
hasConfig = verifyImsRcsConfigured(slotId);
if (slotId < mRcsFeatureListeners.size()) {
RcsFeatureListener listener = mRcsFeatureListeners.valueAt(slotId);
listener.notifyConfigChanged(hasConfig);
}
}
/**
* Notifies carrier configuration has changed.
*/
@VisibleForTesting
public void notifyCarrierConfigChanged(int slotId) {
logv("notifyCarrierConfigChanged slotId=" + slotId);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_CARRIER_CONFIG_CHANGED, slotId, 0));
}
/**
* Register IImsStateCallback
*
* @param feature for which state is changed, ImsFeature.FEATURE_*
*/
public void registerImsStateCallback(int subId, int feature, IImsStateCallback cb) {
logv("registerImsStateCallback subId=" + subId + ", feature=" + feature);
CallbackWrapper wrapper = new CallbackWrapper(subId, feature, cb);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_CALLBACK, wrapper));
}
/**
* Unegister previously registered callback
*/
public void unregisterImsStateCallback(IImsStateCallback cb) {
logv("unregisterImsStateCallback");
mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_CALLBACK, cb));
}
private void removeInactiveCallbacks(
ArrayList<IImsStateCallback> inactiveCallbacks, String message) {
if (inactiveCallbacks == null || inactiveCallbacks.size() == 0) return;
logv("removeInactiveCallbacks size=" + inactiveCallbacks.size() + " from " + message);
for (IImsStateCallback cb : inactiveCallbacks) {
CallbackWrapper wrapper = mWrappers.get(cb);
if (wrapper != null) {
// Send the reason REASON_SUBSCRIPTION_INACTIVE to the client
wrapper.notifyInactive();
mWrappers.remove(cb);
}
}
inactiveCallbacks.clear();
}
private int getSubId(int slotId) {
Phone phone = mPhoneFactoryProxy.getPhone(slotId);
if (phone != null) return phone.getSubId();
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
private static boolean isActive(final int[] activeSubs, int subId) {
for (int i : activeSubs) {
if (i == subId) return true;
}
return false;
}
private static int convertReasonType(int reason) {
switch(reason) {
case UNAVAILABLE_REASON_NOT_READY:
return REASON_IMS_SERVICE_NOT_READY;
case UNAVAILABLE_REASON_IMS_UNSUPPORTED:
return REASON_NO_IMS_SERVICE_CONFIGURED;
default:
break;
}
return REASON_IMS_SERVICE_DISCONNECTED;
}
private boolean verifyImsMmTelConfigured(int slotId) {
boolean ret = false;
if (mImsResolver == null) {
loge("verifyImsMmTelConfigured mImsResolver is null");
} else {
ret = mImsResolver.isImsServiceConfiguredForFeature(slotId, FEATURE_MMTEL);
}
logv("verifyImsMmTelConfigured slotId=" + slotId + ", ret=" + ret);
return ret;
}
private boolean verifyImsRcsConfigured(int slotId) {
boolean ret = false;
if (mImsResolver == null) {
loge("verifyImsRcsConfigured mImsResolver is null");
} else {
ret = mImsResolver.isImsServiceConfiguredForFeature(slotId, FEATURE_RCS);
}
logv("verifyImsRcsConfigured slotId=" + slotId + ", ret=" + ret);
return ret;
}
private static String connectorReasonToString(int reason) {
switch(reason) {
case UNAVAILABLE_REASON_DISCONNECTED:
return "DISCONNECTED";
case UNAVAILABLE_REASON_NOT_READY:
return "NOT_READY";
case UNAVAILABLE_REASON_IMS_UNSUPPORTED:
return "IMS_UNSUPPORTED";
case UNAVAILABLE_REASON_SERVER_UNAVAILABLE:
return "SERVER_UNAVAILABLE";
default:
break;
}
return "";
}
private static String imsStateReasonToString(int reason) {
switch(reason) {
case REASON_UNKNOWN_TEMPORARY_ERROR:
return "UNKNOWN_TEMPORARY_ERROR";
case REASON_UNKNOWN_PERMANENT_ERROR:
return "UNKNOWN_PERMANENT_ERROR";
case REASON_IMS_SERVICE_DISCONNECTED:
return "IMS_SERVICE_DISCONNECTED";
case REASON_NO_IMS_SERVICE_CONFIGURED:
return "NO_IMS_SERVICE_CONFIGURED";
case REASON_SUBSCRIPTION_INACTIVE:
return "SUBSCRIPTION_INACTIVE";
case REASON_IMS_SERVICE_NOT_READY:
return "IMS_SERVICE_NOT_READY";
default:
break;
}
return "";
}
/**
* PhoneFactory Dependencies for testing.
*/
@VisibleForTesting
public interface PhoneFactoryProxy {
/**
* Override getPhone for testing.
*/
Phone getPhone(int index);
}
private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
@Override
public Phone getPhone(int index) {
return PhoneFactory.getPhone(index);
}
};
private void release() {
logv("release");
mTelephonyRegistryManager.removeOnSubscriptionsChangedListener(mSubChangedListener);
mApp.unregisterReceiver(mReceiver);
for (int i = 0; i < mMmTelFeatureListeners.size(); i++) {
mMmTelFeatureListeners.valueAt(i).destroy();
}
mMmTelFeatureListeners.clear();
for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
mRcsFeatureListeners.valueAt(i).destroy();
}
mRcsFeatureListeners.clear();
}
/**
* destroy the instance
*/
@VisibleForTesting
public void destroy() {
logv("destroy it");
release();
mHandler.getLooper().quit();
}
/**
* get the handler
*/
@VisibleForTesting
public Handler getHandler() {
return mHandler;
}
/**
* Determine whether the callback is registered or not
*/
@VisibleForTesting
public boolean isRegistered(IImsStateCallback cb) {
if (cb == null) return false;
return mWrappers.containsKey(cb);
}
private static void logv(String msg) {
if (VDBG) {
Rlog.d(TAG, msg);
}
}
private static void logd(String msg) {
Rlog.d(TAG, msg);
}
private static void loge(String msg) {
Rlog.e(TAG, msg);
}
}