blob: 101aa810c3d7f1a590d63842fe5112b2ed837391 [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.ims.rcs.uce.presence.publish;
import static android.telephony.ims.RcsUceAdapter.PUBLISH_STATE_OK;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.telephony.CarrierConfigManager;
import android.telephony.ims.ImsException;
import android.telephony.ims.PublishAttributes;
import android.telephony.ims.RcsContactPresenceTuple;
import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.RcsContactUceCapability.CapabilityMechanism;
import android.telephony.ims.RcsUceAdapter;
import android.telephony.ims.RcsUceAdapter.PublishState;
import android.telephony.ims.SipDetails;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities;
import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
import android.util.Log;
import com.android.ims.RcsFeatureManager;
import com.android.ims.rcs.uce.UceController;
import com.android.ims.rcs.uce.UceController.UceControllerCallback;
import com.android.ims.rcs.uce.UceDeviceState;
import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult;
import com.android.ims.rcs.uce.UceStatsWriter;
import com.android.ims.rcs.uce.util.UceUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* The implementation of PublishController.
*/
public class PublishControllerImpl implements PublishController {
private static final String LOG_TAG = UceUtils.getLogPrefix() + "PublishController";
/**
* Used to inject PublishProcessor instances for testing.
*/
@VisibleForTesting
public interface PublishProcessorFactory {
PublishProcessor createPublishProcessor(Context context, int subId,
DeviceCapabilityInfo capabilityInfo, PublishControllerCallback callback);
}
/**
* Used to inject DeviceCapabilityListener instances for testing.
*/
@VisibleForTesting
public interface DeviceCapListenerFactory {
DeviceCapabilityListener createDeviceCapListener(Context context, int subId,
DeviceCapabilityInfo capInfo, PublishControllerCallback callback,
UceStatsWriter uceStatsWriter);
}
private final int mSubId;
private final Context mContext;
private final LocalLog mLocalLog = new LocalLog(UceUtils.LOG_SIZE);
private PublishHandler mPublishHandler;
private volatile boolean mIsDestroyedFlag;
private volatile boolean mReceivePublishFromService;
private volatile RcsFeatureManager mRcsFeatureManager;
private final UceControllerCallback mUceCtrlCallback;
private final UceStatsWriter mUceStatsWriter;
// The capability type that the device is using.
private @RcsImsCapabilityFlag int mCapabilityType;
// The device publish state
@VisibleForTesting
public @PublishState int mLastPublishState;
// The device publish state to support the newly added publish state
@VisibleForTesting
public @PublishState int mCurrentPublishState;
// The timestamp of updating the publish state
private Instant mPublishStateUpdatedTime = Instant.now();
// The last PIDF XML used in the publish
private String mPidfXml;
// The callbacks to notify publish state changed.
private RemoteCallbackList<IRcsUcePublishStateCallback> mPublishStateCallbacks;
private final Object mPublishStateLock = new Object();
// The information of the device's capabilities.
private DeviceCapabilityInfo mDeviceCapabilityInfo;
// The processor of publishing device's capabilities.
private PublishProcessor mPublishProcessor;
private PublishProcessorFactory mPublishProcessorFactory = (context, subId, capInfo, callback)
-> new PublishProcessor(context, subId, capInfo, callback);
// The listener to listen to the device's capabilities changed.
private DeviceCapabilityListener mDeviceCapListener;
private DeviceCapListenerFactory mDeviceCapListenerFactory = (context, subId, capInfo, callback,
uceStatsWriter)
-> new DeviceCapabilityListener(context, subId, capInfo, callback, uceStatsWriter);
// Listen to the RCS availability status changed.
private final IImsCapabilityCallback mRcsCapabilitiesCallback =
new IImsCapabilityCallback.Stub() {
@Override
public void onQueryCapabilityConfiguration(
int resultCapability, int resultRadioTech, boolean enabled) {
}
@Override
public void onCapabilitiesStatusChanged(@RcsImsCapabilityFlag int capabilities) {
logd("onCapabilitiesStatusChanged: " + capabilities);
mPublishHandler.sendRcsCapabilitiesStatusChangedMsg(capabilities);
}
@Override
public void onChangeCapabilityConfigurationError(int capability, int radioTech,
int reason) {
}
};
public PublishControllerImpl(Context context, int subId, UceControllerCallback callback,
Looper looper) {
mSubId = subId;
mContext = context;
mUceCtrlCallback = callback;
mUceStatsWriter = UceStatsWriter.getInstance();
logi("create");
initPublishController(looper);
}
@VisibleForTesting
public PublishControllerImpl(Context context, int subId, UceControllerCallback c,
Looper looper, DeviceCapListenerFactory deviceCapFactory,
PublishProcessorFactory processorFactory, UceStatsWriter instance) {
mSubId = subId;
mContext = context;
mUceCtrlCallback = c;
mDeviceCapListenerFactory = deviceCapFactory;
mPublishProcessorFactory = processorFactory;
mUceStatsWriter = instance;
initPublishController(looper);
}
private void initPublishController(Looper looper) {
mCapabilityType = PublishUtils.getCapabilityType(mContext, mSubId);
mCurrentPublishState = getInitialPublishState(mCapabilityType);
mLastPublishState = mCurrentPublishState;
mPublishStateCallbacks = new RemoteCallbackList<>();
mPublishHandler = new PublishHandler(this, looper);
String[] serviceDescFeatureTagMap = getCarrierServiceDescriptionFeatureTagMap();
mDeviceCapabilityInfo = new DeviceCapabilityInfo(mSubId, serviceDescFeatureTagMap);
initPublishProcessor();
initDeviceCapabilitiesListener();
// Turn on the listener to listen to the device changes.
mDeviceCapListener.initialize();
logd("initPublishController completed: capabilityType=" + mCapabilityType +
", publishState=" + mCurrentPublishState);
}
/**
* Get the initial publish state according to the given capability type.
* <p>
* The default publish state is NOT_PUBLISH when the capability type is PRESENCE.
* The default publish state is OK when the capability type is SIP OPTIONS.
* Otherwise, the default initial value is ERROR.
*/
private int getInitialPublishState(@RcsImsCapabilityFlag int capabilityType) {
if (capabilityType == RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE) {
return RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED;
} else if (capabilityType == RcsImsCapabilities.CAPABILITY_TYPE_OPTIONS_UCE) {
return PUBLISH_STATE_OK;
} else {
return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
}
}
private void initPublishProcessor() {
mPublishProcessor = mPublishProcessorFactory.createPublishProcessor(mContext, mSubId,
mDeviceCapabilityInfo, mPublishControllerCallback);
}
private void initDeviceCapabilitiesListener() {
mDeviceCapListener = mDeviceCapListenerFactory.createDeviceCapListener(mContext, mSubId,
mDeviceCapabilityInfo, mPublishControllerCallback, mUceStatsWriter);
}
@Override
public void onRcsConnected(RcsFeatureManager manager) {
logd("onRcsConnected");
mPublishHandler.sendRcsConnectedMsg(manager);
}
@Override
public void onRcsDisconnected() {
logd("onRcsDisconnected");
mPublishHandler.sendRcsDisconnectedMsg();
}
@Override
public void onDestroy() {
logi("onDestroy");
mPublishHandler.sendDestroyedMsg();
}
@Override
public void onCarrierConfigChanged() {
logi("onCarrierConfigChanged");
mPublishHandler.sendCarrierConfigChangedMsg();
}
@Override
public int getUcePublishState(boolean isSupportPublishingState) {
synchronized (mPublishStateLock) {
if (mIsDestroyedFlag) {
return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
}
if (isSupportPublishingState) {
// in that case, the caller is support Build.VERSION_CODES.S
// return the current state that is including newly added publishing state.
return mCurrentPublishState;
} else {
if (mCurrentPublishState == RcsUceAdapter.PUBLISH_STATE_PUBLISHING) {
return mLastPublishState;
}
return mCurrentPublishState;
}
}
}
@Override
public RcsContactUceCapability addRegistrationOverrideCapabilities(Set<String> featureTags) {
if (mDeviceCapabilityInfo.addRegistrationOverrideCapabilities(featureTags)) {
mPublishHandler.sendPublishMessage(PublishController.PUBLISH_TRIGGER_OVERRIDE_CAPS);
}
return mDeviceCapabilityInfo.getDeviceCapabilities(
RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE, mContext);
}
@Override
public RcsContactUceCapability removeRegistrationOverrideCapabilities(Set<String> featureTags) {
if (mDeviceCapabilityInfo.removeRegistrationOverrideCapabilities(featureTags)) {
mPublishHandler.sendPublishMessage(PublishController.PUBLISH_TRIGGER_OVERRIDE_CAPS);
}
return mDeviceCapabilityInfo.getDeviceCapabilities(
RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE, mContext);
}
@Override
public RcsContactUceCapability clearRegistrationOverrideCapabilities() {
if (mDeviceCapabilityInfo.clearRegistrationOverrideCapabilities()) {
mPublishHandler.sendPublishMessage(PublishController.PUBLISH_TRIGGER_OVERRIDE_CAPS);
}
return mDeviceCapabilityInfo.getDeviceCapabilities(
RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE, mContext);
}
@Override
public RcsContactUceCapability getLatestRcsContactUceCapability() {
return mDeviceCapabilityInfo.getDeviceCapabilities(
RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE, mContext);
}
@Override
public String getLastPidfXml() {
return mPidfXml;
}
/**
* Register a {@link PublishStateCallback} to listen to the published state changed.
*/
@Override
public void registerPublishStateCallback(@NonNull IRcsUcePublishStateCallback c,
boolean supportPublishingState) {
synchronized (mPublishStateLock) {
if (mIsDestroyedFlag) return;
mPublishStateCallbacks.register(c, Boolean.valueOf(supportPublishingState));
logd("registerPublishStateCallback: size="
+ mPublishStateCallbacks.getRegisteredCallbackCount());
}
// Notify the current publish state
mPublishHandler.sendNotifyCurrentPublishStateMessage(c, supportPublishingState);
}
/**
* Removes an existing {@link PublishStateCallback}.
*/
@Override
public void unregisterPublishStateCallback(@NonNull IRcsUcePublishStateCallback c) {
synchronized (mPublishStateLock) {
if (mIsDestroyedFlag) return;
mPublishStateCallbacks.unregister(c);
logd("unregisterPublishStateCallback:mPublishStateCallbacks: size="
+ mPublishStateCallbacks.getRegisteredCallbackCount());
}
}
@Override
public void setupResetDeviceStateTimer(long resetAfterSec) {
logd("setupResetDeviceStateTimer: resetAfterSec=" + resetAfterSec);
mPublishHandler.sendResetDeviceStateTimerMessage(resetAfterSec);
}
@Override
public void clearResetDeviceStateTimer() {
logd("clearResetDeviceStateTimer");
mPublishHandler.clearResetDeviceStateTimer();
}
// Clear all the publish state callbacks since the publish controller instance is destroyed.
private void clearPublishStateCallbacks() {
synchronized (mPublishStateLock) {
logd("clearPublishStateCallbacks");
final int lastIndex = mPublishStateCallbacks.getRegisteredCallbackCount() - 1;
for (int index = lastIndex; index >= 0; index--) {
IRcsUcePublishStateCallback callback =
mPublishStateCallbacks.getRegisteredCallbackItem(index);
mPublishStateCallbacks.unregister(callback);
}
}
}
/**
* Notify that the device's capabilities has been unpublished from the network.
*/
@Override
public void onUnpublish() {
logd("onUnpublish");
if (mIsDestroyedFlag) return;
mPublishHandler.sendUnpublishedMessage(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED);
}
/**
* Notify that the device's publish status have been changed.
*/
@Override
public void onPublishUpdated(@NonNull SipDetails details) {
if (mIsDestroyedFlag) return;
mPublishHandler.sendPublishUpdatedMessage(details);
}
@Override
public RcsContactUceCapability getDeviceCapabilities(@CapabilityMechanism int mechanism) {
return mDeviceCapabilityInfo.getDeviceCapabilities(mechanism, mContext);
}
// The local publish request from the sub-components which interact with PublishController.
private final PublishControllerCallback mPublishControllerCallback =
new PublishControllerCallback() {
@Override
public void requestPublishFromInternal(@PublishTriggerType int type) {
logd("requestPublishFromInternal: type=" + type);
mPublishHandler.sendPublishMessage(type);
}
@Override
public void onRequestCommandError(PublishRequestResponse requestResponse) {
logd("onRequestCommandError: taskId=" + requestResponse.getTaskId()
+ ", time=" + requestResponse.getResponseTimestamp());
mPublishHandler.sendRequestCommandErrorMessage(requestResponse);
}
@Override
public void onRequestNetworkResp(PublishRequestResponse requestResponse) {
logd("onRequestNetworkResp: taskId=" + requestResponse.getTaskId()
+ ", time=" + requestResponse.getResponseTimestamp());
mPublishHandler.sendRequestNetworkRespMessage(requestResponse);
}
@Override
public void setupRequestCanceledTimer(long taskId, long delay) {
logd("setupRequestCanceledTimer: taskId=" + taskId + ", delay=" + delay);
mPublishHandler.sendRequestCanceledTimerMessage(taskId, delay);
}
@Override
public void clearRequestCanceledTimer() {
logd("clearRequestCanceledTimer");
mPublishHandler.clearRequestCanceledTimer();
}
@Override
public void updatePublishRequestResult(@PublishState int state,
Instant updatedTime, String pidfXml, SipDetails details) {
logd("updatePublishRequestResult: " + state + ", time=" + updatedTime);
mPublishHandler.sendPublishStateChangedMessage(state, updatedTime, pidfXml,
details);
}
@Override
public void updatePublishThrottle(int value) {
logd("updatePublishThrottle: value=" + value);
mPublishProcessor.updatePublishThrottle(value);
}
@Override
public void refreshDeviceState(int sipCode, String reason) {
mUceCtrlCallback.refreshDeviceState(sipCode, reason,
UceController.REQUEST_TYPE_PUBLISH);
}
@Override
public void notifyPendingPublishRequest() {
logd("notifyPendingPublishRequest");
mPublishHandler.sendPublishSentMessage();
}
@Override
public void updateImsUnregistered() {
logd("updateImsUnregistered");
mPublishHandler.sendImsUnregisteredMessage();
}
};
/**
* Publish the device's capabilities to the network. This method is triggered by ImsService.
*/
@Override
public void requestPublishCapabilitiesFromService(int triggerType) {
logi("Receive the publish request from service: service trigger type=" + triggerType);
mPublishHandler.sendPublishMessage(PublishController.PUBLISH_TRIGGER_SERVICE);
}
private static class PublishHandler extends Handler {
private static final int MSG_RCS_CONNECTED = 1;
private static final int MSG_RCS_DISCONNECTED = 2;
private static final int MSG_DESTROYED = 3;
private static final int MSG_CARRIER_CONFIG_CHANGED = 4;
private static final int MSG_RCS_CAPABILITIES_CHANGED = 5;
private static final int MSG_PUBLISH_STATE_CHANGED = 6;
private static final int MSG_NOTIFY_CURRENT_PUBLISH_STATE = 7;
private static final int MSG_REQUEST_PUBLISH = 8;
private static final int MSG_REQUEST_CMD_ERROR = 9;
private static final int MSG_REQUEST_NETWORK_RESPONSE = 10;
private static final int MSG_REQUEST_CANCELED = 11;
private static final int MSG_RESET_DEVICE_STATE = 12;
private static final int MSG_UNPUBLISHED = 13;
private static final int MSG_PUBLISH_SENT = 14;
private static final int MSG_PUBLISH_UPDATED = 15;
private static final int MSG_IMS_UNREGISTERED = 16;
private final WeakReference<PublishControllerImpl> mPublishControllerRef;
public PublishHandler(PublishControllerImpl publishController, Looper looper) {
super(looper);
mPublishControllerRef = new WeakReference<>(publishController);
}
@Override
public void handleMessage(Message message) {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) {
return;
}
if (publishCtrl.mIsDestroyedFlag) return;
publishCtrl.logd("handleMessage: " + EVENT_DESCRIPTION.get(message.what));
switch (message.what) {
case MSG_RCS_CONNECTED: {
SomeArgs args = (SomeArgs) message.obj;
RcsFeatureManager manager = (RcsFeatureManager) args.arg1;
args.recycle();
publishCtrl.handleRcsConnectedMessage(manager);
break;
}
case MSG_RCS_DISCONNECTED:
publishCtrl.handleRcsDisconnectedMessage();
break;
case MSG_DESTROYED:
publishCtrl.handleDestroyedMessage();
break;
case MSG_CARRIER_CONFIG_CHANGED:
publishCtrl.handleCarrierConfigChangedMessage();
break;
case MSG_RCS_CAPABILITIES_CHANGED:
int RcsCapabilities = message.arg1;
publishCtrl.handleRcsCapabilitiesChangedMessage(RcsCapabilities);
break;
case MSG_PUBLISH_STATE_CHANGED: {
SomeArgs args = (SomeArgs) message.obj;
int newPublishState = (Integer) args.arg1;
Instant updatedTimestamp = (Instant) args.arg2;
String pidfXml = (String) args.arg3;
SipDetails details = (SipDetails) args.arg4;
args.recycle();
publishCtrl.handlePublishStateChangedMessage(newPublishState, updatedTimestamp,
pidfXml, details);
break;
}
case MSG_NOTIFY_CURRENT_PUBLISH_STATE:
IRcsUcePublishStateCallback c = (IRcsUcePublishStateCallback) message.obj;
boolean supportPublishingState = false;
if (message.arg1 == 1) {
supportPublishingState = true;
}
publishCtrl.handleNotifyCurrentPublishStateMessage(c, supportPublishingState);
break;
case MSG_REQUEST_PUBLISH:
int type = message.arg1;
publishCtrl.handleRequestPublishMessage(type);
break;
case MSG_REQUEST_CMD_ERROR:
PublishRequestResponse cmdErrorResponse = (PublishRequestResponse) message.obj;
publishCtrl.mPublishProcessor.onCommandError(cmdErrorResponse);
break;
case MSG_REQUEST_NETWORK_RESPONSE:
PublishRequestResponse networkResponse = (PublishRequestResponse) message.obj;
publishCtrl.mPublishProcessor.onNetworkResponse(networkResponse);
break;
case MSG_REQUEST_CANCELED:
long taskId = (Long) message.obj;
publishCtrl.handleRequestCanceledMessage(taskId);
break;
case MSG_RESET_DEVICE_STATE:
publishCtrl.handleResetDeviceStateMessage();
break;
case MSG_UNPUBLISHED: {
SomeArgs args = (SomeArgs) message.obj;
int newPublishState = (Integer) args.arg1;
Instant updatedTimestamp = (Instant) args.arg2;
args.recycle();
publishCtrl.handleUnpublishedMessage(newPublishState, updatedTimestamp);
break;
}
case MSG_PUBLISH_SENT:
publishCtrl.handlePublishSentMessage();
break;
case MSG_PUBLISH_UPDATED: {
SipDetails details = (SipDetails) message.obj;
publishCtrl.handlePublishUpdatedMessage(details);
break;
}
case MSG_IMS_UNREGISTERED:
publishCtrl.handleUnpublishedMessage(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED,
Instant.now());
break;
default:
publishCtrl.logd("invalid message: " + message.what);
break;
}
publishCtrl.logd("handleMessage done: " + EVENT_DESCRIPTION.get(message.what));
}
/**
* Remove all the messages from the handler.
*/
public void onDestroy() {
removeCallbacksAndMessages(null);
}
public void sendRcsConnectedMsg(RcsFeatureManager manager) {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) return;
if (publishCtrl.mIsDestroyedFlag) return;
SomeArgs args = SomeArgs.obtain();
args.arg1 = manager;
Message message = obtainMessage();
message.what = MSG_RCS_CONNECTED;
message.obj = args;
sendMessage(message);
}
public void sendRcsDisconnectedMsg() {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) return;
if (publishCtrl.mIsDestroyedFlag) return;
Message message = obtainMessage();
message.what = MSG_RCS_DISCONNECTED;
sendMessage(message);
}
public void sendDestroyedMsg() {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) return;
if (publishCtrl.mIsDestroyedFlag) return;
Message message = obtainMessage();
message.what = MSG_DESTROYED;
sendMessage(message);
}
public void sendCarrierConfigChangedMsg() {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) return;
if (publishCtrl.mIsDestroyedFlag) return;
Message message = obtainMessage();
message.what = MSG_CARRIER_CONFIG_CHANGED;
sendMessage(message);
}
public void sendRcsCapabilitiesStatusChangedMsg(@RcsImsCapabilityFlag int capabilities) {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) return;
if (publishCtrl.mIsDestroyedFlag) return;
Message message = obtainMessage();
message.what = MSG_RCS_CAPABILITIES_CHANGED;
message.arg1 = capabilities;
sendMessage(message);
}
/**
* Send the message to notify the publish state is changed.
*/
public void sendPublishStateChangedMessage(@PublishState int publishState,
@NonNull Instant updatedTimestamp, String pidfXml, SipDetails details) {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) return;
if (publishCtrl.mIsDestroyedFlag) return;
SomeArgs args = SomeArgs.obtain();
args.arg1 = publishState;
args.arg2 = updatedTimestamp;
args.arg3 = pidfXml;
args.arg4 = details;
Message message = obtainMessage();
message.what = MSG_PUBLISH_STATE_CHANGED;
message.obj = args;
sendMessage(message);
}
/**
* Send the message to notify the publish state is changed.
*/
public void sendUnpublishedMessage(@PublishState int publishState) {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) return;
if (publishCtrl.mIsDestroyedFlag) return;
SomeArgs args = SomeArgs.obtain();
args.arg1 = publishState;
args.arg2 = Instant.now();
Message message = obtainMessage();
message.what = MSG_UNPUBLISHED;
message.obj = args;
sendMessage(message);
}
/**
* Send the message to notify the publish state is changed.
*/
public void sendPublishUpdatedMessage(@NonNull SipDetails details) {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) return;
if (publishCtrl.mIsDestroyedFlag) return;
Message message = obtainMessage();
message.what = MSG_PUBLISH_UPDATED;
message.obj = details;
sendMessage(message);
}
/**
* Send the message to notify the new added callback of the latest publish state.
*/
public void sendNotifyCurrentPublishStateMessage(
IRcsUcePublishStateCallback callback, boolean supportPublishingState) {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) return;
if (publishCtrl.mIsDestroyedFlag) return;
Message message = obtainMessage();
message.what = MSG_NOTIFY_CURRENT_PUBLISH_STATE;
message.arg1 = supportPublishingState ? 1 : 0;
message.obj = callback;
sendMessage(message);
}
/**
* Send the message that the publish request has been sent to the ImsService.
*/
public void sendPublishSentMessage() {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) return;
if (publishCtrl.mIsDestroyedFlag) return;
Message message = obtainMessage();
message.what = MSG_PUBLISH_SENT;
sendMessage(message);
}
public void sendPublishMessage(@PublishTriggerType int type) {
sendPublishMessage(type, 0L);
}
public void sendPublishMessage(@PublishTriggerType int type, long delay) {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) return;
if (publishCtrl.mIsDestroyedFlag) return;
// Disallow publish if the PRESENCE PUBLISH is not enabled and this request is not
// triggered by the ImsService.
if (!publishCtrl.isPresencePublishEnabled() && type != PUBLISH_TRIGGER_SERVICE) {
publishCtrl.logd("sendPublishMessage: disallowed type=" + type);
return;
}
Message message = obtainMessage();
message.what = MSG_REQUEST_PUBLISH;
message.arg1 = type;
sendMessageDelayed(message, delay);
}
public void sendRequestCommandErrorMessage(PublishRequestResponse response) {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) {
return;
}
if (publishCtrl.mIsDestroyedFlag) return;
Message message = obtainMessage();
message.what = MSG_REQUEST_CMD_ERROR;
message.obj = response;
sendMessage(message);
}
public void sendRequestNetworkRespMessage(PublishRequestResponse response) {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) {
return;
}
if (publishCtrl.mIsDestroyedFlag) return;
Message message = obtainMessage();
message.what = MSG_REQUEST_NETWORK_RESPONSE;
message.obj = response;
sendMessage(message);
}
public void sendRequestCanceledTimerMessage(long taskId, long delay) {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) {
return;
}
if (publishCtrl.mIsDestroyedFlag) return;
removeMessages(MSG_REQUEST_CANCELED, (Long) taskId);
Message message = obtainMessage();
message.what = MSG_REQUEST_CANCELED;
message.obj = (Long) taskId;
sendMessageDelayed(message, delay);
}
public void clearRequestCanceledTimer() {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) {
return;
}
if (publishCtrl.mIsDestroyedFlag) return;
removeMessages(MSG_REQUEST_CANCELED);
}
public void sendResetDeviceStateTimerMessage(long resetAfterSec) {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) {
return;
}
if (publishCtrl.mIsDestroyedFlag) return;
// Remove old timer and setup the new timer.
removeMessages(MSG_RESET_DEVICE_STATE);
Message message = obtainMessage();
message.what = MSG_RESET_DEVICE_STATE;
sendMessageDelayed(message, TimeUnit.SECONDS.toMillis(resetAfterSec));
}
public void clearResetDeviceStateTimer() {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) {
return;
}
if (publishCtrl.mIsDestroyedFlag) return;
removeMessages(MSG_RESET_DEVICE_STATE);
}
public void sendImsUnregisteredMessage() {
PublishControllerImpl publishCtrl = mPublishControllerRef.get();
if (publishCtrl == null) return;
if (publishCtrl.mIsDestroyedFlag) return;
Message message = obtainMessage();
message.what = MSG_IMS_UNREGISTERED;
sendMessage(message);
}
private static Map<Integer, String> EVENT_DESCRIPTION = new HashMap<>();
static {
EVENT_DESCRIPTION.put(MSG_RCS_CONNECTED, "RCS_CONNECTED");
EVENT_DESCRIPTION.put(MSG_RCS_DISCONNECTED, "RCS_DISCONNECTED");
EVENT_DESCRIPTION.put(MSG_DESTROYED, "DESTROYED");
EVENT_DESCRIPTION.put(MSG_CARRIER_CONFIG_CHANGED, "CARRIER_CONFIG_CHANGED");
EVENT_DESCRIPTION.put(MSG_RCS_CAPABILITIES_CHANGED, "RCS_CAPABILITIES_CHANGED");
EVENT_DESCRIPTION.put(MSG_PUBLISH_STATE_CHANGED, "PUBLISH_STATE_CHANGED");
EVENT_DESCRIPTION.put(MSG_NOTIFY_CURRENT_PUBLISH_STATE, "NOTIFY_PUBLISH_STATE");
EVENT_DESCRIPTION.put(MSG_REQUEST_PUBLISH, "REQUEST_PUBLISH");
EVENT_DESCRIPTION.put(MSG_REQUEST_CMD_ERROR, "REQUEST_CMD_ERROR");
EVENT_DESCRIPTION.put(MSG_REQUEST_NETWORK_RESPONSE, "REQUEST_NETWORK_RESPONSE");
EVENT_DESCRIPTION.put(MSG_REQUEST_CANCELED, "REQUEST_CANCELED");
EVENT_DESCRIPTION.put(MSG_RESET_DEVICE_STATE, "RESET_DEVICE_STATE");
EVENT_DESCRIPTION.put(MSG_UNPUBLISHED, "MSG_UNPUBLISHED");
EVENT_DESCRIPTION.put(MSG_PUBLISH_SENT, "MSG_PUBLISH_SENT");
EVENT_DESCRIPTION.put(MSG_PUBLISH_UPDATED, "MSG_PUBLISH_UPDATED");
EVENT_DESCRIPTION.put(MSG_IMS_UNREGISTERED, "MSG_IMS_UNREGISTERED");
}
}
/**
* Check if the PUBLISH request is allowed.
*/
private boolean isPublishRequestAllowed() {
// The PUBLISH request requires that the RCS PRESENCE is capable.
if (!mDeviceCapabilityInfo.isPresenceCapable()) {
logd("isPublishRequestAllowed: capability presence uce is not enabled.");
return false;
}
// The first PUBLISH request is required to be triggered from the service.
if (!mReceivePublishFromService) {
logd("isPublishRequestAllowed: "
+ "The first PUBLISH request from the server has not been received.");
return false;
}
// Check whether the device state is not allowed to execute the PUBLISH request.
DeviceStateResult deviceState = mUceCtrlCallback.getDeviceState();
if (deviceState.isRequestForbidden() || deviceState.isPublishRequestBlocked()) {
logd("isPublishRequestAllowed: The device state is disallowed. "
+ deviceState.getDeviceState());
return false;
}
// Check whether there is already a publish request running or not. When the running
// request is finished and there is a pending request, it will send a new request.
if (mPublishProcessor.isPublishingNow()) {
logd("isPublishRequestAllowed: There is already a publish request running now.");
return false;
}
return true;
}
/**
* Check whether the PRESENCE PUBLISH should be enabled or not. It should be enabled only when
* the PRESENCE mechanism is supported.
*/
private boolean isPresencePublishEnabled() {
synchronized (mPublishStateLock) {
return mCapabilityType == RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE;
}
}
/**
* Handle the RCS connected message. This method is called in the handler thread.
*/
private void handleRcsConnectedMessage(RcsFeatureManager manager) {
if (mIsDestroyedFlag) return;
mRcsFeatureManager = manager;
mDeviceCapListener.onRcsConnected();
mPublishProcessor.onRcsConnected(manager);
registerRcsAvailabilityChanged(manager);
}
/**
* Handle the RCS disconnected message. This method is called in the handler thread.
*/
private void handleRcsDisconnectedMessage() {
if (mIsDestroyedFlag) return;
mRcsFeatureManager = null;
mDeviceCapabilityInfo.updatePresenceCapable(false);
mDeviceCapListener.onRcsDisconnected();
mPublishProcessor.onRcsDisconnected();
// When the RCS is disconnected, update the publish state to NOT_PUBLISH if the PRESENCE
// PUBLISH is enabled.
if (isPresencePublishEnabled()) {
handlePublishStateChangedMessage(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED,
Instant.now(), null /*pidfXml*/, null /*SipDetails*/);
}
}
/**
* Handle the Destroyed message. This method is called in the handler thread.
*/
private void handleDestroyedMessage() {
mIsDestroyedFlag = true;
mDeviceCapabilityInfo.updatePresenceCapable(false);
unregisterRcsAvailabilityChanged();
mDeviceCapListener.onDestroy(); // It will turn off the listener automatically.
mPublishHandler.onDestroy();
mPublishProcessor.onDestroy();
synchronized (mPublishStateLock) {
clearPublishStateCallbacks();
}
}
/*
* Register the availability callback to receive the RCS capabilities change. This method is
* called when the RCS is connected.
*/
private void registerRcsAvailabilityChanged(RcsFeatureManager manager) {
try {
manager.registerRcsAvailabilityCallback(mSubId, mRcsCapabilitiesCallback);
} catch (ImsException e) {
logw("registerRcsAvailabilityChanged exception " + e);
}
}
/*
* Unregister the availability callback. This method is called when the PublishController
* instance is destroyed.
*/
private void unregisterRcsAvailabilityChanged() {
RcsFeatureManager manager = mRcsFeatureManager;
if (manager == null) return;
try {
manager.unregisterRcsAvailabilityCallback(mSubId, mRcsCapabilitiesCallback);
} catch (Exception e) {
// Do not handle the exception
}
}
/**
* Handle the carrier config changed message. This method is called in the handler thread.
*/
private void handleCarrierConfigChangedMessage() {
if (mIsDestroyedFlag) return;
updateCapabilityTypeAndPublishStateIfNeeded();
String[] newMap = getCarrierServiceDescriptionFeatureTagMap();
if (mDeviceCapabilityInfo.updateCapabilityRegistrationTrackerMap(newMap)) {
mPublishHandler.sendPublishMessage(
PublishController.PUBLISH_TRIGGER_CARRIER_CONFIG_CHANGED);
}
}
/**
* Check whether the capability type has changed or not because of the carrier config changed.
* If the capability type has changed, the publish state also needs to be reinitialized.
* <p>
* This method is called in the handler thread.
*/
private void updateCapabilityTypeAndPublishStateIfNeeded() {
synchronized (mPublishStateLock) {
int originalMechanism = mCapabilityType;
mCapabilityType = PublishUtils.getCapabilityType(mContext, mSubId);
// Return when the capability type has not changed.
if (originalMechanism == mCapabilityType) {
logd("updateCapTypeAndPublishStateIfNeeded: " +
"The capability type is not changed=" + mCapabilityType);
return;
}
// Reinitialize the publish state because the capability type has changed.
int updatedPublishState = getInitialPublishState(mCapabilityType);
logd("updateCapTypeAndPublishStateIfNeeded from " + originalMechanism +
" to " + mCapabilityType + ", new publish state=" + updatedPublishState);
// Update the publish state directly. Because this method is called in the
// handler thread already, the process of updating publish state does not need to be
// sent to the looper again.
handlePublishStateChangedMessage(updatedPublishState, Instant.now(), null /*pidfxml*/,
null /*SipDetails*/);
}
}
private String[] getCarrierServiceDescriptionFeatureTagMap() {
CarrierConfigManager manager = mContext.getSystemService(CarrierConfigManager.class);
PersistableBundle bundle = manager != null ? manager.getConfigForSubId(mSubId) :
CarrierConfigManager.getDefaultConfig();
return bundle.getStringArray(CarrierConfigManager.Ims.
KEY_PUBLISH_SERVICE_DESC_FEATURE_TAG_MAP_OVERRIDE_STRING_ARRAY);
}
private void handleRcsCapabilitiesChangedMessage(int capabilities) {
logd("handleRcsCapabilitiesChangedMessage: " + capabilities);
if (mIsDestroyedFlag) return;
RcsImsCapabilities RcsImsCapabilities = new RcsImsCapabilities(capabilities);
mDeviceCapabilityInfo.updatePresenceCapable(
RcsImsCapabilities.isCapable(RcsUceAdapter.CAPABILITY_TYPE_PRESENCE_UCE));
// Trigger a publish request if the RCS capabilities presence is enabled.
if (mDeviceCapabilityInfo.isPresenceCapable()) {
mPublishProcessor.checkAndSendPendingRequest();
}
}
/**
* Update the publish state and notify the publish state callback if the new state is different
* from original state.
*/
private void handlePublishStateChangedMessage(@PublishState int newPublishState,
Instant updatedTimestamp, String pidfXml, SipDetails details) {
synchronized (mPublishStateLock) {
if (mIsDestroyedFlag) return;
// Check if the time of the given publish state is not earlier than existing time.
if (updatedTimestamp == null || !updatedTimestamp.isAfter(mPublishStateUpdatedTime)) {
logd("handlePublishStateChangedMessage: updatedTimestamp is not allowed: "
+ mPublishStateUpdatedTime + " to " + updatedTimestamp
+ ", publishState=" + newPublishState);
return;
}
logd("publish state changes from " + mCurrentPublishState + " to " + newPublishState +
", time=" + updatedTimestamp);
mPublishStateUpdatedTime = updatedTimestamp;
mPidfXml = pidfXml;
// Bail early and do not update listeners if the publish state didn't change.
if (mCurrentPublishState == newPublishState) return;
mLastPublishState = mCurrentPublishState;
mCurrentPublishState = newPublishState;
}
if (newPublishState == RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED) {
mUceStatsWriter.setUnPublish(mSubId);
}
// Trigger the publish state changed in handler thread since it may take time.
logd("Notify publish state changed: " + mCurrentPublishState);
mPublishStateCallbacks.broadcast(c -> {
try {
c.onPublishUpdated(getPublishAttributes(mCurrentPublishState, details));
} catch (RemoteException e) {
logw("Notify publish state changed error: " + e);
}
});
logd("Notify publish state changed: completed");
}
private PublishAttributes getPublishAttributes(@PublishState int mCurrentPublishState,
SipDetails details) {
List<RcsContactPresenceTuple> tuples = null;
if (mCurrentPublishState == PUBLISH_STATE_OK) {
tuples = mDeviceCapabilityInfo.getLastSuccessfulPresenceTuplesWithoutContactUri();
}
if (tuples != null && !tuples.isEmpty()) {
return new PublishAttributes.Builder(mCurrentPublishState).setSipDetails(details)
.setPresenceTuples(tuples).build();
}
return new PublishAttributes.Builder(mCurrentPublishState).setSipDetails(details).build();
}
private void handleNotifyCurrentPublishStateMessage(IRcsUcePublishStateCallback callback,
boolean supportPublishingState) {
if (mIsDestroyedFlag || callback == null) return;
try {
int publishState = getUcePublishState(supportPublishingState);
callback.onPublishUpdated(getPublishAttributes(publishState, null /*SipDetails*/));
} catch (RemoteException e) {
logw("handleCurrentPublishStateUpdateMessage exception: " + e);
}
}
private void handleRequestPublishMessage(@PublishTriggerType int type) {
if (mIsDestroyedFlag) return;
logd("handleRequestPublishMessage: type=" + type);
// Set the PUBLISH FROM SERVICE flag and reset the device state if the PUBLISH request is
// triggered by the ImsService.
if (type == PublishController.PUBLISH_TRIGGER_SERVICE) {
// Set the flag
if (!mReceivePublishFromService) {
mReceivePublishFromService = true;
}
// Reset device state
DeviceStateResult deviceState = mUceCtrlCallback.getDeviceState();
if (deviceState.isRequestForbidden() || deviceState.isPublishRequestBlocked()) {
mUceCtrlCallback.resetDeviceState();
}
}
// Set the pending flag and return if the request is not allowed.
if (!isPublishRequestAllowed()) {
logd("handleRequestPublishMessage: SKIP. The request is not allowed. type=" + type);
mPublishProcessor.setPendingRequest(type);
return;
}
// Update the latest PUBLISH allowed time according to the given trigger type.
mPublishProcessor.updatePublishingAllowedTime(type);
// Get the publish request delay time. If the delay is not present, the first
// PUBLISH is not allowed to be executed; If the delay time is 0, it means that
// this request can be executed immediately.
Optional<Long> delay = mPublishProcessor.getPublishingDelayTime();
if (!delay.isPresent()) {
logd("handleRequestPublishMessage: SKIP. The delay is empty. type=" + type);
mPublishProcessor.setPendingRequest(type);
return;
}
logd("handleRequestPublishMessage: " + type + ", delay=" + delay.get());
if (delay.get() == 0L) {
mPublishProcessor.doPublish(type);
} else {
mPublishHandler.sendPublishMessage(type, delay.get());
}
}
private void handleRequestCanceledMessage(long taskId) {
if (mIsDestroyedFlag) return;
mPublishProcessor.cancelPublishRequest(taskId);
}
private void handleResetDeviceStateMessage() {
if(mIsDestroyedFlag) return;
mUceCtrlCallback.resetDeviceState();
}
private void handleUnpublishedMessage(@PublishState int newPublishState,
Instant updatedTimestamp) {
if (mIsDestroyedFlag) return;
mPublishProcessor.resetState();
handlePublishStateChangedMessage(newPublishState, updatedTimestamp,
null /*pidfXml*/, null /*SipDetails*/);
}
private void handlePublishSentMessage() {
synchronized (mPublishStateLock) {
if (mIsDestroyedFlag) return;
int lastIndex = mPublishStateCallbacks.getRegisteredCallbackCount() - 1;
int tempPublishState = mCurrentPublishState;
for (int index = lastIndex; index >= 0; index--) {
IRcsUcePublishStateCallback callback =
mPublishStateCallbacks.getRegisteredCallbackItem(index);
boolean isSupportPublishingState = false;
try {
Object object = mPublishStateCallbacks.getRegisteredCallbackCookie(index);
if (object != null) {
isSupportPublishingState = (Boolean) object;
}
} catch (Exception e) {
// Do not handle the exception
}
try {
mCurrentPublishState = RcsUceAdapter.PUBLISH_STATE_PUBLISHING;
if (isSupportPublishingState) {
if (callback != null) {
callback.onPublishUpdated(getPublishAttributes(mCurrentPublishState,
null /*SipDetails*/));
}
} else {
// If it is currently PUBLISH_STATE_OK, the state must not be changed to
// PUBLISH_STATE_NOT_PUBLISHED.
// And in the case of the current PUBLISH_STATE_NOT_PUBLISHED, it is
// necessary to avoid reporting the duplicate state.
if (tempPublishState != PUBLISH_STATE_OK
&& tempPublishState != RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED) {
// set the state to PUBLISH_STATE_NOT_PUBLISHED so that
// getUcePublishState is consistent with the callback
mLastPublishState = RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED;
if (callback != null) {
callback.onPublishUpdated(getPublishAttributes(mLastPublishState,
null /*SipDetails*/));
}
}
}
} catch (RemoteException e) {
logw("Notify publish state changed error: " + e);
}
}
}
}
private void handlePublishUpdatedMessage(@NonNull SipDetails details) {
if (mIsDestroyedFlag) return;
PublishRequestResponse updatedPublish = new PublishRequestResponse(getLastPidfXml(),
details);
mPublishProcessor.publishUpdated(updatedPublish);
}
@VisibleForTesting
public void setCapabilityType(int type) {
mCapabilityType = type;
mCurrentPublishState = getInitialPublishState(mCapabilityType);
mLastPublishState = mCurrentPublishState;
}
@VisibleForTesting
public void setPublishStateCallback(RemoteCallbackList<IRcsUcePublishStateCallback> list) {
mPublishStateCallbacks = list;
}
@VisibleForTesting
public PublishHandler getPublishHandler() {
return mPublishHandler;
}
@VisibleForTesting
public IImsCapabilityCallback getRcsCapabilitiesCallback() {
return mRcsCapabilitiesCallback;
}
@VisibleForTesting
public PublishControllerCallback getPublishControllerCallback() {
return mPublishControllerCallback;
}
private void logd(String log) {
Log.d(LOG_TAG, getLogPrefix().append(log).toString());
mLocalLog.log("[D] " + log);
}
private void logi(String log) {
Log.i(LOG_TAG, getLogPrefix().append(log).toString());
mLocalLog.log("[I] " + log);
}
private void logw(String log) {
Log.w(LOG_TAG, getLogPrefix().append(log).toString());
mLocalLog.log("[W] " + log);
}
private StringBuilder getLogPrefix() {
StringBuilder builder = new StringBuilder("[");
builder.append(mSubId);
builder.append("] ");
return builder;
}
@Override
public void dump(PrintWriter printWriter) {
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
pw.println("PublishControllerImpl" + "[subId: " + mSubId + "]:");
pw.increaseIndent();
pw.print("isPresenceCapable=");
pw.println(mDeviceCapabilityInfo.isPresenceCapable());
pw.print("mCurrentPublishState=");
pw.print(mCurrentPublishState);
pw.print("mLastPublishState=");
pw.print(mLastPublishState);
pw.print(" at time ");
pw.println(mPublishStateUpdatedTime);
pw.println("Last PIDF XML:");
pw.increaseIndent();
if (Build.IS_ENG) {
pw.println(mPidfXml);
} else if (Build.IS_DEBUGGABLE) {
String pidfXml = (mPidfXml == null) ? "null" : mPidfXml;
pw.println(PublishUtils.removeNumbersFromUris(pidfXml));
} else {
pw.println(mPidfXml != null ? "***" : "null");
}
pw.decreaseIndent();
if (mPublishProcessor != null) {
mPublishProcessor.dump(pw);
} else {
pw.println("mPublishProcessor is null");
}
pw.println();
mDeviceCapListener.dump(pw);
pw.println("Log:");
pw.increaseIndent();
mLocalLog.dump(pw);
pw.decreaseIndent();
pw.println("---");
pw.decreaseIndent();
}
}