blob: e3f50c348f17d29962bd38a03f4330aee6fbf626 [file] [log] [blame]
/*
* Copyright (c) 2019 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.ims;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.net.Uri;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.telephony.BinderCacheManager;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsService;
import android.telephony.ims.RcsUceAdapter.StackPublishTriggerType;
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.SipDetails;
import android.telephony.ims.aidl.ICapabilityExchangeEventListener;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IImsRcsController;
import android.telephony.ims.aidl.IImsRcsFeature;
import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.aidl.IImsRegistrationCallback;
import android.telephony.ims.aidl.IOptionsRequestCallback;
import android.telephony.ims.aidl.IOptionsResponseCallback;
import android.telephony.ims.aidl.IPublishResponseCallback;
import android.telephony.ims.aidl.ISipTransport;
import android.telephony.ims.aidl.ISubscribeResponseCallback;
import android.telephony.ims.feature.CapabilityChangeRequest;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;
import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.telephony.Rlog;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
/**
* Encapsulates all logic related to the RcsFeature:
* - Updating RcsFeature capabilities.
* - Registering/Unregistering availability/registration callbacks.
* - Querying Registration and Capability information.
*/
public class RcsFeatureManager implements FeatureUpdates {
private static final String TAG = "RcsFeatureManager";
private static boolean DBG = true;
private static final int CAPABILITY_OPTIONS = RcsImsCapabilities.CAPABILITY_TYPE_OPTIONS_UCE;
private static final int CAPABILITY_PRESENCE = RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE;
/**
* The capability exchange event callbacks from the RcsFeature.
*/
public interface CapabilityExchangeEventCallback {
/**
* Triggered by RcsFeature to publish the device's capabilities to the network.
*/
void onRequestPublishCapabilities(@StackPublishTriggerType int publishTriggerType);
/**
* Notify that the devices is unpublished.
*/
void onUnpublish();
/**
* Notify the framework that the ImsService has refreshed the PUBLISH
* internally, which has resulted in a new PUBLISH result.
* <p>
* This method must be called to notify the framework of SUCCESS (200 OK) and FAILURE (300+)
* codes in order to keep the AOSP stack up to date.
*/
void onPublishUpdated(SipDetails details);
/**
* Receive a capabilities request from the remote client.
*/
void onRemoteCapabilityRequest(Uri contactUri,
List<String> remoteCapabilities, IOptionsRequestCallback cb);
}
/*
* Setup the listener to listen to the requests and updates from ImsService.
*/
private ICapabilityExchangeEventListener mCapabilityEventListener =
new ICapabilityExchangeEventListener.Stub() {
@Override
public void onRequestPublishCapabilities(@StackPublishTriggerType int type) {
mCapabilityEventCallback.forEach(
callback -> callback.onRequestPublishCapabilities(type));
}
@Override
public void onUnpublish() {
mCapabilityEventCallback.forEach(callback -> callback.onUnpublish());
}
@Override
public void onPublishUpdated(@NonNull SipDetails details) {
mCapabilityEventCallback.forEach(
callback ->callback.onPublishUpdated(details));
}
@Override
public void onRemoteCapabilityRequest(Uri contactUri,
List<String> remoteCapabilities, IOptionsRequestCallback cb) {
mCapabilityEventCallback.forEach(
callback -> callback.onRemoteCapabilityRequest(
contactUri, remoteCapabilities, cb));
}
};
private final int mSlotId;
private final Context mContext;
private final Set<CapabilityExchangeEventCallback> mCapabilityEventCallback
= new CopyOnWriteArraySet<>();
private final BinderCacheManager<IImsRcsController> mBinderCache
= new BinderCacheManager<>(RcsFeatureManager::getIImsRcsControllerInterface);
@VisibleForTesting
public RcsFeatureConnection mRcsFeatureConnection;
/**
* Use to obtain a FeatureConnector, which will maintain a consistent listener to the
* RcsFeature attached to the specified slotId. If the RcsFeature changes (due to things like
* SIM swap), a new RcsFeatureManager will be delivered to this Listener.
* @param context The Context this connector should use.
* @param slotId The slotId associated with the Listener and requested RcsFeature
* @param listener The listener, which will be used to generate RcsFeatureManager instances.
* @param executor The executor that the Listener callbacks will be called on.
* @param logPrefix The prefix used in logging of the FeatureConnector for notable events.
* @return A FeatureConnector, which will start delivering RcsFeatureManagers as the underlying
* RcsFeature instances become available to the platform.
* @see {@link FeatureConnector#connect()}.
*/
public static FeatureConnector<RcsFeatureManager> getConnector(Context context, int slotId,
FeatureConnector.Listener<RcsFeatureManager> listener, Executor executor,
String logPrefix) {
ArrayList<Integer> filter = new ArrayList<>();
filter.add(ImsFeature.STATE_READY);
return new FeatureConnector<>(context, slotId, RcsFeatureManager::new, logPrefix, filter,
listener, executor);
}
/**
* Use {@link #getConnector} to get an instance of this class.
*/
private RcsFeatureManager(Context context, int slotId) {
mContext = context;
mSlotId = slotId;
}
/**
* Opens a persistent connection to the RcsFeature. This must be called before the RcsFeature
* can be used to communicate.
*/
public void openConnection() throws android.telephony.ims.ImsException {
try {
mRcsFeatureConnection.setCapabilityExchangeEventListener(mCapabilityEventListener);
} catch (RemoteException e){
throw new android.telephony.ims.ImsException("Service is not available.",
android.telephony.ims.ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
/**
* Closes the persistent connection to the RcsFeature. This must be called when this manager
* wishes to no longer be used to communicate with the RcsFeature.
*/
public void releaseConnection() {
try {
mRcsFeatureConnection.setCapabilityExchangeEventListener(null);
} catch (RemoteException e){
// Connection may not be available at this point.
}
mRcsFeatureConnection.close();
mCapabilityEventCallback.clear();
}
/**
* Adds a callback for {@link CapabilityExchangeEventCallback}.
* Note: These callbacks will be sent on the binder thread used to notify the callback.
*/
public void addCapabilityEventCallback(CapabilityExchangeEventCallback listener) {
mCapabilityEventCallback.add(listener);
}
/**
* Removes an existing {@link CapabilityExchangeEventCallback}.
*/
public void removeCapabilityEventCallback(CapabilityExchangeEventCallback listener) {
mCapabilityEventCallback.remove(listener);
}
/**
* Update the capabilities for this RcsFeature.
*/
public void updateCapabilities(int newSubId) throws android.telephony.ims.ImsException {
boolean optionsSupport = isOptionsSupported(newSubId);
boolean presenceSupported = isPresenceSupported(newSubId);
logi("Update capabilities for slot " + mSlotId + " and sub " + newSubId + ": options="
+ optionsSupport+ ", presence=" + presenceSupported);
if (optionsSupport || presenceSupported) {
CapabilityChangeRequest request = new CapabilityChangeRequest();
if (optionsSupport) {
addRcsUceCapability(request, CAPABILITY_OPTIONS);
}
if (presenceSupported) {
addRcsUceCapability(request, CAPABILITY_PRESENCE);
}
sendCapabilityChangeRequest(request);
} else {
disableAllRcsUceCapabilities();
}
}
/**
* 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 android.telephony.ims.ImsException {
try {
mRcsFeatureConnection.addCallbackForSubscription(subId, callback);
} catch (IllegalStateException e) {
loge("registerImsRegistrationCallback error: ", e);
throw new android.telephony.ims.ImsException("Can not register callback",
android.telephony.ims.ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
/**
* Add a {@link RegistrationManager.RegistrationCallback} callback that gets called when IMS
* registration has changed, independent of the subscription it is currently on.
*/
public void registerImsRegistrationCallback(IImsRegistrationCallback callback)
throws android.telephony.ims.ImsException {
try {
mRcsFeatureConnection.addCallback(callback);
} catch (IllegalStateException e) {
loge("registerImsRegistrationCallback error: ", e);
throw new android.telephony.ims.ImsException("Can not register callback",
android.telephony.ims.ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
/**
* Removes a previously registered {@link RegistrationManager.RegistrationCallback} callback
* that is associated with a specific subscription.
*/
public void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback callback) {
mRcsFeatureConnection.removeCallbackForSubscription(subId, callback);
}
/**
* Removes a previously registered {@link RegistrationManager.RegistrationCallback} callback
* that was not associated with a subscription.
*/
public void unregisterImsRegistrationCallback(IImsRegistrationCallback callback) {
mRcsFeatureConnection.removeCallback(callback);
}
/**
* Get the IMS RCS registration technology for this Phone,
* defined in {@link ImsRegistrationImplBase}.
*/
public void getImsRegistrationTech(Consumer<Integer> callback) {
try {
int tech = mRcsFeatureConnection.getRegistrationTech();
callback.accept(tech);
} catch (RemoteException e) {
loge("getImsRegistrationTech error: ", e);
callback.accept(ImsRegistrationImplBase.REGISTRATION_TECH_NONE);
}
}
/**
* Register an ImsCapabilityCallback with RCS service, which will provide RCS availability
* updates.
*/
public void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback)
throws android.telephony.ims.ImsException {
try {
mRcsFeatureConnection.addCallbackForSubscription(subId, callback);
} catch (IllegalStateException e) {
loge("registerRcsAvailabilityCallback: ", e);
throw new android.telephony.ims.ImsException("Can not register callback",
android.telephony.ims.ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
/**
* Remove an registered ImsCapabilityCallback from RCS service.
*/
public void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback callback) {
mRcsFeatureConnection.removeCallbackForSubscription(subId, callback);
}
public boolean isImsServiceCapable(@ImsService.ImsServiceCapability long capabilities)
throws ImsException {
try {
return mRcsFeatureConnection.isCapable(capabilities);
} catch (RemoteException e) {
throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
/**
* @return The SipTransport interface if it exists or {@code null} if it does not exist due to
* the ImsService not supporting it.
*/
public ISipTransport getSipTransport() throws ImsException {
if (!isImsServiceCapable(ImsService.CAPABILITY_SIP_DELEGATE_CREATION)) {
return null;
}
return mRcsFeatureConnection.getSipTransport();
}
public IImsRegistration getImsRegistration() {
return mRcsFeatureConnection.getRegistration();
}
/**
* Query for the specific capability.
*/
public boolean isCapable(
@RcsImsCapabilities.RcsImsCapabilityFlag int capability,
@ImsRegistrationImplBase.ImsRegistrationTech int radioTech)
throws android.telephony.ims.ImsException {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Boolean> capableRef = new AtomicReference<>();
IImsCapabilityCallback callback = new IImsCapabilityCallback.Stub() {
@Override
public void onQueryCapabilityConfiguration(
int resultCapability, int resultRadioTech, boolean enabled) {
if ((capability != resultCapability) || (radioTech != resultRadioTech)) {
return;
}
if (DBG) log("capable result:capability=" + capability + ", enabled=" + enabled);
capableRef.set(enabled);
latch.countDown();
}
@Override
public void onCapabilitiesStatusChanged(int config) {
// Don't handle it
}
@Override
public void onChangeCapabilityConfigurationError(int capability, int radioTech,
int reason) {
// Don't handle it
}
};
try {
if (DBG) log("Query capability: " + capability + ", radioTech=" + radioTech);
mRcsFeatureConnection.queryCapabilityConfiguration(capability, radioTech, callback);
return awaitResult(latch, capableRef);
} catch (RemoteException e) {
loge("isCapable error: ", e);
throw new android.telephony.ims.ImsException("Can not determine capabilities",
android.telephony.ims.ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
private static <T> T awaitResult(CountDownLatch latch, AtomicReference<T> resultRef) {
try {
latch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return resultRef.get();
}
/**
* Query the availability of an IMS RCS capability.
*/
public boolean isAvailable(@RcsImsCapabilities.RcsImsCapabilityFlag int capability,
@ImsRegistrationImplBase.ImsRegistrationTech int radioTech)
throws android.telephony.ims.ImsException {
try {
if (mRcsFeatureConnection.getRegistrationTech() != radioTech) {
return false;
}
int currentStatus = mRcsFeatureConnection.queryCapabilityStatus();
return new RcsImsCapabilities(currentStatus).isCapable(capability);
} catch (RemoteException e) {
loge("isAvailable error: ", e);
throw new android.telephony.ims.ImsException("Can not determine availability",
android.telephony.ims.ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
/**
* Add UCE capabilities with given type.
* @param capability the specific RCS UCE capability wants to enable
*/
public void addRcsUceCapability(CapabilityChangeRequest request,
@RcsImsCapabilities.RcsImsCapabilityFlag int capability) {
request.addCapabilitiesToEnableForTech(capability,
ImsRegistrationImplBase.REGISTRATION_TECH_NR);
request.addCapabilitiesToEnableForTech(capability,
ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
request.addCapabilitiesToEnableForTech(capability,
ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
}
public void requestPublication(String pidfXml, IPublishResponseCallback responseCallback)
throws RemoteException {
mRcsFeatureConnection.requestPublication(pidfXml, responseCallback);
}
public void requestCapabilities(List<Uri> uris, ISubscribeResponseCallback c)
throws RemoteException {
mRcsFeatureConnection.requestCapabilities(uris, c);
}
public void sendOptionsCapabilityRequest(Uri contactUri, List<String> myCapabilities,
IOptionsResponseCallback callback) throws RemoteException {
mRcsFeatureConnection.sendOptionsCapabilityRequest(contactUri, myCapabilities, callback);
}
/**
* Disable all of the UCE capabilities.
*/
private void disableAllRcsUceCapabilities() throws android.telephony.ims.ImsException {
final int techNr = ImsRegistrationImplBase.REGISTRATION_TECH_NR;
final int techLte = ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
final int techIWlan = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
CapabilityChangeRequest request = new CapabilityChangeRequest();
request.addCapabilitiesToDisableForTech(CAPABILITY_OPTIONS, techNr);
request.addCapabilitiesToDisableForTech(CAPABILITY_OPTIONS, techLte);
request.addCapabilitiesToDisableForTech(CAPABILITY_OPTIONS, techIWlan);
request.addCapabilitiesToDisableForTech(CAPABILITY_PRESENCE, techNr);
request.addCapabilitiesToDisableForTech(CAPABILITY_PRESENCE, techLte);
request.addCapabilitiesToDisableForTech(CAPABILITY_PRESENCE, techIWlan);
sendCapabilityChangeRequest(request);
}
private void sendCapabilityChangeRequest(CapabilityChangeRequest request)
throws android.telephony.ims.ImsException {
try {
if (DBG) log("sendCapabilityChangeRequest: " + request);
mRcsFeatureConnection.changeEnabledCapabilities(request, null);
} catch (RemoteException e) {
throw new android.telephony.ims.ImsException("Can not connect to service",
android.telephony.ims.ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
private boolean isOptionsSupported(int subId) {
return isCapabilityTypeSupported(mContext, subId, CAPABILITY_OPTIONS);
}
private boolean isPresenceSupported(int subId) {
return isCapabilityTypeSupported(mContext, subId, CAPABILITY_PRESENCE);
}
/*
* Check if the given type of capability is supported.
*/
private static boolean isCapabilityTypeSupported(
Context context, int subId, int capabilityType) {
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
Log.e(TAG, "isCapabilityTypeSupported: Invalid subId=" + subId);
return false;
}
CarrierConfigManager configManager =
(CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
if (configManager == null) {
Log.e(TAG, "isCapabilityTypeSupported: CarrierConfigManager is null, " + subId);
return false;
}
PersistableBundle b = configManager.getConfigForSubId(subId);
if (b == null) {
Log.e(TAG, "isCapabilityTypeSupported: PersistableBundle is null, " + subId);
return false;
}
if (capabilityType == CAPABILITY_OPTIONS) {
return b.getBoolean(CarrierConfigManager.KEY_USE_RCS_SIP_OPTIONS_BOOL, false);
} else if (capabilityType == CAPABILITY_PRESENCE) {
return b.getBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL, false);
}
return false;
}
@Override
public void registerFeatureCallback(int slotId, IImsServiceFeatureCallback cb) {
IImsRcsController controller = mBinderCache.listenOnBinder(cb, () -> {
try {
cb.imsFeatureRemoved(
FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE);
} catch (RemoteException ignore) {} // This is local.
});
try {
if (controller == null) {
Log.e(TAG, "registerRcsFeatureListener: IImsRcsController is null");
cb.imsFeatureRemoved(FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE);
return;
}
controller.registerRcsFeatureCallback(slotId, cb);
} catch (ServiceSpecificException e) {
try {
switch (e.errorCode) {
case ImsException.CODE_ERROR_UNSUPPORTED_OPERATION:
cb.imsFeatureRemoved(FeatureConnector.UNAVAILABLE_REASON_IMS_UNSUPPORTED);
break;
default: {
cb.imsFeatureRemoved(
FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE);
}
}
} catch (RemoteException ignore) {} // Already dead anyway if this happens.
} catch (RemoteException e) {
try {
cb.imsFeatureRemoved(FeatureConnector.UNAVAILABLE_REASON_SERVER_UNAVAILABLE);
} catch (RemoteException ignore) {} // Already dead if this happens.
}
}
@Override
public void unregisterFeatureCallback(IImsServiceFeatureCallback cb) {
try {
IImsRcsController imsRcsController = mBinderCache.removeRunnable(cb);
if (imsRcsController != null) {
imsRcsController.unregisterImsFeatureCallback(cb);
}
} catch (RemoteException e) {
// This means that telephony died, so do not worry about it.
Rlog.e(TAG, "unregisterImsFeatureCallback (RCS), RemoteException: "
+ e.getMessage());
}
}
private IImsRcsController getIImsRcsController() {
return mBinderCache.getBinder();
}
private static IImsRcsController getIImsRcsControllerInterface() {
IBinder binder = TelephonyFrameworkInitializer
.getTelephonyServiceManager()
.getTelephonyImsServiceRegisterer()
.get();
IImsRcsController c = IImsRcsController.Stub.asInterface(binder);
return c;
}
@Override
public void associate(ImsFeatureContainer c, int subId) {
IImsRcsFeature f = IImsRcsFeature.Stub.asInterface(c.imsFeature);
mRcsFeatureConnection = new RcsFeatureConnection(mContext, mSlotId, subId, f, c.imsConfig,
c.imsRegistration, c.sipTransport);
}
@Override
public void invalidate() {
mRcsFeatureConnection.onRemovedOrDied();
}
@Override
public void updateFeatureState(int state) {
mRcsFeatureConnection.updateFeatureState(state);
}
@Override
public void updateFeatureCapabilities(long capabilities) {
mRcsFeatureConnection.updateFeatureCapabilities(capabilities);
}
public IImsConfig getConfig() {
return mRcsFeatureConnection.getConfig();
}
/**
* @return the subscription ID associated with this ImsService connection.
*/
public int getSubId() {
return mRcsFeatureConnection.getSubId();
}
private void log(String s) {
Rlog.d(TAG + " [" + mSlotId + "]", s);
}
private void logi(String s) {
Rlog.i(TAG + " [" + mSlotId + "]", s);
}
private void loge(String s) {
Rlog.e(TAG + " [" + mSlotId + "]", s);
}
private void loge(String s, Throwable t) {
Rlog.e(TAG + " [" + mSlotId + "]", s, t);
}
}