blob: 79310ef11edc4d779f6ede86843bcf8aba3b9dda [file] [log] [blame]
/*
* Copyright 2020 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 android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyRegistryManager;
import android.telephony.ims.ProvisioningManager;
import android.telephony.ims.RcsConfig;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IRcsConfigCallback;
import android.telephony.ims.feature.ImsFeature;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.CollectionUtils;
import com.android.telephony.Rlog;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
/**
* Class to monitor RCS Provisioning Status
*/
public class RcsProvisioningMonitor {
private static final String TAG = "RcsProvisioningMonitor";
private static final boolean DBG = Build.IS_ENG;
private static final int EVENT_SUB_CHANGED = 1;
private static final int EVENT_DMA_CHANGED = 2;
private static final int EVENT_CC_CHANGED = 3;
private static final int EVENT_CONFIG_RECEIVED = 4;
private static final int EVENT_RECONFIG_REQUEST = 5;
private static final int EVENT_DEVICE_CONFIG_OVERRIDE = 6;
private static final int EVENT_CARRIER_CONFIG_OVERRIDE = 7;
private final PhoneGlobals mPhone;
private final Handler mHandler;
// Cache the RCS provsioning info and related sub id
private final ConcurrentHashMap<Integer, RcsProvisioningInfo> mRcsProvisioningInfos =
new ConcurrentHashMap<>();
private Boolean mDeviceSingleRegistrationEnabledOverride;
private final HashMap<Integer, Boolean> mCarrierSingleRegistrationEnabledOverride =
new HashMap<>();
private String mDmaPackageName;
private final CarrierConfigManager mCarrierConfigManager;
private final DmaChangedListener mDmaChangedListener;
private final SubscriptionManager mSubscriptionManager;
private final TelephonyRegistryManager mTelephonyRegistryManager;
private final RoleManagerAdapter mRoleManager;
private static RcsProvisioningMonitor sInstance;
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 BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(
intent.getAction())) {
int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
logv("Carrier-config changed for sub : " + subId);
if (SubscriptionManager.isValidSubscriptionId(subId)
&& !mHandler.hasMessages(EVENT_CC_CHANGED)) {
mHandler.sendEmptyMessage(EVENT_CC_CHANGED);
}
}
}
};
private final class DmaChangedListener implements OnRoleHoldersChangedListener {
@Override
public void onRoleHoldersChanged(String role, UserHandle user) {
if (RoleManager.ROLE_SMS.equals(role)) {
logv("default messaging application changed.");
String packageName = getDmaPackageName();
mHandler.sendEmptyMessage(EVENT_DMA_CHANGED);
}
}
public void register() {
try {
mRoleManager.addOnRoleHoldersChangedListenerAsUser(
mPhone.getMainExecutor(), this, UserHandle.SYSTEM);
} catch (RuntimeException e) {
loge("Could not register dma change listener due to " + e);
}
}
public void unregister() {
try {
mRoleManager.removeOnRoleHoldersChangedListenerAsUser(this, UserHandle.SYSTEM);
} catch (RuntimeException e) {
loge("Could not unregister dma change listener due to " + e);
}
}
}
private final class MyHandler extends Handler {
MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_SUB_CHANGED:
onSubChanged();
break;
case EVENT_DMA_CHANGED:
onDefaultMessagingApplicationChanged();
break;
case EVENT_CC_CHANGED:
onCarrierConfigChange();
break;
case EVENT_CONFIG_RECEIVED:
onConfigReceived(msg.arg1, (byte[]) msg.obj, msg.arg2 == 1);
break;
case EVENT_RECONFIG_REQUEST:
onReconfigRequest(msg.arg1);
break;
case EVENT_DEVICE_CONFIG_OVERRIDE:
Boolean deviceEnabled = (Boolean) msg.obj;
if (!booleanEquals(deviceEnabled, mDeviceSingleRegistrationEnabledOverride)) {
mDeviceSingleRegistrationEnabledOverride = deviceEnabled;
onCarrierConfigChange();
}
break;
case EVENT_CARRIER_CONFIG_OVERRIDE:
Boolean carrierEnabledOverride = (Boolean) msg.obj;
Boolean carrierEnabled = mCarrierSingleRegistrationEnabledOverride.put(
msg.arg1, carrierEnabledOverride);
if (!booleanEquals(carrierEnabledOverride, carrierEnabled)) {
onCarrierConfigChange();
}
break;
default:
loge("Unhandled event " + msg.what);
}
}
}
private final class RcsProvisioningInfo {
private int mSubId;
private volatile int mSingleRegistrationCapability;
private volatile byte[] mConfig;
private HashSet<IRcsConfigCallback> mRcsConfigCallbacks;
RcsProvisioningInfo(int subId, int singleRegistrationCapability, byte[] config) {
mSubId = subId;
mSingleRegistrationCapability = singleRegistrationCapability;
mConfig = config;
mRcsConfigCallbacks = new HashSet<>();
}
void setSingleRegistrationCapability(int singleRegistrationCapability) {
mSingleRegistrationCapability = singleRegistrationCapability;
}
int getSingleRegistrationCapability() {
return mSingleRegistrationCapability;
}
void setConfig(byte[] config) {
mConfig = config;
}
byte[] getConfig() {
return mConfig;
}
boolean addRcsConfigCallback(IRcsConfigCallback cb) {
IImsConfig imsConfig = getIImsConfig(mSubId, ImsFeature.FEATURE_RCS);
if (imsConfig == null) {
logd("fail to addRcsConfigCallback as imsConfig is null");
return false;
}
synchronized (mRcsConfigCallbacks) {
try {
imsConfig.addRcsConfigCallback(cb);
} catch (RemoteException e) {
loge("fail to addRcsConfigCallback due to " + e);
return false;
}
mRcsConfigCallbacks.add(cb);
}
return true;
}
boolean removeRcsConfigCallback(IRcsConfigCallback cb) {
boolean result = true;
IImsConfig imsConfig = getIImsConfig(mSubId, ImsFeature.FEATURE_RCS);
synchronized (mRcsConfigCallbacks) {
if (imsConfig != null) {
try {
imsConfig.removeRcsConfigCallback(cb);
} catch (RemoteException e) {
loge("fail to removeRcsConfigCallback due to " + e);
}
} else {
// Return false but continue to remove the callback
result = false;
}
try {
cb.onRemoved();
} catch (RemoteException e) {
logd("Failed to notify onRemoved due to dead binder of " + cb);
}
mRcsConfigCallbacks.remove(cb);
}
return result;
}
void clear() {
setConfig(null);
synchronized (mRcsConfigCallbacks) {
IImsConfig imsConfig = getIImsConfig(mSubId, ImsFeature.FEATURE_RCS);
Iterator<IRcsConfigCallback> it = mRcsConfigCallbacks.iterator();
while (it.hasNext()) {
IRcsConfigCallback cb = it.next();
if (imsConfig != null) {
try {
imsConfig.removeRcsConfigCallback(cb);
} catch (RemoteException e) {
loge("fail to removeRcsConfigCallback due to " + e);
}
}
try {
cb.onRemoved();
} catch (RemoteException e) {
logd("Failed to notify onRemoved due to dead binder of " + cb);
}
it.remove();
}
}
}
}
@VisibleForTesting
public RcsProvisioningMonitor(PhoneGlobals app, Looper looper, RoleManagerAdapter roleManager) {
mPhone = app;
mHandler = new MyHandler(looper);
mCarrierConfigManager = mPhone.getSystemService(CarrierConfigManager.class);
mSubscriptionManager = mPhone.getSystemService(SubscriptionManager.class);
mTelephonyRegistryManager = mPhone.getSystemService(TelephonyRegistryManager.class);
mRoleManager = roleManager;
mDmaPackageName = getDmaPackageName();
logv("DMA is " + mDmaPackageName);
IntentFilter filter = new IntentFilter();
filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
mPhone.registerReceiver(mReceiver, filter);
mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
mSubChangedListener, mSubChangedListener.getHandlerExecutor());
mDmaChangedListener = new DmaChangedListener();
mDmaChangedListener.register();
//initialize configs for all active sub
onSubChanged();
}
/**
* create an instance
*/
public static RcsProvisioningMonitor make(PhoneGlobals app) {
if (sInstance == null) {
logd("RcsProvisioningMonitor created.");
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
sInstance = new RcsProvisioningMonitor(app, handlerThread.getLooper(),
new RoleManagerAdapterImpl(app));
}
return sInstance;
}
/**
* get the instance
*/
public static RcsProvisioningMonitor getInstance() {
return sInstance;
}
/**
* destroy the instance
*/
@VisibleForTesting
public void destroy() {
logd("destroy it.");
mDmaChangedListener.unregister();
mTelephonyRegistryManager.removeOnSubscriptionsChangedListener(mSubChangedListener);
mPhone.unregisterReceiver(mReceiver);
mHandler.getLooper().quit();
}
/**
* get the handler
*/
@VisibleForTesting
public Handler getHandler() {
return mHandler;
}
/**
* Gets the config for a subscription
*/
@VisibleForTesting
public byte[] getConfig(int subId) {
if (mRcsProvisioningInfos.containsKey(subId)) {
return mRcsProvisioningInfos.get(subId).getConfig();
}
return null;
}
/**
* Returns whether Rcs Volte single registration is enabled for the sub.
*/
public boolean isRcsVolteSingleRegistrationEnabled(int subId) {
if (mRcsProvisioningInfos.containsKey(subId)) {
return mRcsProvisioningInfos.get(subId).getSingleRegistrationCapability()
== ProvisioningManager.STATUS_CAPABLE;
}
return false;
}
/**
* Called when the new rcs config is received
*/
public void updateConfig(int subId, byte[] config, boolean isCompressed) {
mHandler.sendMessage(mHandler.obtainMessage(
EVENT_CONFIG_RECEIVED, subId, isCompressed ? 1 : 0, config));
}
/**
* Called when the application needs rcs re-config
*/
public void requestReconfig(int subId) {
mHandler.sendMessage(mHandler.obtainMessage(EVENT_RECONFIG_REQUEST, subId, 0));
}
/**
* Called when the application registers rcs provisioning changed callback
*/
public boolean registerRcsProvisioningChangedCallback(int subId, IRcsConfigCallback cb) {
RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
// should not happen in normal case
if (info == null) {
logd("fail to register rcs provisioning changed due to subscription unavailable");
return false;
}
return info.addRcsConfigCallback(cb);
}
/**
* Called when the application unregisters rcs provisioning changed callback
*/
public boolean unregisterRcsProvisioningChangedCallback(int subId, IRcsConfigCallback cb) {
RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
// should not happen in normal case
if (info == null) {
logd("fail to unregister rcs provisioning changed due to subscription unavailable");
return false;
}
return info.removeRcsConfigCallback(cb);
}
/**
* override the device config whether single registration is enabled
*/
public void overrideDeviceSingleRegistrationEnabled(Boolean enabled) {
mHandler.sendMessage(mHandler.obtainMessage(EVENT_DEVICE_CONFIG_OVERRIDE, enabled));
}
/**
* Overrides the carrier config whether single registration is enabled
*/
public boolean overrideCarrierSingleRegistrationEnabled(int subId, Boolean enabled) {
if (!mRcsProvisioningInfos.containsKey(subId)) {
return false;
}
mHandler.sendMessage(mHandler.obtainMessage(
EVENT_CARRIER_CONFIG_OVERRIDE, subId, 0, enabled));
return true;
}
/**
* Returns the device config whether single registration is enabled
*/
public boolean getDeviceSingleRegistrationEnabled() {
for (RcsProvisioningInfo info : mRcsProvisioningInfos.values()) {
return (info.getSingleRegistrationCapability()
& ProvisioningManager.STATUS_DEVICE_NOT_CAPABLE) == 0;
}
return false;
}
/**
* Returns the carrier config whether single registration is enabled
*/
public boolean getCarrierSingleRegistrationEnabled(int subId) {
if (mRcsProvisioningInfos.containsKey(subId)) {
return (mRcsProvisioningInfos.get(subId).getSingleRegistrationCapability()
& ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE) == 0;
}
return false;
}
private void onDefaultMessagingApplicationChanged() {
final String packageName = getDmaPackageName();
if (!TextUtils.equals(mDmaPackageName, packageName)) {
mDmaPackageName = packageName;
logv("new default messaging application " + mDmaPackageName);
mRcsProvisioningInfos.forEach((k, v) -> {
byte[] cachedConfig = v.getConfig();
//clear old callbacks
v.clear();
if (isAcsUsed(k)) {
logv("acs used, trigger to re-configure.");
notifyRcsAutoConfigurationRemoved(k);
triggerRcsReconfiguration(k);
} else {
v.setConfig(cachedConfig);
logv("acs not used, notify.");
notifyRcsAutoConfigurationReceived(k, v.getConfig(), false);
}
});
}
}
private void notifyRcsAutoConfigurationReceived(int subId, byte[] config,
boolean isCompressed) {
if (config == null) {
logd("Rcs config is null for sub : " + subId);
return;
}
IImsConfig imsConfig = getIImsConfig(subId, ImsFeature.FEATURE_RCS);
if (imsConfig != null) {
try {
imsConfig.notifyRcsAutoConfigurationReceived(config, isCompressed);
} catch (RemoteException e) {
loge("fail to notify rcs configuration received!");
}
} else {
logd("getIImsConfig returns null.");
}
}
private void notifyRcsAutoConfigurationRemoved(int subId) {
RcsConfig.updateConfigForSub(mPhone, subId, null, true);
IImsConfig imsConfig = getIImsConfig(subId, ImsFeature.FEATURE_RCS);
if (imsConfig != null) {
try {
imsConfig.notifyRcsAutoConfigurationRemoved();
} catch (RemoteException e) {
loge("fail to notify rcs configuration removed!");
}
} else {
logd("getIImsConfig returns null.");
}
}
private void triggerRcsReconfiguration(int subId) {
IImsConfig imsConfig = getIImsConfig(subId, ImsFeature.FEATURE_RCS);
if (imsConfig != null) {
try {
imsConfig.triggerRcsReconfiguration();
} catch (RemoteException e) {
loge("fail to trigger rcs reconfiguration!");
}
} else {
logd("getIImsConfig returns null.");
}
}
private boolean isAcsUsed(int subId) {
PersistableBundle b = mCarrierConfigManager.getConfigForSubId(subId);
if (b == null) {
return false;
}
return b.getBoolean(CarrierConfigManager.KEY_USE_ACS_FOR_RCS_BOOL);
}
private boolean isSingleRegistrationRequiredByCarrier(int subId) {
Boolean enabledByOverride = mCarrierSingleRegistrationEnabledOverride.get(subId);
if (enabledByOverride != null) {
return enabledByOverride;
}
PersistableBundle b = mCarrierConfigManager.getConfigForSubId(subId);
if (b == null) {
return false;
}
return b.getBoolean(CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL);
}
private int getSingleRegistrationCapableValue(int subId) {
boolean isSingleRegistrationEnabledOnDevice =
mDeviceSingleRegistrationEnabledOverride != null
? mDeviceSingleRegistrationEnabledOverride
: mPhone.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION);
int value = (isSingleRegistrationEnabledOnDevice ? 0
: ProvisioningManager.STATUS_DEVICE_NOT_CAPABLE) | (
isSingleRegistrationRequiredByCarrier(subId) ? 0
: ProvisioningManager.STATUS_CARRIER_NOT_CAPABLE);
logv("SingleRegistrationCapableValue : " + value);
return value;
}
private void onCarrierConfigChange() {
logv("onCarrierConfigChange");
mRcsProvisioningInfos.forEach((subId, info) -> {
int value = getSingleRegistrationCapableValue(subId);
if (value != info.getSingleRegistrationCapability()) {
info.setSingleRegistrationCapability(value);
notifyDmaForSub(subId, value);
}
});
}
private void onSubChanged() {
final int[] activeSubs = mSubscriptionManager.getActiveSubscriptionIdList();
final HashSet<Integer> subsToBeDeactivated = new HashSet<>(mRcsProvisioningInfos.keySet());
for (int i : activeSubs) {
subsToBeDeactivated.remove(i);
if (!mRcsProvisioningInfos.containsKey(i)) {
byte[] data = RcsConfig.loadRcsConfigForSub(mPhone, i, false);
int capability = getSingleRegistrationCapableValue(i);
logv("new info is created for sub : " + i + ", single registration capability :"
+ capability + ", rcs config : " + data);
mRcsProvisioningInfos.put(i, new RcsProvisioningInfo(i, capability, data));
notifyRcsAutoConfigurationReceived(i, data, false);
notifyDmaForSub(i, capability);
}
}
subsToBeDeactivated.forEach(i -> {
RcsProvisioningInfo info = mRcsProvisioningInfos.remove(i);
notifyRcsAutoConfigurationRemoved(i);
if (info != null) {
info.clear();
}
});
}
private void onConfigReceived(int subId, byte[] config, boolean isCompressed) {
logv("onConfigReceived, subId:" + subId + ", config:"
+ config + ", isCompressed:" + isCompressed);
RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
if (info != null) {
info.setConfig(isCompressed ? RcsConfig.decompressGzip(config) : config);
}
RcsConfig.updateConfigForSub(mPhone, subId, config, isCompressed);
notifyRcsAutoConfigurationReceived(subId, config, isCompressed);
}
private void onReconfigRequest(int subId) {
logv("onReconfigRequest, subId:" + subId);
RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
if (info != null) {
info.setConfig(null);
}
notifyRcsAutoConfigurationRemoved(subId);
triggerRcsReconfiguration(subId);
}
private void notifyDmaForSub(int subId, int capability) {
final Intent intent = new Intent(
ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE);
intent.setPackage(mDmaPackageName);
intent.putExtra(ProvisioningManager.EXTRA_SUBSCRIPTION_ID, subId);
intent.putExtra(ProvisioningManager.EXTRA_STATUS, capability);
logv("notify " + intent);
mPhone.sendBroadcast(intent);
}
private IImsConfig getIImsConfig(int subId, int feature) {
return mPhone.getImsResolver().getImsConfig(
SubscriptionManager.getSlotIndex(subId), feature);
}
private String getDmaPackageName() {
try {
return CollectionUtils.firstOrNull(mRoleManager.getRoleHolders(RoleManager.ROLE_SMS));
} catch (RuntimeException e) {
loge("Could not get dma name due to " + e);
return null;
}
}
private static boolean booleanEquals(Boolean val1, Boolean val2) {
return (val1 == null && val2 == null)
|| (Boolean.TRUE.equals(val1) && Boolean.TRUE.equals(val2))
|| (Boolean.FALSE.equals(val1) && Boolean.FALSE.equals(val2));
}
private static void logv(String msg) {
if (DBG) {
Rlog.d(TAG, msg);
}
}
private static void logd(String msg) {
Rlog.d(TAG, msg);
}
private static void loge(String msg) {
Rlog.e(TAG, msg);
}
/**
* {@link RoleManager} is final so we have to wrap the implementation for testing.
*/
@VisibleForTesting
public interface RoleManagerAdapter {
/** See {@link RoleManager#getRoleHolders(String)} */
List<String> getRoleHolders(String roleName);
/** See {@link RoleManager#addOnRoleHoldersChangedListenerAsUser} */
void addOnRoleHoldersChangedListenerAsUser(Executor executor,
OnRoleHoldersChangedListener listener, UserHandle user);
/** See {@link RoleManager#removeOnRoleHoldersChangedListenerAsUser} */
void removeOnRoleHoldersChangedListenerAsUser(OnRoleHoldersChangedListener listener,
UserHandle user);
}
private static class RoleManagerAdapterImpl implements RoleManagerAdapter {
private final RoleManager mRoleManager;
private RoleManagerAdapterImpl(Context context) {
mRoleManager = context.getSystemService(RoleManager.class);
}
@Override
public List<String> getRoleHolders(String roleName) {
return mRoleManager.getRoleHolders(roleName);
}
@Override
public void addOnRoleHoldersChangedListenerAsUser(Executor executor,
OnRoleHoldersChangedListener listener, UserHandle user) {
mRoleManager.addOnRoleHoldersChangedListenerAsUser(executor, listener, user);
}
@Override
public void removeOnRoleHoldersChangedListenerAsUser(OnRoleHoldersChangedListener listener,
UserHandle user) {
mRoleManager.removeOnRoleHoldersChangedListenerAsUser(listener, user);
}
}
}