blob: 30edca1b012b619d0f6fe661b36b342c11fdd156 [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.telephony.ims.DelegateRegistrationState;
import android.telephony.ims.DelegateRequest;
import android.telephony.ims.FeatureTagState;
import android.telephony.ims.SipDelegateConnection;
import android.telephony.ims.SipDelegateManager;
import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.aidl.ISipDelegate;
import android.telephony.ims.aidl.ISipDelegateConnectionStateCallback;
import android.telephony.ims.aidl.ISipDelegateMessageCallback;
import android.telephony.ims.aidl.ISipTransport;
import android.telephony.ims.stub.DelegateConnectionStateCallback;
import android.telephony.ims.stub.SipDelegate;
import android.util.ArraySet;
import android.util.LocalLog;
import android.util.Log;
import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.stream.Collectors;
/**
* Created when an IMS application wishes to open up a {@link SipDelegateConnection} and manages the
* resulting {@link SipDelegate} that may be created on the ImsService side.
*/
public class SipDelegateController {
static final String LOG_TAG = "SipDelegateC";
private class BinderConnectionFactory implements DelegateBinderStateManager.Factory {
private final ISipTransport mSipTransportImpl;
private final IImsRegistration mImsRegistrationImpl;
BinderConnectionFactory(ISipTransport transport, IImsRegistration registration) {
mSipTransportImpl = transport;
mImsRegistrationImpl = registration;
}
@Override
public DelegateBinderStateManager create(int subId,
DelegateRequest requestedConfig, Set<FeatureTagState> transportDeniedTags,
Executor executor, List<DelegateBinderStateManager.StateCallback> stateCallbacks) {
// We should not actually create a SipDelegate in this case.
if (requestedConfig.getFeatureTags().isEmpty()) {
return new SipDelegateBinderConnectionStub(transportDeniedTags, executor,
stateCallbacks);
}
return new SipDelegateBinderConnection(mSubId, mSipTransportImpl, mImsRegistrationImpl,
requestedConfig, transportDeniedTags, mExecutorService, stateCallbacks);
}
}
private final int mSubId;
private final String mPackageName;
private final DelegateRequest mInitialRequest;
private final ScheduledExecutorService mExecutorService;
private final MessageTransportWrapper mMessageTransportWrapper;
private final DelegateStateTracker mDelegateStateTracker;
private final DelegateBinderStateManager.Factory mBinderConnectionFactory;
private final LocalLog mLocalLog = new LocalLog(SipTransportController.LOG_SIZE);
private DelegateBinderStateManager mBinderConnection;
private Set<String> mTrackedFeatureTags;
public SipDelegateController(int subId, DelegateRequest initialRequest, String packageName,
ISipTransport transportImpl, IImsRegistration registrationImpl,
ScheduledExecutorService executorService,
ISipDelegateConnectionStateCallback stateCallback,
ISipDelegateMessageCallback messageCallback) {
mSubId = subId;
mPackageName = packageName;
mInitialRequest = initialRequest;
mExecutorService = executorService;
mBinderConnectionFactory = new BinderConnectionFactory(transportImpl, registrationImpl);
mMessageTransportWrapper = new MessageTransportWrapper(mSubId, executorService,
messageCallback);
mDelegateStateTracker = new DelegateStateTracker(mSubId, stateCallback,
mMessageTransportWrapper.getDelegateConnection());
}
/**
* Inject dependencies for testing only.
*/
@VisibleForTesting
public SipDelegateController(int subId, DelegateRequest initialRequest, String packageName,
ScheduledExecutorService executorService,
MessageTransportWrapper messageTransportWrapper,
DelegateStateTracker delegateStateTracker,
DelegateBinderStateManager.Factory connectionFactory) {
mSubId = subId;
mInitialRequest = initialRequest;
mPackageName = packageName;
mExecutorService = executorService;
mMessageTransportWrapper = messageTransportWrapper;
mDelegateStateTracker = delegateStateTracker;
mBinderConnectionFactory = connectionFactory;
}
/**
* @return The InitialRequest from the IMS application. The feature tags that are actually set
* up may differ from this request based on the state of this controller.
*/
public DelegateRequest getInitialRequest() {
return mInitialRequest;
}
/**
* @return The package name of the IMS application associated with this SipDelegateController.
*/
public String getPackageName() {
return mPackageName;
}
/**
* @return The ImsService's SIP delegate binder impl associated with this controller.
*/
public ISipDelegate getSipDelegateInterface() {
return mMessageTransportWrapper.getDelegateConnection();
}
/**
* @return The IMS app's message callback binder.
*/
public ISipDelegateMessageCallback getAppMessageCallback() {
return mMessageTransportWrapper.getAppMessageCallback();
}
/**
* Create the underlying SipDelegate.
* <p>
* This may not happen instantly, The CompletableFuture returned will not complete until
* {@link DelegateConnectionStateCallback#onCreated(SipDelegateConnection)} is called by the
* SipDelegate or DelegateStateTracker state is updated in the case that all requested features
* were denied.
* @return A CompletableFuture that will complete once the SipDelegate has been created. If true
* is returned, the SipDelegate has been created successfully. If false, the ImsService is not
* reachable and the process should be aborted.
*/
public CompletableFuture<Boolean> create(Set<String> supportedSet,
Set<FeatureTagState> deniedSet) {
logi("create, supported: " + supportedSet + ", denied: " + deniedSet);
mTrackedFeatureTags = supportedSet;
DelegateBinderStateManager connection =
createBinderConnection(supportedSet, deniedSet);
CompletableFuture<Pair<ISipDelegate, Set<FeatureTagState>>> pendingCreate =
createSipDelegate(connection);
// May need to implement special case handling where SipDelegate denies all in supportedSet,
// however that should be a very rare case. For now, if that happens, just keep the
// SipDelegate bound.
// use thenApply here because we need this to happen on the same thread that it was called
// on in order to ensure ordering of onCreated being called, followed by registration
// state changed. If not, this is subject to race conditions where registered is queued
// before the async processing of this future.
return pendingCreate.thenApply((resultPair) -> {
if (resultPair == null) {
logw("create: resultPair returned null");
return false;
}
mBinderConnection = connection;
logi("create: created, delegate denied: " + resultPair.second);
Set<String> allowedTags = new ArraySet<>(supportedSet);
// Start with the supported set and remove all tags that were denied.
allowedTags.removeAll(resultPair.second.stream().map(FeatureTagState::getFeatureTag)
.collect(Collectors.toSet()));
mMessageTransportWrapper.openTransport(resultPair.first, allowedTags,
resultPair.second);
mDelegateStateTracker.sipDelegateConnected(resultPair.second);
return true;
});
}
/**
* Modify the SipTransport to reflect the new Feature Tag set that the IMS application has
* access to.
* <p>
* This involves the following operations if the new supported tag set does not match the
* the existing set:
* 1) destroy the existing underlying SipDelegate. If there are SIP Dialogs that are active
* on the SipDelegate that is pending to be destroyed, we must move the feature tags into a
* deregistering state via
* {@link DelegateRegistrationState#DEREGISTERING_REASON_FEATURE_TAGS_CHANGING} to signal to the
* IMS application to close all dialogs before the operation can proceed. If any outgoing
* out-of-dialog messages are sent at this time, they will also fail with reason
* {@link SipDelegateManager#MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION}.
* 2) create a new underlying SipDelegate and notify trackers, allowing the transport to
* re-open.
* @param newSupportedSet The new supported set of feature tags that the SipDelegate should
* be opened for.
* @param deniedSet The new set of tags that have been denied as well as the reason for the
* denial to be reported back to the IMS Application.
* @return A CompletableFuture containing the pending operation that will change the supported
* feature tags. Any operations to change the supported feature tags of the associated
* SipDelegate after this should not happen until this pending operation completes. Will
* complete with {@code true} if the operation was successful or {@code false} if the
* IMS service was unreachable.
*/
public CompletableFuture<Boolean> changeSupportedFeatureTags(Set<String> newSupportedSet,
Set<FeatureTagState> deniedSet) {
logi("Received feature tag set change, old: [" + mTrackedFeatureTags + "], new: "
+ newSupportedSet + ",denied: [" + deniedSet + "]");
if (mTrackedFeatureTags != null && mTrackedFeatureTags.equals(newSupportedSet)) {
logi("changeSupportedFeatureTags: no change, returning");
return CompletableFuture.completedFuture(true);
}
mTrackedFeatureTags = newSupportedSet;
// Next perform the destroy operation.
CompletableFuture<Integer> pendingDestroy = destroySipDelegate(false/*force*/,
SipDelegateManager.MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION,
SipDelegateManager.MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION,
DelegateRegistrationState.DEREGISTERING_REASON_FEATURE_TAGS_CHANGING,
SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP);
// Next perform the create operation with the new set of supported feature tags.
return pendingDestroy.thenComposeAsync((reasonFromService) -> {
logi("changeSupportedFeatureTags: destroy stage complete, reason reported: "
+ reasonFromService);
return create(newSupportedSet, deniedSet);
}, mExecutorService);
}
/**
* Destroy this SipDelegate. This controller should be disposed of after this method is
* called.
* <p>
* This may not happen instantly if there are SIP Dialogs that are active on this SipDelegate.
* In this case, the CompletableFuture will not complete until
* {@link DelegateConnectionStateCallback#onDestroyed(int)} is called by the SipDelegate.
* @param force If set true, we will close the transport immediately and call
* {@link SipDelegate#closeDialog(String)} on any open dialogs. If false, we will wait for the
* SIP Dialogs to close or the close timer to timeout before destroying the underlying
* SipDelegate.
* @param destroyReason The reason for why this SipDelegate is being destroyed.
* @return A CompletableFuture that will complete once the SipDelegate has been destroyed.
*/
public CompletableFuture<Integer> destroy(boolean force, int destroyReason) {
logi("destroy, forced " + force + ", destroyReason: " + destroyReason);
CompletableFuture<Integer> pendingOperationComplete =
destroySipDelegate(force, SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED,
getMessageFailReasonFromDestroyReason(destroyReason),
DelegateRegistrationState.DEREGISTERING_REASON_DESTROY_PENDING,
destroyReason);
return pendingOperationComplete.thenApplyAsync((reasonFromDelegate) -> {
logi("destroy, operation complete, notifying trackers, reason " + reasonFromDelegate);
mDelegateStateTracker.sipDelegateDestroyed(reasonFromDelegate);
return reasonFromDelegate;
}, mExecutorService);
};
/**
* The IMS application is notifying the ImsService that it has received a response to a request
* that will require that the IMS registration be torn down and brought back up.
*<p>
* See {@link SipDelegateManager#triggerFullNetworkRegistration} for more information.
*/
public void triggerFullNetworkRegistration(int sipCode, String sipReason) {
logi("triggerFullNetworkRegistration, code=" + sipCode + ", reason=" + sipReason);
if (mBinderConnection != null) {
mBinderConnection.triggerFullNetworkRegistration(sipCode, sipReason);
} else {
logw("triggerFullNetworkRegistration called when binder connection is null");
}
}
private static int getMessageFailReasonFromDestroyReason(int destroyReason) {
switch (destroyReason) {
case SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD:
return SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD;
case SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP:
case SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_USER_DISABLED_RCS:
return SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED;
default:
return SipDelegateManager.MESSAGE_FAILURE_REASON_UNKNOWN;
}
}
/**
* @param force If set true, we will close the transport immediately and call
* {@link SipDelegate#closeDialog(String)} on any open dialogs. If false, we will wait for the
* SIP Dialogs to close or the close timer to timeout before destroying the underlying
* SipDelegate.
* @param messageDestroyingReason The reason to send back to the IMS application in the case
* that a new outgoing SIP message is sent that is out-of-dialog while the message
* transport is closing.
* @param messageDestroyedReason The reason to send back to the IMS application in the case
* that a new outgoing SIP message is sent once the underlying transport is closed.
* @param deregisteringReason The deregistering state reported to the IMS application for all
* registered feature tags.
* @param delegateDestroyedReason The reason to send to the underlying SipDelegate that is being
* destroyed.
* @return A CompletableFuture containing the reason from the SipDelegate for why it was
* destroyed.
*/
private CompletableFuture<Integer> destroySipDelegate(boolean force,
int messageDestroyingReason, int messageDestroyedReason, int deregisteringReason,
int delegateDestroyedReason) {
if (mBinderConnection == null) {
logi("destroySipDelegate, called when binder connection is already null");
return CompletableFuture.completedFuture(delegateDestroyedReason);
}
// First, bring down the message transport.
CompletableFuture<Boolean> pendingTransportClosed = new CompletableFuture<>();
if (force) {
logi("destroySipDelegate, forced");
mMessageTransportWrapper.close(messageDestroyedReason);
pendingTransportClosed.complete(true);
} else {
mMessageTransportWrapper.closeGracefully(messageDestroyingReason,
messageDestroyedReason, pendingTransportClosed::complete);
}
// Do not send an intermediate pending state to app if there are no open SIP dialogs to
// worry about.
if (!pendingTransportClosed.isDone()) {
mDelegateStateTracker.sipDelegateChanging(deregisteringReason);
} else {
logi("destroySipDelegate, skip DEREGISTERING_REASON_DESTROY_PENDING");
}
// Next, destroy the SipDelegate.
return pendingTransportClosed.thenComposeAsync((wasGraceful) -> {
logi("destroySipDelegate, transport gracefully closed = " + wasGraceful);
CompletableFuture<Integer> pendingDestroy = new CompletableFuture<>();
mBinderConnection.destroy(delegateDestroyedReason, pendingDestroy::complete);
return pendingDestroy;
}, mExecutorService);
}
/**
* @return a CompletableFuture that returns a Pair containing SipDelegate Binder interface as
* well as rejected feature tags or a {@code null} Pair instance if the ImsService is not
* available.
*/
private CompletableFuture<Pair<ISipDelegate, Set<FeatureTagState>>> createSipDelegate(
DelegateBinderStateManager connection) {
CompletableFuture<Pair<ISipDelegate, Set<FeatureTagState>>> createdFuture =
new CompletableFuture<>();
boolean isStarted = connection.create(mMessageTransportWrapper.getMessageCallback(),
(delegate, delegateDeniedTags) ->
createdFuture.complete(new Pair<>(delegate, delegateDeniedTags)));
if (!isStarted) {
logw("Couldn't create binder connection, ImsService is not available.");
connection.destroy(SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD, null);
return CompletableFuture.completedFuture(null);
}
return createdFuture;
}
private DelegateBinderStateManager createBinderConnection(Set<String> supportedSet,
Set<FeatureTagState> deniedSet) {
List<DelegateBinderStateManager.StateCallback> stateCallbacks = new ArrayList<>(2);
stateCallbacks.add(mDelegateStateTracker);
stateCallbacks.add(mMessageTransportWrapper);
return mBinderConnectionFactory.create(mSubId,
new DelegateRequest(supportedSet), deniedSet, mExecutorService, stateCallbacks);
}
/**
* Write the current state of this controller in String format using the PrintWriter provided
* for dumpsys.
*/
public void dump(PrintWriter printWriter) {
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
pw.println("SipDelegateController" + "[" + mSubId + "]:");
pw.increaseIndent();
pw.println("Most recent logs:");
pw.increaseIndent();
mLocalLog.dump(pw);
pw.decreaseIndent();
pw.println();
pw.println("DelegateStateTracker:");
pw.increaseIndent();
mDelegateStateTracker.dump(pw);
pw.decreaseIndent();
pw.println();
pw.println("MessageStateTracker:");
pw.increaseIndent();
mMessageTransportWrapper.dump(pw);
pw.decreaseIndent();
pw.decreaseIndent();
}
private void logi(String log) {
Log.w(SipTransportController.LOG_TAG, LOG_TAG + "[" + mSubId + "] " + log);
mLocalLog.log("[I] " + log);
}
private void logw(String log) {
Log.w(SipTransportController.LOG_TAG, LOG_TAG + "[" + mSubId + "] " + log);
mLocalLog.log("[W] " + log);
}
}