blob: c47d5b05c684067912387a2fbdb7676d98f12661 [file] [log] [blame]
/*
* 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.internal.telephony.ims;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ChangedPackages;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
import android.os.UserHandle;
import android.permission.LegacyPermissionManager;
import android.telephony.AnomalyReporter;
import android.telephony.ims.ImsService;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.aidl.IImsServiceController;
import android.telephony.ims.aidl.ISipTransport;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.stub.ImsFeatureConfiguration;
import android.util.LocalLog;
import android.util.Log;
import com.android.ims.ImsFeatureBinderRepository;
import com.android.ims.ImsFeatureContainer;
import com.android.ims.internal.IImsFeatureStatusCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.ExponentialBackoff;
import com.android.internal.telephony.util.TelephonyUtils;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
/**
* Manages the Binding lifecycle of one ImsService as well as the relevant ImsFeatures that the
* ImsService will support.
*
* When the ImsService is first bound, {@link ImsService#createMmTelFeature(int)} and
* {@link ImsService#createRcsFeature(int)} will be called
* on each feature that the service supports. For each ImsFeature that is created,
* {@link ImsServiceControllerCallbacks#imsServiceFeatureCreated} will be called to notify the
* listener that the ImsService now supports that feature.
*
* When {@link #changeImsServiceFeatures} is called with a set of features that is different from
* the original set, create*Feature and {@link IImsServiceController#removeImsFeature} will be
* called for each feature that is created/removed.
*/
public class ImsServiceController {
private final UUID mAnomalyUUID = UUID.fromString("e93b05e4-6d0a-4755-a6da-a2d2dbfb10d6");
private int mLastSequenceNumber = 0;
private ChangedPackages mChangedPackages;
private PackageManager mPackageManager;
class ImsServiceConnection implements ServiceConnection {
// Track the status of whether or not the Service has died in case we need to permanently
// unbind (see onNullBinding below).
private boolean mIsServiceConnectionDead = false;
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
mBackoff.stop();
mIsBound = true;
mIsBinding = false;
try {
mLocalLog.log("onServiceConnected");
Log.d(LOG_TAG, "ImsService(" + name + "): onServiceConnected with binder: "
+ service);
setServiceController(service);
notifyImsServiceReady();
retrieveStaticImsServiceCapabilities();
// create all associated features in the ImsService
for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
long caps = modifyCapabiltiesForSlot(mImsFeatures, i.slotId,
mServiceCapabilities);
addImsServiceFeature(i, caps);
}
} catch (RemoteException e) {
mIsBound = false;
mIsBinding = false;
// RemoteException means that the process holding the binder died or something
// unexpected happened... try a full rebind.
cleanupConnection();
unbindService();
startDelayedRebindToService();
mLocalLog.log("onConnected exception=" + e.getMessage() + ", retry in "
+ mBackoff.getCurrentDelay() + " mS");
Log.e(LOG_TAG, "ImsService(" + name + ") RemoteException:"
+ e.getMessage());
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mLock) {
mIsBinding = false;
cleanupConnection();
}
mLocalLog.log("onServiceDisconnected");
Log.w(LOG_TAG, "ImsService(" + name + "): onServiceDisconnected. Waiting...");
// Service disconnected, but we are still technically bound. Waiting for reconnect.
checkAndReportAnomaly(name);
}
@Override
public void onBindingDied(ComponentName name) {
mIsServiceConnectionDead = true;
synchronized (mLock) {
mIsBinding = false;
mIsBound = false;
// according to the docs, we should fully unbind before rebinding again.
cleanupConnection();
unbindService();
startDelayedRebindToService();
}
Log.w(LOG_TAG, "ImsService(" + name + "): onBindingDied. Starting rebind...");
mLocalLog.log("onBindingDied, retrying in " + mBackoff.getCurrentDelay() + " mS");
}
@Override
public void onNullBinding(ComponentName name) {
Log.w(LOG_TAG, "ImsService(" + name + "): onNullBinding. Is service dead = "
+ mIsServiceConnectionDead);
mLocalLog.log("onNullBinding, is service dead = " + mIsServiceConnectionDead);
// onNullBinding will happen after onBindingDied. In this case, we should not
// permanently unbind and instead let the automatic rebind occur.
if (mIsServiceConnectionDead) return;
synchronized (mLock) {
mIsBinding = false;
// Service connection exists, so we are bound but the binder is null. Wait for
// ImsResolver to trigger the unbind here.
mIsBound = true;
cleanupConnection();
}
if (mCallbacks != null) {
// Will trigger an unbind.
mCallbacks.imsServiceBindPermanentError(getComponentName());
}
}
// Does not clear feature configuration, just cleans up the active callbacks and
// invalidates remote FeatureConnections.
// This should only be called when locked
private void cleanupConnection() {
cleanupAllFeatures();
setServiceController(null);
}
}
/**
* Defines callbacks that are used by the ImsServiceController to notify when an ImsService
* has created or removed a new feature as well as the associated ImsServiceController.
*/
public interface ImsServiceControllerCallbacks {
/**
* Called by ImsServiceController when a new MMTEL or RCS feature has been created.
*/
void imsServiceFeatureCreated(int slotId, int feature, ImsServiceController controller);
/**
* Called by ImsServiceController when a new MMTEL or RCS feature has been removed.
*/
void imsServiceFeatureRemoved(int slotId, int feature, ImsServiceController controller);
/**
* Called by the ImsServiceController when the ImsService has notified the framework that
* its features have changed.
*/
void imsServiceFeaturesChanged(ImsFeatureConfiguration config,
ImsServiceController controller);
/**
* Called by the ImsServiceController when there has been an error binding that is
* not recoverable, such as the ImsService returning a null binder.
*/
void imsServiceBindPermanentError(ComponentName name);
}
/**
* Returns the currently defined rebind retry timeout. Used for testing.
*/
@VisibleForTesting
public interface RebindRetry {
/**
* Returns a long in ms indicating how long the ImsServiceController should wait before
* rebinding for the first time.
*/
long getStartDelay();
/**
* Returns a long in ms indicating the maximum time the ImsServiceController should wait
* before rebinding.
*/
long getMaximumDelay();
}
private static final String LOG_TAG = "ImsServiceController";
private static final int REBIND_START_DELAY_MS = 2 * 1000; // 2 seconds
private static final int REBIND_MAXIMUM_DELAY_MS = 60 * 1000; // 1 minute
private static final long CHANGE_PERMISSION_TIMEOUT_MS = 15 * 1000; // 15 seconds
// Enforce ImsService has both MMTEL and RCS supported in order to enable SIP transport API.
// Enable ImsServiceControllerTest and SipDelegateManagerTest cases if this is re-enabled.
private static final boolean ENFORCE_SINGLE_SERVICE_FOR_SIP_TRANSPORT = false;
private final ComponentName mComponentName;
private final HandlerThread mHandlerThread = new HandlerThread("ImsServiceControllerHandler");
private final LegacyPermissionManager mPermissionManager;
private ImsFeatureBinderRepository mRepo;
private ImsServiceControllerCallbacks mCallbacks;
private ExponentialBackoff mBackoff;
private boolean mIsBound = false;
private boolean mIsBinding = false;
// Set of a pair of slotId->feature
private Set<ImsFeatureConfiguration.FeatureSlotPair> mImsFeatures;
private IImsServiceController mIImsServiceController;
// The Capabilities bitmask of the connected ImsService (see ImsService#ImsServiceCapability).
private long mServiceCapabilities;
private ImsServiceConnection mImsServiceConnection;
// Only added or removed, never accessed on purpose.
private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>();
private final LocalLog mLocalLog = new LocalLog(10);
protected final Object mLock = new Object();
protected final Context mContext;
private ImsService.Listener mFeatureChangedListener = new ImsService.Listener() {
@Override
public void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) {
if (mCallbacks == null) {
return;
}
mLocalLog.log("onUpdateSupportedImsFeatures to " + c.getServiceFeatures());
mCallbacks.imsServiceFeaturesChanged(c, ImsServiceController.this);
}
};
/**
* Container class for the IImsFeatureStatusCallback callback implementation. This class is
* never used directly, but we need to keep track of the IImsFeatureStatusCallback
* implementations explicitly.
*/
private class ImsFeatureStatusCallback {
private int mSlotId;
private int mFeatureType;
private final IImsFeatureStatusCallback mCallback = new IImsFeatureStatusCallback.Stub() {
@Override
public void notifyImsFeatureStatus(int featureStatus) throws RemoteException {
Log.i(LOG_TAG, "notifyImsFeatureStatus: slot=" + mSlotId + ", feature="
+ ImsFeature.FEATURE_LOG_MAP.get(mFeatureType) + ", status="
+ ImsFeature.STATE_LOG_MAP.get(featureStatus));
mRepo.notifyFeatureStateChanged(mSlotId, mFeatureType, featureStatus);
}
};
ImsFeatureStatusCallback(int slotId, int featureType) {
mSlotId = slotId;
mFeatureType = featureType;
}
public IImsFeatureStatusCallback getCallback() {
return mCallback;
}
}
// Retry the bind to the ImsService that has died after mRebindRetry timeout.
private Runnable mRestartImsServiceRunnable = new Runnable() {
@Override
public void run() {
synchronized (mLock) {
if (mIsBound) {
return;
}
bind(mImsFeatures);
}
}
};
private RebindRetry mRebindRetry = new RebindRetry() {
@Override
public long getStartDelay() {
return REBIND_START_DELAY_MS;
}
@Override
public long getMaximumDelay() {
return REBIND_MAXIMUM_DELAY_MS;
}
};
public ImsServiceController(Context context, ComponentName componentName,
ImsServiceControllerCallbacks callbacks, ImsFeatureBinderRepository repo) {
mContext = context;
mComponentName = componentName;
mCallbacks = callbacks;
mHandlerThread.start();
mBackoff = new ExponentialBackoff(
mRebindRetry.getStartDelay(),
mRebindRetry.getMaximumDelay(),
2, /* multiplier */
mHandlerThread.getLooper(),
mRestartImsServiceRunnable);
mPermissionManager = (LegacyPermissionManager) mContext.getSystemService(
Context.LEGACY_PERMISSION_SERVICE);
mRepo = repo;
mPackageManager = mContext.getPackageManager();
if (mPackageManager != null) {
mChangedPackages = mPackageManager.getChangedPackages(mLastSequenceNumber);
if (mChangedPackages != null) {
mLastSequenceNumber = mChangedPackages.getSequenceNumber();
}
}
}
@VisibleForTesting
// Creating a new HandlerThread and background handler for each test causes a segfault, so for
// testing, use a handler supplied by the testing system.
public ImsServiceController(Context context, ComponentName componentName,
ImsServiceControllerCallbacks callbacks, Handler handler, RebindRetry rebindRetry,
ImsFeatureBinderRepository repo) {
mContext = context;
mComponentName = componentName;
mCallbacks = callbacks;
mBackoff = new ExponentialBackoff(
rebindRetry.getStartDelay(),
rebindRetry.getMaximumDelay(),
2, /* multiplier */
handler,
mRestartImsServiceRunnable);
mPermissionManager = null;
mRepo = repo;
}
/**
* Sends request to bind to ImsService designated by the {@link ComponentName} with the feature
* set imsFeatureSet.
*
* @param imsFeatureSet a Set of Pairs that designate the slotId->featureId that need to be
* created once the service is bound.
* @return {@link true} if the service is in the process of being bound, {@link false} if it
* has failed.
*/
public boolean bind(Set<ImsFeatureConfiguration.FeatureSlotPair> imsFeatureSet) {
synchronized (mLock) {
if (!mIsBound && !mIsBinding) {
mIsBinding = true;
sanitizeFeatureConfig(imsFeatureSet);
mImsFeatures = imsFeatureSet;
grantPermissionsToService();
Intent imsServiceIntent = new Intent(getServiceInterface()).setComponent(
mComponentName);
mImsServiceConnection = new ImsServiceConnection();
int serviceFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
| Context.BIND_IMPORTANT;
mLocalLog.log("binding " + imsFeatureSet);
Log.i(LOG_TAG, "Binding ImsService:" + mComponentName);
try {
boolean bindSucceeded = mContext.bindService(imsServiceIntent,
mImsServiceConnection, serviceFlags);
if (!bindSucceeded) {
mLocalLog.log(" binding failed, retrying in "
+ mBackoff.getCurrentDelay() + " mS");
mIsBinding = false;
mBackoff.notifyFailed();
}
return bindSucceeded;
} catch (Exception e) {
mBackoff.notifyFailed();
mLocalLog.log(" binding exception=" + e.getMessage() + ", retrying in "
+ mBackoff.getCurrentDelay() + " mS");
Log.e(LOG_TAG, "Error binding (" + mComponentName + ") with exception: "
+ e.getMessage() + ", rebinding in " + mBackoff.getCurrentDelay()
+ " ms");
return false;
}
} else {
return false;
}
}
}
/**
* Ensure the feature includes MMTEL when it supports EMERGENCY_MMTEL, if not, remove.
*/
private void sanitizeFeatureConfig(Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
Set<ImsFeatureConfiguration.FeatureSlotPair> emergencyMmtelFeatures = features.stream()
.filter(feature -> feature.featureType == ImsFeature.FEATURE_EMERGENCY_MMTEL)
.collect(Collectors.toSet());
for (ImsFeatureConfiguration.FeatureSlotPair feature : emergencyMmtelFeatures) {
if (!features.contains(new ImsFeatureConfiguration.FeatureSlotPair(feature.slotId,
ImsFeature.FEATURE_MMTEL))) {
features.remove(feature);
}
}
}
/**
* Calls {@link IImsServiceController#removeImsFeature} on all features that the
* ImsService supports and then unbinds the service.
*/
public void unbind() throws RemoteException {
synchronized (mLock) {
mBackoff.stop();
// Clean up all features
changeImsServiceFeatures(new HashSet<>());
mIsBound = false;
mIsBinding = false;
setServiceController(null);
unbindService();
}
}
/**
* For every feature that is added, the service calls the associated create. For every
* ImsFeature that is removed, {@link IImsServiceController#removeImsFeature} is called.
*/
public void changeImsServiceFeatures(
Set<ImsFeatureConfiguration.FeatureSlotPair> newImsFeatures)
throws RemoteException {
sanitizeFeatureConfig(newImsFeatures);
synchronized (mLock) {
if (mImsFeatures.equals(newImsFeatures)) {
return;
}
mLocalLog.log("Features changed (" + mImsFeatures + "->" + newImsFeatures + ")");
Log.i(LOG_TAG, "Features changed (" + mImsFeatures + "->" + newImsFeatures + ") for "
+ "ImsService: " + mComponentName);
HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldImsFeatures =
new HashSet<>(mImsFeatures);
// Set features first in case we lose binding and need to rebind later.
mImsFeatures = newImsFeatures;
if (mIsBound) {
// add features to service.
HashSet<ImsFeatureConfiguration.FeatureSlotPair> newFeatures =
new HashSet<>(mImsFeatures);
newFeatures.removeAll(oldImsFeatures);
for (ImsFeatureConfiguration.FeatureSlotPair i : newFeatures) {
long caps = modifyCapabiltiesForSlot(mImsFeatures, i.slotId,
mServiceCapabilities);
addImsServiceFeature(i, caps);
}
// remove old features
HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldFeatures =
new HashSet<>(oldImsFeatures);
oldFeatures.removeAll(mImsFeatures);
for (ImsFeatureConfiguration.FeatureSlotPair i : oldFeatures) {
removeImsServiceFeature(i);
}
// ensure the capabilities have been updated for unchanged features.
HashSet<ImsFeatureConfiguration.FeatureSlotPair> unchangedFeatures =
new HashSet<>(mImsFeatures);
unchangedFeatures.removeAll(oldFeatures);
unchangedFeatures.removeAll(newFeatures);
for (ImsFeatureConfiguration.FeatureSlotPair p : unchangedFeatures) {
long caps = modifyCapabiltiesForSlot(mImsFeatures, p.slotId,
mServiceCapabilities);
mRepo.notifyFeatureCapabilitiesChanged(p.slotId, p.featureType, caps);
}
}
}
}
@VisibleForTesting
public IImsServiceController getImsServiceController() {
return mIImsServiceController;
}
@VisibleForTesting
public long getRebindDelay() {
return mBackoff.getCurrentDelay();
}
@VisibleForTesting
public void stopBackoffTimerForTesting() {
mBackoff.stop();
}
public ComponentName getComponentName() {
return mComponentName;
}
public void enableIms(int slotId) {
try {
synchronized (mLock) {
if (isServiceControllerAvailable()) {
mIImsServiceController.enableIms(slotId);
}
}
} catch (RemoteException e) {
Log.w(LOG_TAG, "Couldn't enable IMS: " + e.getMessage());
}
}
public void disableIms(int slotId) {
try {
synchronized (mLock) {
if (isServiceControllerAvailable()) {
mIImsServiceController.disableIms(slotId);
}
}
} catch (RemoteException e) {
Log.w(LOG_TAG, "Couldn't disable IMS: " + e.getMessage());
}
}
/**
* @return the IImsRegistration that corresponds to the slot id specified.
*/
public IImsRegistration getRegistration(int slotId) throws RemoteException {
synchronized (mLock) {
return isServiceControllerAvailable()
? mIImsServiceController.getRegistration(slotId) : null;
}
}
/**
* @return the IImsConfig that corresponds to the slot id specified.
*/
public IImsConfig getConfig(int slotId) throws RemoteException {
synchronized (mLock) {
return isServiceControllerAvailable() ? mIImsServiceController.getConfig(slotId) : null;
}
}
/**
* @return the ISipTransport instance associated with the requested slot ID.
*/
public ISipTransport getSipTransport(int slotId) throws RemoteException {
synchronized (mLock) {
return isServiceControllerAvailable()
? mIImsServiceController.getSipTransport(slotId) : null;
}
}
protected long getStaticServiceCapabilities() throws RemoteException {
synchronized (mLock) {
return isServiceControllerAvailable()
? mIImsServiceController.getImsServiceCapabilities() : 0L;
}
}
/**
* notify the ImsService that the ImsService is ready for feature creation.
*/
protected void notifyImsServiceReady() throws RemoteException {
synchronized (mLock) {
if (isServiceControllerAvailable()) {
Log.d(LOG_TAG, "notifyImsServiceReady");
mIImsServiceController.setListener(mFeatureChangedListener);
mIImsServiceController.notifyImsServiceReadyForFeatureCreation();
}
}
}
private void retrieveStaticImsServiceCapabilities() throws RemoteException {
long caps = getStaticServiceCapabilities();
Log.i(LOG_TAG, "retrieveStaticImsServiceCapabilities: "
+ ImsService.getCapabilitiesString(caps));
mLocalLog.log("retrieveStaticImsServiceCapabilities: "
+ ImsService.getCapabilitiesString(caps));
synchronized (mLock) {
mServiceCapabilities = caps;
}
}
protected String getServiceInterface() {
return ImsService.SERVICE_INTERFACE;
}
/**
* Sets the IImsServiceController instance. Overridden by compat layers to set compatibility
* versions of this service controller.
*/
protected void setServiceController(IBinder serviceController) {
mIImsServiceController = IImsServiceController.Stub.asInterface(serviceController);
}
/**
* Check to see if the service controller is available, overridden for compat versions,
* @return true if available, false otherwise;
*/
protected boolean isServiceControllerAvailable() {
return mIImsServiceController != null;
}
// Only add a new rebind if there are no pending rebinds waiting.
private void startDelayedRebindToService() {
mBackoff.start();
}
private void unbindService() {
synchronized (mLock) {
if (mImsServiceConnection != null) {
Log.i(LOG_TAG, "Unbinding ImsService: " + mComponentName);
mLocalLog.log("unbinding: " + mComponentName);
mContext.unbindService(mImsServiceConnection);
mImsServiceConnection = null;
} else {
Log.i(LOG_TAG, "unbindService called on already unbound ImsService: "
+ mComponentName);
mLocalLog.log("Note: unbindService called with no ServiceConnection on "
+ mComponentName);
}
}
}
/**
* Modify the capabilities returned by the ImsService based on the state of this controller:
* - CAPABILITY_EMERGENCY_OVER_MMTEL should only be set if features contains
* FEATURE_EMERGENCY_MMTEL (This is not set by the ImsService itself).
* - CAPABILITY_SIP_DELEGATE_CREATION should only be set in the case that this ImsService is
* handling both MMTEL and RCS features for this slot.
*/
private long modifyCapabiltiesForSlot(
Set<ImsFeatureConfiguration.FeatureSlotPair> features, int slotId, long serviceCaps) {
long caps = serviceCaps;
List<Integer> featureTypes = getFeaturesForSlot(slotId, features);
if (featureTypes.contains(ImsFeature.FEATURE_EMERGENCY_MMTEL)) {
// We only consider MMTEL_EMERGENCY as a capability here, so set the capability if
// the ImsService has declared it.
caps |= ImsService.CAPABILITY_EMERGENCY_OVER_MMTEL;
}
if (ENFORCE_SINGLE_SERVICE_FOR_SIP_TRANSPORT) {
if (!featureTypes.contains(ImsFeature.FEATURE_MMTEL)
|| !featureTypes.contains(ImsFeature.FEATURE_RCS)) {
// Only allow SipDelegate creation if this ImsService is providing both MMTEL and
// RCS features.
caps &= ~(ImsService.CAPABILITY_SIP_DELEGATE_CREATION);
}
} else {
Log.i(LOG_TAG, "skipping single service enforce check...");
}
return caps;
}
// Grant runtime permissions to ImsService. PermissionManager ensures that the ImsService is
// system/signed before granting permissions.
private void grantPermissionsToService() {
mLocalLog.log("grant permissions to " + getComponentName());
Log.i(LOG_TAG, "Granting Runtime permissions to:" + getComponentName());
String[] pkgToGrant = {mComponentName.getPackageName()};
try {
if (mPermissionManager != null) {
CountDownLatch latch = new CountDownLatch(1);
mPermissionManager.grantDefaultPermissionsToEnabledImsServices(
pkgToGrant, UserHandle.of(UserHandle.myUserId()), Runnable::run,
isSuccess -> {
if (isSuccess) {
latch.countDown();
} else {
Log.e(LOG_TAG, "Failed to grant permissions to service.");
}
});
TelephonyUtils.waitUntilReady(latch, CHANGE_PERMISSION_TIMEOUT_MS);
}
} catch (RuntimeException e) {
Log.w(LOG_TAG, "Unable to grant permissions, binder died.");
}
}
// This method should only be called when synchronized on mLock
private void addImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair,
long capabilities)
throws RemoteException {
if (!isServiceControllerAvailable() || mCallbacks == null) {
Log.w(LOG_TAG, "addImsServiceFeature called with null values.");
return;
}
if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) {
IInterface f = createImsFeature(featurePair.slotId, featurePair.featureType);
addImsFeatureBinder(featurePair.slotId, featurePair.featureType, f, capabilities);
addImsFeatureStatusCallback(featurePair.slotId, featurePair.featureType);
} else {
// Don't update ImsService for emergency MMTEL feature.
Log.i(LOG_TAG, "supports emergency calling on slot " + featurePair.slotId);
}
// Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
mCallbacks.imsServiceFeatureCreated(featurePair.slotId, featurePair.featureType, this);
}
// This method should only be called when synchronized on mLock
private void removeImsServiceFeature(ImsFeatureConfiguration.FeatureSlotPair featurePair) {
if (!isServiceControllerAvailable() || mCallbacks == null) {
Log.w(LOG_TAG, "removeImsServiceFeature called with null values.");
return;
}
// Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
mCallbacks.imsServiceFeatureRemoved(featurePair.slotId, featurePair.featureType, this);
if (featurePair.featureType != ImsFeature.FEATURE_EMERGENCY_MMTEL) {
removeImsFeatureStatusCallback(featurePair.slotId, featurePair.featureType);
removeImsFeatureBinder(featurePair.slotId, featurePair.featureType);
try {
removeImsFeature(featurePair.slotId, featurePair.featureType);
} catch (RemoteException e) {
// The connection to this ImsService doesn't exist. This may happen if the service
// has died and we are removing features.
Log.i(LOG_TAG, "Couldn't remove feature {"
+ ImsFeature.FEATURE_LOG_MAP.get(featurePair.featureType)
+ "}, connection is down: " + e.getMessage());
}
} else {
// Don't update ImsService for emergency MMTEL feature.
Log.i(LOG_TAG, "doesn't support emergency calling on slot " + featurePair.slotId);
}
}
// This method should only be called when already synchronized on mLock.
// overridden by compat layer to create features
protected IInterface createImsFeature(int slotId, int featureType)
throws RemoteException {
switch (featureType) {
case ImsFeature.FEATURE_MMTEL: {
return mIImsServiceController.createMmTelFeature(slotId);
}
case ImsFeature.FEATURE_RCS: {
return mIImsServiceController.createRcsFeature(slotId);
}
default:
return null;
}
}
// This method should only be called when already synchronized on mLock.
private void addImsFeatureStatusCallback(int slotId, int featureType) throws RemoteException {
ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(slotId, featureType);
mFeatureStatusCallbacks.add(c);
registerImsFeatureStatusCallback(slotId, featureType, c.getCallback());
}
// This method should only be called when already synchronized on mLock.
private void removeImsFeatureStatusCallback(int slotId, int featureType) {
ImsFeatureStatusCallback callbackToRemove = mFeatureStatusCallbacks.stream().filter(c ->
c.mSlotId == slotId && c.mFeatureType == featureType).findFirst().orElse(null);
// Remove status callbacks from list.
if (callbackToRemove != null) {
mFeatureStatusCallbacks.remove(callbackToRemove);
unregisterImsFeatureStatusCallback(slotId, featureType, callbackToRemove.getCallback());
}
}
// overridden by compat layer to register feature status callbacks
protected void registerImsFeatureStatusCallback(int slotId, int featureType,
IImsFeatureStatusCallback c) throws RemoteException {
mIImsServiceController.addFeatureStatusCallback(slotId, featureType, c);
}
// overridden by compat layer to deregister feature status callbacks
protected void unregisterImsFeatureStatusCallback(int slotId, int featureType,
IImsFeatureStatusCallback c) {
try {
mIImsServiceController.removeFeatureStatusCallback(slotId, featureType, c);
} catch (RemoteException e) {
mLocalLog.log("unregisterImsFeatureStatusCallback - couldn't remove " + c);
}
}
// overridden by compat layer to remove features
protected void removeImsFeature(int slotId, int featureType)
throws RemoteException {
mIImsServiceController.removeImsFeature(slotId, featureType);
}
private void addImsFeatureBinder(int slotId, int featureType, IInterface b, long capabilities)
throws RemoteException {
if (b == null) {
Log.w(LOG_TAG, "addImsFeatureBinder: null IInterface reported for "
+ ImsFeature.FEATURE_LOG_MAP.get(featureType));
mLocalLog.log("addImsFeatureBinder: null IInterface reported for "
+ ImsFeature.FEATURE_LOG_MAP.get(featureType));
return;
}
ImsFeatureContainer fc = createFeatureContainer(slotId, b.asBinder(), capabilities);
mRepo.addConnection(slotId, featureType, fc);
}
private void removeImsFeatureBinder(int slotId, int featureType) {
mRepo.removeConnection(slotId, featureType);
}
private ImsFeatureContainer createFeatureContainer(int slotId, IBinder b, long capabilities)
throws RemoteException {
IImsConfig config = getConfig(slotId);
IImsRegistration reg = getRegistration(slotId);
// When either is null, this is an unexpected condition. Do not report the ImsService as
// being available.
if (config == null || reg == null) {
Log.w(LOG_TAG, "createFeatureContainer: invalid state. Reporting as not "
+ "available. componentName= " + getComponentName());
mLocalLog.log("createFeatureContainer: invalid state. Reporting as not "
+ "available.");
return null;
}
// SipTransport AIDL may be null for older devices, this is expected.
ISipTransport transport = getSipTransport(slotId);
return new ImsFeatureContainer(b, config, reg, transport, capabilities);
}
private List<Integer> getFeaturesForSlot(int slotId,
Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
return features.stream().filter(f -> f.slotId == slotId).map(f -> f.featureType)
.collect(Collectors.toList());
}
private void cleanupAllFeatures() {
synchronized (mLock) {
// Remove all features and clean up all associated Binders.
for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
removeImsServiceFeature(i);
}
}
}
private void checkAndReportAnomaly(ComponentName name) {
if (mPackageManager == null) {
Log.w(LOG_TAG, "mPackageManager null");
return;
}
ChangedPackages curChangedPackages =
mPackageManager.getChangedPackages(mLastSequenceNumber);
if (curChangedPackages != null) {
mLastSequenceNumber = curChangedPackages.getSequenceNumber();
List<String> packagesNames = curChangedPackages.getPackageNames();
if (packagesNames.contains(name.getPackageName())) {
Log.d(LOG_TAG, "Ignore due to updated, package: " + name.getPackageName());
return;
}
}
String message = "IMS Service Crashed";
AnomalyReporter.reportAnomaly(mAnomalyUUID, message);
}
@Override
public String toString() {
synchronized (mLock) {
return "[ImsServiceController: componentName=" + getComponentName() + ", features="
+ mImsFeatures + ", isBinding=" + mIsBinding + ", isBound=" + mIsBound
+ ", serviceController=" + getImsServiceController() + ", rebindDelay="
+ getRebindDelay() + "]";
}
}
public void dump(PrintWriter printWriter) {
mLocalLog.dump(printWriter);
}
}