blob: 7834903baa23ea8d237302ef845c97db3092e247 [file] [log] [blame]
/*
* Copyright (C) 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.services.telephony.rcs;
import android.annotation.AnyThread;
import android.content.Context;
import android.net.Uri;
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.ArrayMap;
import android.util.Log;
import com.android.ims.FeatureConnector;
import com.android.ims.FeatureUpdates;
import com.android.ims.RcsFeatureManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.imsphone.ImsRegistrationCallbackHelper;
import com.android.internal.util.IndentingPrintWriter;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* Contains the RCS feature implementations that are associated with this slot's RcsFeature.
*/
@AnyThread
public class RcsFeatureController {
private static final String LOG_TAG = "RcsFeatureController";
/**
* Interface used by RCS features that need to listen for when the associated service has been
* connected.
*/
public interface Feature {
/**
* The RcsFeature has been connected to the framework and is ready.
*/
void onRcsConnected(RcsFeatureManager manager);
/**
* The framework has lost the binding to the RcsFeature or it is in the process of changing.
*/
void onRcsDisconnected();
/**
* The subscription associated with the slot this controller is bound to has changed.
*/
void onAssociatedSubscriptionUpdated(int subId);
/**
* The carrier configuration associated with the active subscription id has changed.
*/
void onCarrierConfigChanged();
/**
* Called when the feature should be destroyed.
*/
void onDestroy();
/**
* Called when a dumpsys is being generated for this RcsFeatureController for all Features
* to report their status.
*/
void dump(PrintWriter pw);
}
/**
* Used to inject FeatureConnector instances for testing.
*/
@VisibleForTesting
public interface FeatureConnectorFactory<U extends FeatureUpdates> {
/**
* @return a {@link FeatureConnector} associated for the given {@link FeatureUpdates}
* and slot index.
*/
FeatureConnector<U> create(Context context, int slotIndex,
FeatureConnector.Listener<U> listener, Executor executor, String logPrefix);
}
/**
* Used to inject ImsRegistrationCallbackHelper instances for testing.
*/
@VisibleForTesting
public interface RegistrationHelperFactory {
/**
* @return an {@link ImsRegistrationCallbackHelper}, which helps manage IMS registration
* state.
*/
ImsRegistrationCallbackHelper create(
ImsRegistrationCallbackHelper.ImsRegistrationUpdate cb, Executor executor);
}
private FeatureConnectorFactory<RcsFeatureManager> mFeatureFactory =
RcsFeatureManager::getConnector;
private RegistrationHelperFactory mRegistrationHelperFactory =
ImsRegistrationCallbackHelper::new;
private final Map<Class<?>, Feature> mFeatures = new ArrayMap<>();
private final Context mContext;
private final ImsRegistrationCallbackHelper mImsRcsRegistrationHelper;
private final int mSlotId;
private final Object mLock = new Object();
private FeatureConnector<RcsFeatureManager> mFeatureConnector;
private RcsFeatureManager mFeatureManager;
private int mAssociatedSubId;
private FeatureConnector.Listener<RcsFeatureManager> mFeatureConnectorListener =
new FeatureConnector.Listener<RcsFeatureManager>() {
@Override
public void connectionReady(RcsFeatureManager manager)
throws com.android.ims.ImsException {
if (manager == null) {
logw("connectionReady returned null RcsFeatureManager");
return;
}
logd("connectionReady");
try {
// May throw ImsException if for some reason the connection to the
// ImsService is gone.
updateConnectionStatus(manager);
setupConnectionToService(manager);
} catch (ImsException e) {
updateConnectionStatus(null /*manager*/);
// Use deprecated Exception for compatibility.
throw new com.android.ims.ImsException(e.getMessage(),
ImsReasonInfo.CODE_LOCAL_IMS_SERVICE_DOWN);
}
}
@Override
public void connectionUnavailable(int reason) {
if (reason == FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE) {
loge("unexpected - connectionUnavailable due to server unavailable");
}
logd("connectionUnavailable");
// Call before disabling connection to manager.
removeConnectionToService();
updateConnectionStatus(null /*manager*/);
}
};
private ImsRegistrationCallbackHelper.ImsRegistrationUpdate mRcsRegistrationUpdate = new
ImsRegistrationCallbackHelper.ImsRegistrationUpdate() {
@Override
public void handleImsRegistered(int imsRadioTech) {
}
@Override
public void handleImsRegistering(int imsRadioTech) {
}
@Override
public void handleImsUnregistered(ImsReasonInfo imsReasonInfo) {
}
@Override
public void handleImsSubscriberAssociatedUriChanged(Uri[] uris) {
}
};
public RcsFeatureController(Context context, int slotId, int associatedSubId) {
mContext = context;
mSlotId = slotId;
mAssociatedSubId = associatedSubId;
mImsRcsRegistrationHelper = mRegistrationHelperFactory.create(mRcsRegistrationUpdate,
mContext.getMainExecutor());
}
/**
* Should only be used to inject registration helpers for testing.
*/
@VisibleForTesting
public RcsFeatureController(Context context, int slotId, int associatedSubId,
RegistrationHelperFactory f) {
mContext = context;
mSlotId = slotId;
mAssociatedSubId = associatedSubId;
mRegistrationHelperFactory = f;
mImsRcsRegistrationHelper = mRegistrationHelperFactory.create(mRcsRegistrationUpdate,
mContext.getMainExecutor());
}
/**
* This method should be called after constructing an instance of this class to start the
* connection process to the associated RcsFeature.
*/
public void connect() {
synchronized (mLock) {
if (mFeatureConnector != null) return;
mFeatureConnector = mFeatureFactory.create(mContext, mSlotId, mFeatureConnectorListener,
mContext.getMainExecutor(), LOG_TAG);
mFeatureConnector.connect();
}
}
/**
* Adds a {@link Feature} to be tracked by this FeatureController.
*/
public <T extends Feature> void addFeature(T connector, Class<T> clazz) {
synchronized (mLock) {
mFeatures.put(clazz, connector);
}
RcsFeatureManager manager = getFeatureManager();
if (manager != null) {
connector.onRcsConnected(manager);
} else {
connector.onRcsDisconnected();
}
}
/**
* @return The RCS feature implementation tracked by this controller.
*/
@SuppressWarnings("unchecked")
public <T> T getFeature(Class<T> clazz) {
synchronized (mLock) {
return (T) mFeatures.get(clazz);
}
}
/**
* Removes the feature associated with this class.
*/
public <T> void removeFeature(Class<T> clazz) {
synchronized (mLock) {
RcsFeatureController.Feature feature = mFeatures.remove(clazz);
feature.onDestroy();
}
}
/**
* @return true if this controller has features it is actively tracking.
*/
public boolean hasActiveFeatures() {
synchronized (mLock) {
return mFeatures.size() > 0;
}
}
/**
* Update the Features associated with this controller due to the associated subscription
* changing.
*/
public void updateAssociatedSubscription(int newSubId) {
mAssociatedSubId = newSubId;
updateCapabilities();
synchronized (mLock) {
for (Feature c : mFeatures.values()) {
c.onAssociatedSubscriptionUpdated(newSubId);
}
}
}
/**
* Update the features associated with this controller due to the carrier configuration
* changing.
*/
public void onCarrierConfigChangedForSubscription() {
updateCapabilities();
synchronized (mLock) {
for (Feature c : mFeatures.values()) {
c.onCarrierConfigChanged();
}
}
}
/**
* Call before this controller is destroyed to tear down associated features.
*/
public void destroy() {
synchronized (mLock) {
Log.i(LOG_TAG, "destroy: slotId=" + mSlotId);
if (mFeatureConnector != null) {
mFeatureConnector.disconnect();
}
for (Feature c : mFeatures.values()) {
c.onRcsDisconnected();
c.onDestroy();
}
mFeatures.clear();
}
}
@VisibleForTesting
public void setFeatureConnectorFactory(FeatureConnectorFactory<RcsFeatureManager> factory) {
mFeatureFactory = factory;
}
/**
* Add a {@link RegistrationManager.RegistrationCallback} callback that gets called when IMS
* registration has changed for a specific subscription.
*/
public void registerImsRegistrationCallback(int subId, IImsRegistrationCallback callback)
throws ImsException {
RcsFeatureManager manager = getFeatureManager();
if (manager == null) {
throw new ImsException("Service is not available",
ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
manager.registerImsRegistrationCallback(subId, callback);
}
/**
* Removes a previously registered {@link RegistrationManager.RegistrationCallback} callback
* that is associated with a specific subscription.
*/
public void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback callback) {
RcsFeatureManager manager = getFeatureManager();
if (manager != null) {
manager.unregisterImsRegistrationCallback(subId, callback);
}
}
/**
* Register an {@link ImsRcsManager.OnAvailabilityChangedListener} with the associated
* RcsFeature, which will provide availability updates.
*/
public void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback)
throws ImsException {
RcsFeatureManager manager = getFeatureManager();
if (manager == null) {
throw new ImsException("Service is not available",
ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
manager.registerRcsAvailabilityCallback(subId, callback);
}
/**
* Remove a registered {@link ImsRcsManager.OnAvailabilityChangedListener} from the RcsFeature.
*/
public void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) {
RcsFeatureManager manager = getFeatureManager();
if (manager != null) {
manager.unregisterRcsAvailabilityCallback(subId, callback);
}
}
/**
* Query for the specific capability.
*/
public boolean isCapable(int capability, int radioTech)
throws android.telephony.ims.ImsException {
RcsFeatureManager manager = getFeatureManager();
if (manager == null) {
throw new ImsException("Service is not available",
ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
return manager.isCapable(capability, radioTech);
}
/**
* Query the availability of an IMS RCS capability.
*/
public boolean isAvailable(int capability, int radioTech)
throws android.telephony.ims.ImsException {
RcsFeatureManager manager = getFeatureManager();
if (manager == null) {
throw new ImsException("Service is not available",
ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
return manager.isAvailable(capability, radioTech);
}
/**
* Get the IMS RCS registration technology for this Phone.
*/
public void getRegistrationTech(Consumer<Integer> callback) {
RcsFeatureManager manager = getFeatureManager();
if (manager != null) {
manager.getImsRegistrationTech(callback);
}
callback.accept(ImsRegistrationImplBase.REGISTRATION_TECH_NONE);
}
/**
* Retrieve the current RCS registration state.
*/
public void getRegistrationState(Consumer<Integer> callback) {
callback.accept(mImsRcsRegistrationHelper.getImsRegistrationState());
}
private void updateCapabilities() {
RcsFeatureManager manager = getFeatureManager();
if (manager != null) {
try {
manager.updateCapabilities(mAssociatedSubId);
} catch (ImsException e) {
Log.w(LOG_TAG, "updateCapabilities failed:" + e);
}
}
}
private void setupConnectionToService(RcsFeatureManager manager) throws ImsException {
logd("setupConnectionToService");
// Open persistent listener connection, sends RcsFeature#onFeatureReady.
manager.openConnection();
manager.updateCapabilities(mAssociatedSubId);
manager.registerImsRegistrationCallback(mImsRcsRegistrationHelper.getCallbackBinder());
}
private void removeConnectionToService() {
logd("removeConnectionToService");
RcsFeatureManager manager = getFeatureManager();
if (manager != null) {
manager.unregisterImsRegistrationCallback(
mImsRcsRegistrationHelper.getCallbackBinder());
// Remove persistent listener connection.
manager.releaseConnection();
}
mImsRcsRegistrationHelper.reset();
}
private void updateConnectionStatus(RcsFeatureManager manager) {
synchronized (mLock) {
mFeatureManager = manager;
if (mFeatureManager != null) {
for (Feature c : mFeatures.values()) {
c.onRcsConnected(manager);
}
} else {
for (Feature c : mFeatures.values()) {
c.onRcsDisconnected();
}
}
}
}
private RcsFeatureManager getFeatureManager() {
synchronized (mLock) {
return mFeatureManager;
}
}
/**
* Dump this controller's instance information for usage in dumpsys.
*/
public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
pw.print("slotId=");
pw.println(mSlotId);
pw.print("RegistrationState=");
pw.println(mImsRcsRegistrationHelper.getImsRegistrationState());
pw.print("connected=");
synchronized (mLock) {
pw.println(mFeatureManager != null);
pw.println();
pw.println("RcsFeatureControllers:");
pw.increaseIndent();
for (Feature f : mFeatures.values()) {
f.dump(pw);
pw.println();
}
pw.decreaseIndent();
}
}
private void logd(String log) {
Log.d(LOG_TAG, getLogPrefix().append(log).toString());
}
private void logw(String log) {
Log.w(LOG_TAG, getLogPrefix().append(log).toString());
}
private void loge(String log) {
Log.e(LOG_TAG, getLogPrefix().append(log).toString());
}
private StringBuilder getLogPrefix() {
StringBuilder sb = new StringBuilder("[");
sb.append(mSlotId);
sb.append("] ");
return sb;
}
}