blob: ee0c5be3275ec08b601128134a32c6673eab8704 [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.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.provider.Settings;
import android.provider.Telephony;
import android.telecom.TelecomManager;
import android.telephony.AccessNetworkConstants;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsManager;
import android.telephony.ims.ImsMmTelManager;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ProvisioningManager;
import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.RcsUceAdapter;
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.aidl.IRcsUceControllerCallback;
import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.stub.RcsCapabilityExchange;
import android.telephony.ims.stub.RcsPresenceExchangeImplBase;
import android.util.Log;
import com.android.ims.RcsFeatureManager;
import com.android.ims.RcsFeatureManager.RcsFeatureCallbacks;
import com.android.ims.ResultCode;
import com.android.internal.annotations.VisibleForTesting;
import com.android.phone.R;
import com.android.service.ims.presence.ContactCapabilityResponse;
import com.android.service.ims.presence.PresenceBase;
import com.android.service.ims.presence.PresencePublication;
import com.android.service.ims.presence.PresencePublisher;
import com.android.service.ims.presence.PresenceSubscriber;
import com.android.service.ims.presence.SubscribePublisher;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* Implements User Capability Exchange using Presence.
*/
public class UserCapabilityExchangeImpl implements RcsFeatureController.Feature, SubscribePublisher,
PresencePublisher {
private static final String LOG_TAG = "RcsUceImpl";
private final int mSlotId;
private volatile int mSubId;
private volatile boolean mImsContentChangedCallbackRegistered = false;
// The result of requesting publish
private volatile int mPublishState = PresenceBase.PUBLISH_STATE_NOT_PUBLISHED;
// The network type which IMS registers on
private volatile int mNetworkRegistrationType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
// The MMTel capabilities of this subscription Id
private MmTelFeature.MmTelCapabilities mMmTelCapabilities;
private final Object mCapabilitiesLock = new Object();
private final Context mContext;
private final UceImplHandler mUceImplHandler;
private RcsFeatureManager mRcsFeatureManager;
private final PresencePublication mPresencePublication;
private final PresenceSubscriber mPresenceSubscriber;
// The task Ids of updating capabilities
private final Set<Integer> mRequestingPublishTaskIds = new HashSet<>();
// The callbacks to notify publish state changed.
private final RemoteCallbackList<IRcsUcePublishStateCallback> mPublishStateCallbacks;
// The task Ids of pending availability request.
private final Set<Integer> mPendingAvailabilityRequests = new HashSet<>();
private final ConcurrentHashMap<Integer, IRcsUceControllerCallback> mPendingCapabilityRequests =
new ConcurrentHashMap<>();
UserCapabilityExchangeImpl(Context context, int slotId, int subId) {
mSlotId = slotId;
mSubId = subId;
logi("created");
mContext = context;
mPublishStateCallbacks = new RemoteCallbackList<>();
HandlerThread handlerThread = new HandlerThread("UceImplHandlerThread");
handlerThread.start();
mUceImplHandler = new UceImplHandler(this, handlerThread.getLooper());
String[] volteError = context.getResources().getStringArray(
R.array.config_volte_provision_error_on_publish_response);
String[] rcsError = context.getResources().getStringArray(
R.array.config_rcs_provision_error_on_publish_response);
// Initialize PresencePublication
mPresencePublication = new PresencePublication(null /*PresencePublisher*/, context,
volteError, rcsError);
// Initialize PresenceSubscriber
mPresenceSubscriber = new PresenceSubscriber(null /*SubscribePublisher*/, context,
volteError, rcsError);
onAssociatedSubscriptionUpdated(mSubId);
registerReceivers();
}
@VisibleForTesting
UserCapabilityExchangeImpl(Context context, int slotId, int subId, Looper looper,
PresencePublication presencePublication, PresenceSubscriber presenceSubscriber,
RemoteCallbackList<IRcsUcePublishStateCallback> publishStateCallbacks) {
mSlotId = slotId;
mSubId = subId;
mContext = context;
mPublishStateCallbacks = publishStateCallbacks;
mUceImplHandler = new UceImplHandler(this, looper);
mPresencePublication = presencePublication;
mPresenceSubscriber = presenceSubscriber;
onAssociatedSubscriptionUpdated(mSubId);
registerReceivers();
}
// Runs on main thread.
@Override
public void onRcsConnected(RcsFeatureManager rcsFeatureManager) {
logi("onRcsConnected");
mRcsFeatureManager = rcsFeatureManager;
mRcsFeatureManager.addFeatureListenerCallback(mRcsFeatureCallback);
mPresencePublication.updatePresencePublisher(this);
mPresenceSubscriber.updatePresenceSubscriber(this);
}
// Runs on main thread.
@Override
public void onRcsDisconnected() {
logi("onRcsDisconnected");
mPresencePublication.removePresencePublisher();
mPresenceSubscriber.removePresenceSubscriber();
if (mRcsFeatureManager != null) {
mRcsFeatureManager.releaseConnection();
mRcsFeatureManager = null;
}
}
// Runs on main thread.
@Override
public void onAssociatedSubscriptionUpdated(int subId) {
logi("onAssociatedSubscriptionUpdated: new subId=" + subId);
// Listen to the IMS content changed with new subId.
mUceImplHandler.registerImsContentChangedReceiver(subId);
mSubId = subId;
mPresencePublication.handleAssociatedSubscriptionChanged(subId);
mPresenceSubscriber.handleAssociatedSubscriptionChanged(subId);
}
/**
* Should be called before destroying this instance.
* This instance is not usable after this method is called.
*/
// Called on main thread.
public void onDestroy() {
logi("onDestroy");
mUceImplHandler.getLooper().quit();
unregisterReceivers();
unregisterImsProvisionCallback(mSubId);
onRcsDisconnected();
}
/**
* @return the UCE Publish state.
*/
// May happen on a Binder thread, PresencePublication locks to get result.
public int getUcePublishState() {
int publishState = mPresencePublication.getPublishState();
return toUcePublishState(publishState);
}
@VisibleForTesting
public UceImplHandler getHandler() {
return mUceImplHandler;
}
/**
* Register receiver to receive UCE publish state changed.
*/
public void registerPublishStateCallback(IRcsUcePublishStateCallback c) {
synchronized (mPublishStateCallbacks) {
mPublishStateCallbacks.register(c);
}
}
/**
* Unregister UCE publish state callback.
*/
public void unregisterUcePublishStateCallback(IRcsUcePublishStateCallback c) {
synchronized (mPublishStateCallbacks) {
mPublishStateCallbacks.unregister(c);
}
}
private void clearPublishStateCallbacks() {
synchronized (mPublishStateCallbacks) {
logi("clearPublishStateCallbacks");
final int lastIndex = mPublishStateCallbacks.getRegisteredCallbackCount() - 1;
for (int index = lastIndex; index >= 0; index--) {
IRcsUcePublishStateCallback callback =
mPublishStateCallbacks.getRegisteredCallbackItem(index);
mPublishStateCallbacks.unregister(callback);
}
}
}
private void notifyPublishStateChanged(@PresenceBase.PresencePublishState int state) {
int result = toUcePublishState(state);
synchronized (mPublishStateCallbacks) {
mPublishStateCallbacks.broadcast(c -> {
try {
c.onPublishStateChanged(result);
} catch (RemoteException e) {
logw("notifyPublishStateChanged error: " + e);
}
});
}
}
/**
* Perform a capabilities request and call {@link IRcsUceControllerCallback} with the result.
*/
// May happen on a Binder thread, PresenceSubscriber locks when requesting Capabilities.
public void requestCapabilities(List<Uri> contactNumbers, IRcsUceControllerCallback c) {
List<String> numbers = contactNumbers.stream()
.map(UserCapabilityExchangeImpl::getNumberFromUri).collect(Collectors.toList());
int taskId = mPresenceSubscriber.requestCapability(numbers,
new ContactCapabilityResponse() {
@Override
public void onSuccess(int reqId) {
logi("onSuccess called for reqId:" + reqId);
}
@Override
public void onError(int reqId, int resultCode) {
IRcsUceControllerCallback c = mPendingCapabilityRequests.remove(reqId);
try {
if (c != null) {
c.onError(toUceError(resultCode));
} else {
logw("onError called for unknown reqId:" + reqId);
}
} catch (RemoteException e) {
logi("Calling back to dead service");
}
}
@Override
public void onFinish(int reqId) {
logi("onFinish called for reqId:" + reqId);
}
@Override
public void onTimeout(int reqId) {
IRcsUceControllerCallback c = mPendingCapabilityRequests.remove(reqId);
try {
if (c != null) {
c.onError(RcsUceAdapter.ERROR_REQUEST_TIMEOUT);
} else {
logw("onTimeout called for unknown reqId:" + reqId);
}
} catch (RemoteException e) {
logi("Calling back to dead service");
}
}
@Override
public void onCapabilitiesUpdated(int reqId,
List<RcsContactUceCapability> contactCapabilities,
boolean updateLastTimestamp) {
IRcsUceControllerCallback c = mPendingCapabilityRequests.remove(reqId);
try {
if (c != null) {
c.onCapabilitiesReceived(contactCapabilities);
} else {
logw("onCapabilitiesUpdated, unknown reqId:" + reqId);
}
} catch (RemoteException e) {
logw("onCapabilitiesUpdated on dead service");
}
}
});
if (taskId < 0) {
try {
c.onError(toUceError(taskId));
} catch (RemoteException e) {
logi("Calling back to dead service");
}
return;
}
mPendingCapabilityRequests.put(taskId, c);
}
@Override
public int requestCapability(String[] formattedContacts, int taskId) {
if (formattedContacts == null || formattedContacts.length == 0) {
logw("requestCapability error: contacts is null.");
return ResultCode.SUBSCRIBE_INVALID_PARAM;
}
if (mRcsFeatureManager == null) {
logw("requestCapability error: RcsFeatureManager is null.");
return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
}
logi("requestCapability: taskId=" + taskId);
try {
List<Uri> contactList = Arrays.stream(formattedContacts)
.map(Uri::parse).collect(Collectors.toList());
mRcsFeatureManager.requestCapabilities(contactList, taskId);
} catch (Exception e) {
logw("requestCapability error: " + e.getMessage());
return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
}
return ResultCode.SUCCESS;
}
@Override
public int requestAvailability(String formattedContact, int taskId) {
if (formattedContact == null || formattedContact.isEmpty()) {
logw("requestAvailability error: contact is null.");
return ResultCode.SUBSCRIBE_INVALID_PARAM;
}
if (mRcsFeatureManager == null) {
logw("requestAvailability error: RcsFeatureManager is null.");
return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
}
logi("requestAvailability: taskId=" + taskId);
addRequestingAvailabilityTaskId(taskId);
try {
Uri contactUri = Uri.parse(formattedContact);
List<Uri> contactUris = new ArrayList<>(Arrays.asList(contactUri));
mRcsFeatureManager.requestCapabilities(contactUris, taskId);
} catch (Exception e) {
logw("requestAvailability error: " + e.getMessage());
removeRequestingAvailabilityTaskId(taskId);
return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
}
return ResultCode.SUCCESS;
}
@Override
public int getStackStatusForCapabilityRequest() {
if (mRcsFeatureManager == null) {
logw("Check Stack status: Error! RcsFeatureManager is null.");
return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
}
if (!isCapabilityDiscoveryEnabled(mSubId)) {
logw("Check Stack status: Error! capability discovery not enabled");
return ResultCode.ERROR_SERVICE_NOT_ENABLED;
}
if (!isEabProvisioned(mContext, mSubId)) {
logw("Check Stack status: Error! EAB provisioning disabled.");
return ResultCode.ERROR_SERVICE_NOT_ENABLED;
}
if (getPublisherState() != PresenceBase.PUBLISH_STATE_200_OK) {
logw("Check Stack status: Error! publish state " + getPublisherState());
return ResultCode.ERROR_SERVICE_NOT_PUBLISHED;
}
return ResultCode.SUCCESS;
}
/**
* The feature callback is to receive the request and update from RcsPresExchangeImplBase
*/
@VisibleForTesting
public RcsFeatureCallbacks mRcsFeatureCallback = new RcsFeatureCallbacks() {
public void onCommandUpdate(int commandCode, int operationToken) {
logi("onCommandUpdate: code=" + commandCode + ", token=" + operationToken);
if (isPublishRequestExisted(operationToken)) {
onCommandUpdateForPublishRequest(commandCode, operationToken);
} else if (isCapabilityRequestExisted(operationToken)) {
onCommandUpdateForCapabilityRequest(commandCode, operationToken);
} else if (isAvailabilityRequestExisted(operationToken)) {
onCommandUpdateForAvailabilityRequest(commandCode, operationToken);
} else {
logw("onCommandUpdate: invalid token " + operationToken);
}
}
/** See {@link RcsPresenceExchangeImplBase#onNetworkResponse(int, String, int)} */
public void onNetworkResponse(int responseCode, String reason, int operationToken) {
logi("onNetworkResponse: code=" + responseCode + ", reason=" + reason
+ ", operationToken=" + operationToken);
if (isPublishRequestExisted(operationToken)) {
onNetworkResponseForPublishRequest(responseCode, reason, operationToken);
} else if (isCapabilityRequestExisted(operationToken)) {
onNetworkResponseForCapabilityRequest(responseCode, reason, operationToken);
} else if (isAvailabilityRequestExisted(operationToken)) {
onNetworkResponseForAvailabilityRequest(responseCode, reason, operationToken);
} else {
logw("onNetworkResponse: invalid token " + operationToken);
}
}
/** See {@link RcsPresenceExchangeImplBase#onCapabilityRequestResponse(List, int)} */
public void onCapabilityRequestResponsePresence(List<RcsContactUceCapability> infos,
int operationToken) {
if (isAvailabilityRequestExisted(operationToken)) {
handleAvailabilityReqResponse(infos, operationToken);
} else if (isCapabilityRequestExisted(operationToken)) {
handleCapabilityReqResponse(infos, operationToken);
} else {
logw("capability request response: invalid token " + operationToken);
}
}
/** See {@link RcsPresenceExchangeImplBase#onNotifyUpdateCapabilites(int)} */
public void onNotifyUpdateCapabilities(int publishTriggerType) {
logi("onNotifyUpdateCapabilities: type=" + publishTriggerType);
mUceImplHandler.notifyUpdateCapabilities(publishTriggerType);
}
/** See {@link RcsPresenceExchangeImplBase#onUnpublish()} */
public void onUnpublish() {
logi("onUnpublish");
mUceImplHandler.unpublish();
}
};
private static class UceImplHandler extends Handler {
private static final int EVENT_REGISTER_IMS_CHANGED_RECEIVER = 1;
private static final int EVENT_NOTIFY_UPDATE_CAPABILITIES = 2;
private static final int EVENT_UNPUBLISH = 3;
private static final int REGISTER_IMS_CHANGED_DELAY = 10000; //10 seconds
private final WeakReference<UserCapabilityExchangeImpl> mUceImplRef;
UceImplHandler(UserCapabilityExchangeImpl uceImpl, Looper looper) {
super(looper);
mUceImplRef = new WeakReference(uceImpl);
}
@Override
public void handleMessage(Message msg) {
UserCapabilityExchangeImpl uceImpl = mUceImplRef.get();
if (uceImpl == null) {
return;
}
switch (msg.what) {
case EVENT_REGISTER_IMS_CHANGED_RECEIVER:
int subId = msg.arg1;
uceImpl.registerImsContentChangedReceiverInternal(subId);
break;
case EVENT_NOTIFY_UPDATE_CAPABILITIES:
int publishTriggerType = msg.arg1;
uceImpl.onNotifyUpdateCapabilities(publishTriggerType);
break;
case EVENT_UNPUBLISH:
uceImpl.onUnPublish();
break;
default:
Log.w(LOG_TAG, "handleMessage: error=" + msg.what);
break;
}
}
private void retryRegisteringImsContentChangedReceiver(int subId) {
sendRegisteringImsContentChangedMessage(subId, REGISTER_IMS_CHANGED_DELAY);
}
private void registerImsContentChangedReceiver(int subId) {
sendRegisteringImsContentChangedMessage(subId, 0);
}
private void sendRegisteringImsContentChangedMessage(int subId, int delay) {
if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
return;
}
removeRegisteringImsContentChangedReceiver();
Message message = obtainMessage(EVENT_REGISTER_IMS_CHANGED_RECEIVER);
message.arg1 = subId;
sendMessageDelayed(message, delay);
}
private void removeRegisteringImsContentChangedReceiver() {
removeMessages(EVENT_REGISTER_IMS_CHANGED_RECEIVER);
}
private void notifyUpdateCapabilities(int publishTriggerType) {
Message message = obtainMessage(EVENT_NOTIFY_UPDATE_CAPABILITIES);
message.arg1 = publishTriggerType;
sendMessage(message);
}
private void unpublish() {
sendEmptyMessage(EVENT_UNPUBLISH);
}
}
private void onNotifyUpdateCapabilities(int publishTriggerType) {
mPresencePublication.onStackPublishRequested(publishTriggerType);
}
private void onUnPublish() {
mPresencePublication.setPublishState(PresenceBase.PUBLISH_STATE_NOT_PUBLISHED);
}
@Override
public @PresenceBase.PresencePublishState int getPublisherState() {
return mPublishState;
}
@Override
public int requestPublication(RcsContactUceCapability capabilities, String contactUri,
int taskId) {
if (mRcsFeatureManager == null) {
logw("requestPublication error: RcsFeatureManager is null.");
return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
}
logi("requestPublication: taskId=" + taskId);
addPublishRequestTaskId(taskId);
try {
mRcsFeatureManager.requestPublication(capabilities, taskId);
} catch (Exception ex) {
logw("requestPublication error: " + ex.getMessage());
removePublishRequestTaskId(taskId);
return ResultCode.PUBLISH_GENERIC_FAILURE;
}
return ResultCode.SUCCESS;
}
/*
* Handle the callback method RcsFeatureCallbacks#onCommandUpdate(int, int)
*/
private void onCommandUpdateForPublishRequest(int commandCode, int operationToken) {
if (!isPublishRequestExisted(operationToken)) {
return;
}
int resultCode = ResultCode.SUCCESS;
if (commandCode != RcsCapabilityExchange.COMMAND_CODE_SUCCESS) {
logw("onCommandUpdateForPublishRequest failed! taskId=" + operationToken
+ ", code=" + commandCode);
removePublishRequestTaskId(operationToken);
resultCode = ResultCode.PUBLISH_GENERIC_FAILURE;
}
mPresencePublication.onCommandStatusUpdated(operationToken, operationToken, resultCode);
}
private void onCommandUpdateForCapabilityRequest(int commandCode, int operationToken) {
if (!isCapabilityRequestExisted(operationToken)) {
return;
}
int resultCode = ResultCode.SUCCESS;
if (commandCode != RcsCapabilityExchange.COMMAND_CODE_SUCCESS) {
logw("onCommandUpdateForCapabilityRequest failed! taskId=" + operationToken
+ ", code=" + commandCode);
mPendingCapabilityRequests.remove(operationToken);
resultCode = ResultCode.PUBLISH_GENERIC_FAILURE;
}
mPresenceSubscriber.onCommandStatusUpdated(operationToken, operationToken, resultCode);
}
private void onCommandUpdateForAvailabilityRequest(int commandCode, int operationToken) {
if (!isAvailabilityRequestExisted(operationToken)) {
return;
}
int resultCode = ResultCode.SUCCESS;
if (commandCode != RcsCapabilityExchange.COMMAND_CODE_SUCCESS) {
logw("onCommandUpdateForAvailabilityRequest failed! taskId=" + operationToken
+ ", code=" + commandCode);
removeRequestingAvailabilityTaskId(operationToken);
resultCode = ResultCode.PUBLISH_GENERIC_FAILURE;
}
mPresenceSubscriber.onCommandStatusUpdated(operationToken, operationToken, resultCode);
}
/*
* Handle the callback method RcsFeatureCallbacks#onNetworkResponse(int, String, int)
*/
private void onNetworkResponseForPublishRequest(int responseCode, String reason,
int operationToken) {
if (!isPublishRequestExisted(operationToken)) {
return;
}
removePublishRequestTaskId(operationToken);
mPresencePublication.onSipResponse(operationToken, responseCode, reason);
}
private void onNetworkResponseForCapabilityRequest(int responseCode, String reason,
int operationToken) {
if (!isCapabilityRequestExisted(operationToken)) {
return;
}
mPresenceSubscriber.onSipResponse(operationToken, responseCode, reason);
}
private void onNetworkResponseForAvailabilityRequest(int responseCode, String reason,
int operationToken) {
if (!isAvailabilityRequestExisted(operationToken)) {
return;
}
removeRequestingAvailabilityTaskId(operationToken);
mPresenceSubscriber.onSipResponse(operationToken, responseCode, reason);
}
private void handleAvailabilityReqResponse(List<RcsContactUceCapability> infos, int token) {
try {
if (infos == null || infos.isEmpty()) {
logw("handle availability request response: infos is null " + token);
return;
}
logi("handleAvailabilityReqResponse: token=" + token);
mPresenceSubscriber.updatePresence(infos.get(0));
} finally {
removeRequestingAvailabilityTaskId(token);
}
}
private void handleCapabilityReqResponse(List<RcsContactUceCapability> infos, int token) {
if (infos == null) {
logw("handleCapabilityReqResponse: infos is null " + token);
mPendingCapabilityRequests.remove(token);
return;
}
logi("handleCapabilityReqResponse: token=" + token);
mPresenceSubscriber.updatePresences(token, infos, true, null);
}
@Override
public void updatePublisherState(@PresenceBase.PresencePublishState int publishState) {
logi("updatePublisherState: from " + mPublishState + " to " + publishState);
mPublishState = publishState;
notifyPublishStateChanged(publishState);
}
private void addPublishRequestTaskId(int taskId) {
synchronized (mRequestingPublishTaskIds) {
mRequestingPublishTaskIds.add(taskId);
}
}
private void removePublishRequestTaskId(int taskId) {
synchronized (mRequestingPublishTaskIds) {
mRequestingPublishTaskIds.remove(taskId);
}
}
private boolean isPublishRequestExisted(Integer taskId) {
synchronized (mRequestingPublishTaskIds) {
return mRequestingPublishTaskIds.contains(taskId);
}
}
private void addRequestingAvailabilityTaskId(int taskId) {
synchronized (mPendingAvailabilityRequests) {
mPendingAvailabilityRequests.contains(taskId);
}
}
private void removeRequestingAvailabilityTaskId(int taskId) {
synchronized (mPendingAvailabilityRequests) {
mPendingAvailabilityRequests.remove(taskId);
}
}
private boolean isAvailabilityRequestExisted(Integer taskId) {
synchronized (mPendingAvailabilityRequests) {
return mPendingAvailabilityRequests.contains(taskId);
}
}
private boolean isCapabilityRequestExisted(Integer taskId) {
return mPendingCapabilityRequests.containsKey(taskId);
}
private static String getNumberFromUri(Uri uri) {
String number = uri.getSchemeSpecificPart();
String[] numberParts = number.split("[@;:]");
if (numberParts.length == 0) {
return null;
}
return numberParts[0];
}
private static int toUcePublishState(int publishState) {
switch (publishState) {
case PresenceBase.PUBLISH_STATE_200_OK:
return RcsUceAdapter.PUBLISH_STATE_OK;
case PresenceBase.PUBLISH_STATE_NOT_PUBLISHED:
return RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED;
case PresenceBase.PUBLISH_STATE_VOLTE_PROVISION_ERROR:
return RcsUceAdapter.PUBLISH_STATE_VOLTE_PROVISION_ERROR;
case PresenceBase.PUBLISH_STATE_RCS_PROVISION_ERROR:
return RcsUceAdapter.PUBLISH_STATE_RCS_PROVISION_ERROR;
case PresenceBase.PUBLISH_STATE_REQUEST_TIMEOUT:
return RcsUceAdapter.PUBLISH_STATE_REQUEST_TIMEOUT;
case PresenceBase.PUBLISH_STATE_OTHER_ERROR:
return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
default:
return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
}
}
private static int toUceError(int resultCode) {
switch (resultCode) {
case ResultCode.SUBSCRIBE_NOT_REGISTERED:
return RcsUceAdapter.ERROR_NOT_REGISTERED;
case ResultCode.SUBSCRIBE_REQUEST_TIMEOUT:
return RcsUceAdapter.ERROR_REQUEST_TIMEOUT;
case ResultCode.SUBSCRIBE_FORBIDDEN:
return RcsUceAdapter.ERROR_FORBIDDEN;
case ResultCode.SUBSCRIBE_NOT_FOUND:
return RcsUceAdapter.ERROR_NOT_FOUND;
case ResultCode.SUBSCRIBE_TOO_LARGE:
return RcsUceAdapter.ERROR_REQUEST_TOO_LARGE;
case ResultCode.SUBSCRIBE_INSUFFICIENT_MEMORY:
return RcsUceAdapter.ERROR_INSUFFICIENT_MEMORY;
case ResultCode.SUBSCRIBE_LOST_NETWORK:
return RcsUceAdapter.ERROR_LOST_NETWORK;
case ResultCode.SUBSCRIBE_ALREADY_IN_QUEUE:
return RcsUceAdapter.ERROR_ALREADY_IN_QUEUE;
default:
return RcsUceAdapter.ERROR_GENERIC_FAILURE;
}
}
/*
* Register receivers for updating capabilities
*/
private void registerReceivers() {
IntentFilter filter = new IntentFilter(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
mContext.registerReceiver(mReceiver, filter);
ContentResolver resolver = mContext.getContentResolver();
if (resolver != null) {
// Register mobile data content changed.
resolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.MOBILE_DATA), false,
mMobileDataObserver);
// Register SIM info content changed.
resolver.registerContentObserver(Telephony.SimInfo.CONTENT_URI, false,
mSimInfoContentObserver);
}
}
private void unregisterReceivers() {
mContext.unregisterReceiver(mReceiver);
ContentResolver resolver = mContext.getContentResolver();
if (resolver != null) {
resolver.unregisterContentObserver(mMobileDataObserver);
resolver.unregisterContentObserver(mSimInfoContentObserver);
}
}
/**
* Register IMS and provision content changed.
*
* Call the UceImplHandler#registerImsContentChangedReceiver instead of
* calling this method directly.
*/
private void registerImsContentChangedReceiverInternal(int subId) {
mUceImplHandler.removeRegisteringImsContentChangedReceiver();
try {
final int originalSubId = mSubId;
if ((originalSubId == subId) && (mImsContentChangedCallbackRegistered)) {
logi("registerImsContentChangedReceiverInternal: already registered. skip");
return;
}
// Unregister original IMS and Provision callback
unregisterImsProvisionCallback(originalSubId);
// Register new IMS and Provision callback
registerImsProvisionCallback(subId);
} catch (ImsException e) {
logw("registerImsContentChangedReceiverInternal error: " + e);
mUceImplHandler.retryRegisteringImsContentChangedReceiver(subId);
}
}
private void unregisterImsProvisionCallback(int subId) {
if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
return;
}
// Unregister IMS callback
ImsMmTelManager imsMmtelManager = getImsMmTelManager(subId);
if (imsMmtelManager != null) {
try {
imsMmtelManager.unregisterImsRegistrationCallback(mImsRegistrationCallback);
imsMmtelManager.unregisterMmTelCapabilityCallback(mCapabilityCallback);
} catch (RuntimeException e) {
logw("unregister IMS callback error: " + e.getMessage());
}
}
// Unregister provision changed callback
ProvisioningManager provisioningManager =
ProvisioningManager.createForSubscriptionId(subId);
try {
provisioningManager.unregisterProvisioningChangedCallback(mProvisioningChangedCallback);
} catch (RuntimeException e) {
logw("unregister provisioning callback error: " + e.getMessage());
}
// Remove all publish state callbacks
clearPublishStateCallbacks();
mImsContentChangedCallbackRegistered = false;
}
private void registerImsProvisionCallback(int subId) throws ImsException {
if (subId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
return;
}
// Register IMS callback
ImsMmTelManager imsMmtelManager = getImsMmTelManager(subId);
if (imsMmtelManager != null) {
imsMmtelManager.registerImsRegistrationCallback(mContext.getMainExecutor(),
mImsRegistrationCallback);
imsMmtelManager.registerMmTelCapabilityCallback(mContext.getMainExecutor(),
mCapabilityCallback);
}
// Register provision changed callback
ProvisioningManager provisioningManager =
ProvisioningManager.createForSubscriptionId(subId);
provisioningManager.registerProvisioningChangedCallback(mContext.getMainExecutor(),
mProvisioningChangedCallback);
mImsContentChangedCallbackRegistered = true;
logi("registerImsProvisionCallback");
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null) return;
switch (intent.getAction()) {
case TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED:
int preferredMode = intent.getIntExtra(
TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF);
logi("TTY preferred mode changed: " + preferredMode);
mPresencePublication.onTtyPreferredModeChanged(preferredMode);
break;
case Intent.ACTION_AIRPLANE_MODE_CHANGED:
boolean airplaneMode = intent.getBooleanExtra("state", false);
logi("Airplane mode changed: " + airplaneMode);
mPresencePublication.onAirplaneModeChanged(airplaneMode);
break;
}
}
};
private ContentObserver mMobileDataObserver = new ContentObserver(
new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
boolean isEnabled = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.MOBILE_DATA, 1) == 1;
logi("Mobile data changed: enabled=" + isEnabled);
mPresencePublication.onMobileDataChanged(isEnabled);
}
};
private ContentObserver mSimInfoContentObserver = new ContentObserver(
new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
if (mSubId <= SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
return;
}
ImsMmTelManager ims = getImsMmTelManager(mSubId);
if (ims == null) return;
try {
boolean isEnabled = ims.isVtSettingEnabled();
logi("SimInfo changed: VT setting=" + isEnabled);
mPresencePublication.onVtEnabled(isEnabled);
} catch (RuntimeException e) {
logw("SimInfo changed error: " + e);
}
}
};
private RegistrationManager.RegistrationCallback mImsRegistrationCallback =
new RegistrationManager.RegistrationCallback() {
@Override
public void onRegistered(int imsTransportType) {
logi("onRegistered: type=" + imsTransportType);
mNetworkRegistrationType = imsTransportType;
mPresencePublication.onImsConnected();
// Also trigger PresencePublication#onFeatureCapabilityChanged method
MmTelFeature.MmTelCapabilities capabilities = null;
synchronized (mCapabilitiesLock) {
capabilities = mMmTelCapabilities;
}
if (capabilities != null) {
mPresencePublication.onFeatureCapabilityChanged(mNetworkRegistrationType,
capabilities);
}
}
@Override
public void onUnregistered(ImsReasonInfo info) {
logi("onUnregistered");
mNetworkRegistrationType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
// Also trigger PresencePublication#onFeatureCapabilityChanged method
MmTelFeature.MmTelCapabilities capabilities = null;
synchronized (mCapabilitiesLock) {
capabilities = mMmTelCapabilities;
}
if (capabilities != null) {
mPresencePublication.onFeatureCapabilityChanged(mNetworkRegistrationType,
capabilities);
}
mPresencePublication.onImsDisconnected();
}
};
private ImsMmTelManager.CapabilityCallback mCapabilityCallback =
new ImsMmTelManager.CapabilityCallback() {
@Override
public void onCapabilitiesStatusChanged(MmTelFeature.MmTelCapabilities capabilities) {
if (capabilities == null) {
logw("onCapabilitiesStatusChanged: parameter is null");
return;
}
synchronized (mCapabilitiesLock) {
mMmTelCapabilities = capabilities;
}
mPresencePublication.onFeatureCapabilityChanged(mNetworkRegistrationType, capabilities);
}
};
private ProvisioningManager.Callback mProvisioningChangedCallback =
new ProvisioningManager.Callback() {
@Override
public void onProvisioningIntChanged(int item, int value) {
logi("onProvisioningIntChanged: item=" + item);
switch (item) {
case ProvisioningManager.KEY_EAB_PROVISIONING_STATUS:
case ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS:
case ProvisioningManager.KEY_VT_PROVISIONING_STATUS:
mPresencePublication.handleProvisioningChanged();
break;
default:
break;
}
}
};
private boolean isCapabilityDiscoveryEnabled(int subId) {
try {
ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId);
int discoveryEnabled = manager.getProvisioningIntValue(
ProvisioningManager.KEY_RCS_CAPABILITY_DISCOVERY_ENABLED);
return (discoveryEnabled == ProvisioningManager.PROVISIONING_VALUE_ENABLED);
} catch (Exception e) {
logw("isCapabilityDiscoveryEnabled error: " + e.getMessage());
}
return false;
}
private boolean isEabProvisioned(Context context, int subId) {
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
logw("isEabProvisioned error: invalid subscriptionId " + subId);
return false;
}
CarrierConfigManager configManager = (CarrierConfigManager)
context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
if (configManager != null) {
PersistableBundle config = configManager.getConfigForSubId(subId);
if (config != null && !config.getBoolean(
CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONED_BOOL)) {
return true;
}
}
try {
ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId);
int provisioningStatus = manager.getProvisioningIntValue(
ProvisioningManager.KEY_EAB_PROVISIONING_STATUS);
return (provisioningStatus == ProvisioningManager.PROVISIONING_VALUE_ENABLED);
} catch (Exception e) {
logw("isEabProvisioned error: " + e.getMessage());
}
return false;
}
private ImsMmTelManager getImsMmTelManager(int subId) {
try {
ImsManager imsManager = (ImsManager) mContext.getSystemService(
Context.TELEPHONY_IMS_SERVICE);
return (imsManager == null) ? null : imsManager.getImsMmTelManager(subId);
} catch (IllegalArgumentException e) {
logw("getImsMmTelManager error: " + e.getMessage());
return null;
}
}
private void logi(String log) {
Log.i(LOG_TAG, getLogPrefix().append(log).toString());
}
private void logw(String log) {
Log.w(LOG_TAG, getLogPrefix().append(log).toString());
}
private StringBuilder getLogPrefix() {
StringBuilder builder = new StringBuilder("[");
builder.append(mSlotId);
builder.append("->");
builder.append(mSubId);
builder.append("] ");
return builder;
}
}