| /* |
| * 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.RcsContactUceCapability.SOURCE_TYPE_CACHED; |
| |
| import android.content.Context; |
| import android.net.Uri; |
| import android.telecom.PhoneAccount; |
| import android.telecom.TelecomManager; |
| import android.telephony.AccessNetworkConstants; |
| import android.telephony.ims.ImsRegistrationAttributes; |
| import android.telephony.ims.RcsContactPresenceTuple; |
| import android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities; |
| import android.telephony.ims.RcsContactUceCapability; |
| import android.telephony.ims.RcsContactUceCapability.CapabilityMechanism; |
| import android.telephony.ims.RcsContactUceCapability.OptionsBuilder; |
| import android.telephony.ims.RcsContactUceCapability.PresenceBuilder; |
| import android.telephony.ims.feature.MmTelFeature; |
| import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities; |
| import android.util.IndentingPrintWriter; |
| import android.util.ArraySet; |
| import android.util.LocalLog; |
| import android.util.Log; |
| |
| import com.android.ims.rcs.uce.util.FeatureTags; |
| import com.android.ims.rcs.uce.util.UceUtils; |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| /** |
| * Stores the device's capabilities information. |
| */ |
| public class DeviceCapabilityInfo { |
| private static final String LOG_TAG = UceUtils.getLogPrefix() + "DeviceCapabilityInfo"; |
| |
| private final int mSubId; |
| |
| private final LocalLog mLocalLog = new LocalLog(UceUtils.LOG_SIZE); |
| |
| // FT overrides to add to the IMS registration, which will be added to the existing |
| // capabilities. |
| private final Set<String> mOverrideAddFeatureTags = new ArraySet<>(); |
| |
| // FT overrides to remove from the existing IMS registration, which will remove the related |
| // capabilities. |
| private final Set<String> mOverrideRemoveFeatureTags = new ArraySet<>(); |
| |
| // Tracks capability status based on the IMS registration. |
| private PublishServiceDescTracker mServiceCapRegTracker; |
| |
| // The feature tags associated with the last IMS registration update. |
| private Set<String> mLastRegistrationFeatureTags = Collections.emptySet(); |
| // The feature tags associated with the last IMS registration update, which also include |
| // overrides |
| private Set<String> mLastRegistrationOverrideFeatureTags = Collections.emptySet(); |
| |
| // The mmtel feature is registered or not |
| private boolean mMmtelRegistered; |
| |
| // The network type which ims mmtel registers on. |
| private int mMmtelNetworkRegType; |
| |
| // The list of the mmtel associated uris |
| private List<Uri> mMmtelAssociatedUris = Collections.emptyList(); |
| |
| // The rcs feature is registered or not |
| private boolean mRcsRegistered; |
| |
| // The list of the rcs associated uris |
| private List<Uri> mRcsAssociatedUris = Collections.emptyList(); |
| |
| // Whether or not presence is reported as capable |
| private boolean mPresenceCapable; |
| |
| // The network type which ims rcs registers on. |
| private int mRcsNetworkRegType; |
| |
| // The MMTel capabilities of this subscription Id |
| private MmTelFeature.MmTelCapabilities mMmTelCapabilities; |
| |
| // Whether the settings are changed or not |
| private int mTtyPreferredMode; |
| private boolean mMobileData; |
| private boolean mVtSetting; |
| |
| // The service description associated with the last publication update. |
| private final Set<ServiceDescription> mLastSuccessfulCapabilities = new ArraySet<>(); |
| // The service description to temporarily store the presence capability being sent. |
| private Set<ServiceDescription> mPendingPublishCapabilities; |
| |
| public DeviceCapabilityInfo(int subId, String[] capToRegistrationMap) { |
| mSubId = subId; |
| mServiceCapRegTracker = PublishServiceDescTracker.fromCarrierConfig(capToRegistrationMap); |
| reset(); |
| } |
| |
| /** |
| * Reset all the status. |
| */ |
| public synchronized void reset() { |
| logd("reset"); |
| mMmtelRegistered = false; |
| mMmtelNetworkRegType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID; |
| mRcsRegistered = false; |
| mRcsNetworkRegType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID; |
| mTtyPreferredMode = TelecomManager.TTY_MODE_OFF; |
| mMobileData = true; |
| mVtSetting = true; |
| mMmTelCapabilities = new MmTelCapabilities(); |
| mMmtelAssociatedUris = Collections.EMPTY_LIST; |
| mRcsAssociatedUris = Collections.EMPTY_LIST; |
| mLastSuccessfulCapabilities.clear(); |
| mPendingPublishCapabilities = null; |
| } |
| |
| /** |
| * Update the capability registration tracker feature tag override mapping. |
| * @return if true, this has caused a change in the Feature Tags associated with the device |
| * and a new PUBLISH should be generated. |
| */ |
| public synchronized boolean updateCapabilityRegistrationTrackerMap(String[] newMap) { |
| Set<String> oldTags = mServiceCapRegTracker.copyRegistrationFeatureTags(); |
| mServiceCapRegTracker = PublishServiceDescTracker.fromCarrierConfig(newMap); |
| mServiceCapRegTracker.updateImsRegistration(mLastRegistrationOverrideFeatureTags); |
| boolean changed = !oldTags.equals(mServiceCapRegTracker.copyRegistrationFeatureTags()); |
| if (changed) logi("Carrier Config Change resulted in associated FT list change"); |
| return changed; |
| } |
| |
| public synchronized boolean isImsRegistered() { |
| return mMmtelRegistered || mRcsRegistered; |
| } |
| |
| /** |
| * Update the status that IMS MMTEL is registered. |
| */ |
| public synchronized void updateImsMmtelRegistered(int type) { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("IMS MMTEL registered: original state=").append(mMmtelRegistered) |
| .append(", changes type from ").append(mMmtelNetworkRegType) |
| .append(" to ").append(type); |
| logi(builder.toString()); |
| |
| if (!mMmtelRegistered) { |
| mMmtelRegistered = true; |
| } |
| |
| if (mMmtelNetworkRegType != type) { |
| mMmtelNetworkRegType = type; |
| } |
| } |
| |
| /** |
| * Update the status that IMS MMTEL is unregistered. |
| */ |
| public synchronized boolean updateImsMmtelUnregistered() { |
| logi("IMS MMTEL unregistered: original state=" + mMmtelRegistered); |
| boolean changed = false; |
| if (mMmtelRegistered) { |
| mMmtelRegistered = false; |
| changed = true; |
| } |
| mMmtelNetworkRegType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID; |
| mLastSuccessfulCapabilities.clear(); |
| mPendingPublishCapabilities = null; |
| return changed; |
| } |
| |
| /** |
| * Update the MMTel associated URIs which are provided by the IMS service. |
| */ |
| public synchronized void updateMmTelAssociatedUri(Uri[] uris) { |
| int originalSize = mMmtelAssociatedUris.size(); |
| if (uris != null) { |
| mMmtelAssociatedUris = Arrays.stream(uris) |
| .filter(Objects::nonNull) |
| .collect(Collectors.toList()); |
| } else { |
| mMmtelAssociatedUris.clear(); |
| } |
| int currentSize = mMmtelAssociatedUris.size(); |
| logd("updateMmTelAssociatedUri: size from " + originalSize + " to " + currentSize); |
| } |
| |
| /** |
| * Get the MMTEL associated URI. When there are multiple uris in the list, take the first uri. |
| * Return null if the list of the MMTEL associated uri is empty. |
| */ |
| public synchronized Uri getMmtelAssociatedUri() { |
| if (!mMmtelAssociatedUris.isEmpty()) { |
| return mMmtelAssociatedUris.get(0); |
| } |
| return null; |
| } |
| |
| /** |
| * Update the status that IMS RCS is registered. |
| * @return true if the IMS registration status changed, false if it did not. |
| */ |
| public synchronized boolean updateImsRcsRegistered(ImsRegistrationAttributes attr) { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("IMS RCS registered: original state=").append(mRcsRegistered) |
| .append(", changes type from ").append(mRcsNetworkRegType) |
| .append(" to ").append(attr.getTransportType()); |
| logi(builder.toString()); |
| |
| boolean changed = false; |
| if (!mRcsRegistered) { |
| mRcsRegistered = true; |
| changed = true; |
| } |
| |
| if (mRcsNetworkRegType != attr.getTransportType()) { |
| mRcsNetworkRegType = attr.getTransportType(); |
| changed = true; |
| } |
| |
| mLastRegistrationFeatureTags = attr.getFeatureTags(); |
| changed |= updateRegistration(mLastRegistrationFeatureTags); |
| |
| return changed; |
| } |
| |
| /** |
| * Update the status that IMS RCS is unregistered. |
| */ |
| public synchronized boolean updateImsRcsUnregistered() { |
| logi("IMS RCS unregistered: original state=" + mRcsRegistered); |
| boolean changed = false; |
| if (mRcsRegistered) { |
| mRcsRegistered = false; |
| changed = true; |
| } |
| |
| mLastRegistrationFeatureTags = Collections.emptySet(); |
| updateRegistration(mLastRegistrationFeatureTags); |
| mRcsNetworkRegType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID; |
| mLastSuccessfulCapabilities.clear(); |
| mPendingPublishCapabilities = null; |
| return changed; |
| } |
| |
| /** |
| * Update the RCS associated URIs which is provided by the IMS service. |
| */ |
| public synchronized void updateRcsAssociatedUri(Uri[] uris) { |
| int originalSize = mRcsAssociatedUris.size(); |
| if (uris != null) { |
| mRcsAssociatedUris = Arrays.stream(uris) |
| .filter(Objects::nonNull) |
| .collect(Collectors.toList()); |
| } else { |
| mRcsAssociatedUris.clear(); |
| } |
| int currentSize = mRcsAssociatedUris.size(); |
| logd("updateRcsAssociatedUri: size from " + originalSize + " to " + currentSize); |
| } |
| |
| /** |
| * Get the RCS associated URI. When there are multiple uris in the list, take the first uri. |
| * Return null if the list of the RCS associated uri is empty. |
| */ |
| public synchronized Uri getRcsAssociatedUri() { |
| if (!mRcsAssociatedUris.isEmpty()) { |
| return mRcsAssociatedUris.get(0); |
| } |
| return null; |
| } |
| |
| /** |
| * Get the first URI from the "p-associated-uri" header included in the IMS registration |
| * response. |
| * @param preferTelUri If {@code true}, prefer returning the first TEL URI. If no TEL |
| * URIs exist, this method will still return the preferred (first) SIP URI |
| * in the header. If {@code false}, we will return the first URI |
| * in the "p-associated-uri" header, independent of the URI scheme. |
| */ |
| public synchronized Uri getImsAssociatedUri(boolean preferTelUri) { |
| if (preferTelUri) { |
| if (!mRcsAssociatedUris.isEmpty()) { |
| for (Uri rcsAssociatedUri : mRcsAssociatedUris) { |
| if (PhoneAccount.SCHEME_TEL.equalsIgnoreCase(rcsAssociatedUri.getScheme())) { |
| return rcsAssociatedUri; |
| } |
| } |
| } |
| if (!mMmtelAssociatedUris.isEmpty()) { |
| for (Uri mmtelAssociatedUri : mMmtelAssociatedUris) { |
| if (PhoneAccount.SCHEME_TEL.equalsIgnoreCase(mmtelAssociatedUri.getScheme())) { |
| return mmtelAssociatedUri; |
| } |
| } |
| } |
| } |
| |
| // Either we have not found a TEL URI or we do not prefer TEL URIs. Get the first URI from |
| // p-associated-uri list. |
| if (!mRcsAssociatedUris.isEmpty()) { |
| return mRcsAssociatedUris.get(0); |
| } else if (!mMmtelAssociatedUris.isEmpty()) { |
| return mMmtelAssociatedUris.get(0); |
| } else { |
| return null; |
| } |
| } |
| |
| public synchronized boolean addRegistrationOverrideCapabilities(Set<String> featureTags) { |
| logd("override - add: " + featureTags); |
| mOverrideRemoveFeatureTags.removeAll(featureTags); |
| mOverrideAddFeatureTags.addAll(featureTags); |
| // Call with the last feature tags so that the new ones will be potentially picked up. |
| return updateRegistration(mLastRegistrationFeatureTags); |
| }; |
| |
| public synchronized boolean removeRegistrationOverrideCapabilities(Set<String> featureTags) { |
| logd("override - remove: " + featureTags); |
| mOverrideAddFeatureTags.removeAll(featureTags); |
| mOverrideRemoveFeatureTags.addAll(featureTags); |
| // Call with the last feature tags so that the new ones will be potentially picked up. |
| return updateRegistration(mLastRegistrationFeatureTags); |
| }; |
| |
| public synchronized boolean clearRegistrationOverrideCapabilities() { |
| logd("override - clear"); |
| mOverrideAddFeatureTags.clear(); |
| mOverrideRemoveFeatureTags.clear(); |
| // Call with the last feature tags so that base tags will be restored |
| return updateRegistration(mLastRegistrationFeatureTags); |
| }; |
| |
| /** |
| * Update the IMS registration tracked by the PublishServiceDescTracker if needed. |
| * @return true if the registration changed, else otherwise. |
| */ |
| private boolean updateRegistration(Set<String> baseTags) { |
| Set<String> updatedTags = updateImsRegistrationFeatureTags(baseTags); |
| if (!mLastRegistrationOverrideFeatureTags.equals(updatedTags)) { |
| mLastRegistrationOverrideFeatureTags = updatedTags; |
| mServiceCapRegTracker.updateImsRegistration(updatedTags); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Combine IMS registration with overrides to produce a new feature tag Set. |
| * @return true if the IMS registration changed, false otherwise. |
| */ |
| private synchronized Set<String> updateImsRegistrationFeatureTags(Set<String> featureTags) { |
| Set<String> tags = new ArraySet<>(featureTags); |
| tags.addAll(mOverrideAddFeatureTags); |
| tags.removeAll(mOverrideRemoveFeatureTags); |
| return tags; |
| } |
| |
| /** |
| * Update the TTY preferred mode. |
| * @return {@code true} if tty preferred mode is changed, {@code false} otherwise. |
| */ |
| public synchronized boolean updateTtyPreferredMode(int ttyMode) { |
| if (mTtyPreferredMode != ttyMode) { |
| logd("TTY preferred mode changes from " + mTtyPreferredMode + " to " + ttyMode); |
| mTtyPreferredMode = ttyMode; |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Update mobile data setting. |
| * @return {@code true} if the mobile data setting is changed, {@code false} otherwise. |
| */ |
| public synchronized boolean updateMobileData(boolean mobileData) { |
| if (mMobileData != mobileData) { |
| logd("Mobile data changes from " + mMobileData + " to " + mobileData); |
| mMobileData = mobileData; |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Update VT setting. |
| * @return {@code true} if vt setting is changed, {@code false}.otherwise. |
| */ |
| public synchronized boolean updateVtSetting(boolean vtSetting) { |
| if (mVtSetting != vtSetting) { |
| logd("VT setting changes from " + mVtSetting + " to " + vtSetting); |
| mVtSetting = vtSetting; |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Update the MMTEL capabilities if the capabilities is changed. |
| * @return {@code true} if the mmtel capabilities are changed, {@code false} otherwise. |
| */ |
| public synchronized boolean updateMmtelCapabilitiesChanged(MmTelCapabilities capabilities) { |
| if (capabilities == null) { |
| return false; |
| } |
| boolean oldVolteAvailable = isVolteAvailable(mMmtelNetworkRegType, mMmTelCapabilities); |
| boolean oldVoWifiAvailable = isVoWifiAvailable(mMmtelNetworkRegType, mMmTelCapabilities); |
| boolean oldVtAvailable = isVtAvailable(mMmtelNetworkRegType, mMmTelCapabilities); |
| boolean oldViWifiAvailable = isViWifiAvailable(mMmtelNetworkRegType, mMmTelCapabilities); |
| boolean oldCallComposerAvailable = isCallComposerAvailable(mMmTelCapabilities); |
| |
| boolean volteAvailable = isVolteAvailable(mMmtelNetworkRegType, capabilities); |
| boolean voWifiAvailable = isVoWifiAvailable(mMmtelNetworkRegType, capabilities); |
| boolean vtAvailable = isVtAvailable(mMmtelNetworkRegType, capabilities); |
| boolean viWifiAvailable = isViWifiAvailable(mMmtelNetworkRegType, capabilities); |
| boolean callComposerAvailable = isCallComposerAvailable(capabilities); |
| |
| logd("updateMmtelCapabilitiesChanged: from " + mMmTelCapabilities + " to " + capabilities); |
| |
| // Update to the new mmtel capabilities |
| mMmTelCapabilities = deepCopyCapabilities(capabilities); |
| |
| if (oldVolteAvailable != volteAvailable |
| || oldVoWifiAvailable != voWifiAvailable |
| || oldVtAvailable != vtAvailable |
| || oldViWifiAvailable != viWifiAvailable |
| || oldCallComposerAvailable != callComposerAvailable) { |
| return true; |
| } |
| return false; |
| } |
| |
| public synchronized void updatePresenceCapable(boolean isCapable) { |
| mPresenceCapable = isCapable; |
| } |
| |
| public synchronized boolean isPresenceCapable() { |
| return mPresenceCapable; |
| } |
| |
| // Get the device's capabilities with the PRESENCE mechanism. |
| public RcsContactUceCapability getChangedPresenceCapability(Context context) { |
| if (context == null) { |
| return null; |
| } |
| Set<ServiceDescription> capableFromReg = |
| mServiceCapRegTracker.copyRegistrationCapabilities(); |
| if (isPresenceCapabilityChanged(capableFromReg)) { |
| RcsContactUceCapability rcsContactUceCapability = getPresenceCapabilities(context); |
| if (rcsContactUceCapability != null) { |
| mPendingPublishCapabilities = mServiceCapRegTracker.copyRegistrationCapabilities(); |
| } |
| return rcsContactUceCapability; |
| } |
| return null; |
| } |
| |
| public void setPresencePublishResult(boolean isSuccess) { |
| if (isSuccess) { |
| mLastSuccessfulCapabilities.clear(); |
| if (mPendingPublishCapabilities != null) { |
| mLastSuccessfulCapabilities.addAll(mPendingPublishCapabilities); |
| } |
| } |
| mPendingPublishCapabilities = null; |
| } |
| |
| public void resetPresenceCapability() { |
| mLastSuccessfulCapabilities.clear(); |
| mPendingPublishCapabilities = null; |
| } |
| |
| public List<RcsContactPresenceTuple> getLastSuccessfulPresenceTuplesWithoutContactUri() { |
| List<RcsContactPresenceTuple> presenceTuples = new ArrayList<>(); |
| if (mLastSuccessfulCapabilities.isEmpty()) { |
| return presenceTuples; |
| } |
| |
| for (ServiceDescription capability : mLastSuccessfulCapabilities) { |
| presenceTuples.add(capability.getTupleBuilder().build()); |
| } |
| return presenceTuples; |
| } |
| |
| @VisibleForTesting |
| public void addLastSuccessfulServiceDescription(ServiceDescription capability) { |
| mLastSuccessfulCapabilities.add(capability); |
| } |
| |
| @VisibleForTesting |
| public boolean isPresenceCapabilityChanged(Set<ServiceDescription> capableFromReg) { |
| if (mLastSuccessfulCapabilities.isEmpty()) { |
| return true; |
| } |
| |
| if (capableFromReg.equals(mLastSuccessfulCapabilities)) { |
| return false; |
| } |
| return true; |
| } |
| |
| private boolean isVolteAvailable(int networkRegType, MmTelCapabilities capabilities) { |
| return (networkRegType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) |
| && capabilities.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE); |
| } |
| |
| private boolean isVoWifiAvailable(int networkRegType, MmTelCapabilities capabilities) { |
| return (networkRegType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) |
| && capabilities.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE); |
| } |
| |
| private boolean isVtAvailable(int networkRegType, MmTelCapabilities capabilities) { |
| return (networkRegType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) |
| && capabilities.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO); |
| } |
| |
| private boolean isViWifiAvailable(int networkRegType, MmTelCapabilities capabilities) { |
| return (networkRegType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) |
| && capabilities.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO); |
| } |
| |
| private boolean isCallComposerAvailable(MmTelCapabilities capabilities) { |
| return capabilities.isCapable( |
| MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER); |
| } |
| |
| /** |
| * Get the device's capabilities. |
| */ |
| public synchronized RcsContactUceCapability getDeviceCapabilities( |
| @CapabilityMechanism int mechanism, Context context) { |
| switch (mechanism) { |
| case RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE: |
| RcsContactUceCapability rcsContactUceCapability = getPresenceCapabilities(context); |
| if (rcsContactUceCapability != null) { |
| mPendingPublishCapabilities = |
| mServiceCapRegTracker.copyRegistrationCapabilities(); |
| } |
| return rcsContactUceCapability; |
| case RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS: |
| return getOptionsCapabilities(context); |
| default: |
| logw("getDeviceCapabilities: invalid mechanism " + mechanism); |
| return null; |
| } |
| } |
| |
| // Get the device's capabilities with the PRESENCE mechanism. |
| private RcsContactUceCapability getPresenceCapabilities(Context context) { |
| Uri uri = PublishUtils.getDeviceContactUri(context, mSubId, this, true); |
| if (uri == null) { |
| logw("getPresenceCapabilities: uri is empty"); |
| return null; |
| } |
| Set<ServiceDescription> capableFromReg = |
| mServiceCapRegTracker.copyRegistrationCapabilities(); |
| |
| PresenceBuilder presenceBuilder = new PresenceBuilder(uri, |
| RcsContactUceCapability.SOURCE_TYPE_CACHED, |
| RcsContactUceCapability.REQUEST_RESULT_FOUND); |
| // RCS presence tag (added to all presence documents) |
| ServiceDescription presDescription = getCustomizedDescription( |
| ServiceDescription.SERVICE_DESCRIPTION_PRESENCE, capableFromReg); |
| addCapability(presenceBuilder, presDescription.getTupleBuilder(), uri); |
| capableFromReg.remove(presDescription); |
| |
| // mmtel |
| ServiceDescription voiceDescription = getCustomizedDescription( |
| ServiceDescription.SERVICE_DESCRIPTION_MMTEL_VOICE, capableFromReg); |
| ServiceDescription vtDescription = getCustomizedDescription( |
| ServiceDescription.SERVICE_DESCRIPTION_MMTEL_VOICE_VIDEO, capableFromReg); |
| ServiceDescription descToUse = (hasVolteCapability() && hasVtCapability()) ? |
| vtDescription : voiceDescription; |
| ServiceCapabilities servCaps = new ServiceCapabilities.Builder( |
| hasVolteCapability(), hasVtCapability()) |
| .addSupportedDuplexMode(ServiceCapabilities.DUPLEX_MODE_FULL).build(); |
| addCapability(presenceBuilder, descToUse.getTupleBuilder() |
| .setServiceCapabilities(servCaps), uri); |
| capableFromReg.remove(voiceDescription); |
| capableFromReg.remove(vtDescription); |
| |
| // call composer via mmtel |
| ServiceDescription composerDescription = getCustomizedDescription( |
| ServiceDescription.SERVICE_DESCRIPTION_CALL_COMPOSER_MMTEL, capableFromReg); |
| if (hasCallComposerCapability()) { |
| addCapability(presenceBuilder, composerDescription.getTupleBuilder(), uri); |
| } |
| capableFromReg.remove(composerDescription); |
| |
| // External features can only be found using registration states from other components. |
| // Count these features as capable and include in PIDF XML if they are registered. |
| for (ServiceDescription capability : capableFromReg) { |
| addCapability(presenceBuilder, capability.getTupleBuilder(), uri); |
| } |
| |
| return presenceBuilder.build(); |
| } |
| |
| /** |
| * Search the refSet for the ServiceDescription that matches the service-id && version and |
| * return that or return the reference if there is no match. |
| */ |
| private ServiceDescription getCustomizedDescription(ServiceDescription reference, |
| Set<ServiceDescription> refSet) { |
| return refSet.stream().filter(s -> s.serviceId.equals(reference.serviceId) |
| && s.version.equals(reference.version)).findFirst().orElse(reference); |
| } |
| |
| // Get the device's capabilities with the OPTIONS mechanism. |
| private RcsContactUceCapability getOptionsCapabilities(Context context) { |
| Uri uri = PublishUtils.getDeviceContactUri(context, mSubId, this, false); |
| if (uri == null) { |
| logw("getOptionsCapabilities: uri is empty"); |
| return null; |
| } |
| |
| Set<String> capableFromReg = mServiceCapRegTracker.copyRegistrationFeatureTags(); |
| |
| OptionsBuilder optionsBuilder = new OptionsBuilder(uri, SOURCE_TYPE_CACHED); |
| optionsBuilder.setRequestResult(RcsContactUceCapability.REQUEST_RESULT_FOUND); |
| FeatureTags.addFeatureTags(optionsBuilder, hasVolteCapability(), hasVtCapability(), |
| isPresenceCapable(), hasCallComposerCapability(), capableFromReg); |
| return optionsBuilder.build(); |
| } |
| |
| private void addCapability(RcsContactUceCapability.PresenceBuilder presenceBuilder, |
| RcsContactPresenceTuple.Builder tupleBuilder, Uri contactUri) { |
| presenceBuilder.addCapabilityTuple(tupleBuilder.setContactUri(contactUri).build()); |
| } |
| |
| // Check if the device has the VoLTE capability |
| private synchronized boolean hasVolteCapability() { |
| return overrideCapability(FeatureTags.FEATURE_TAG_MMTEL, mMmTelCapabilities != null |
| && mMmTelCapabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VOICE)); |
| } |
| |
| // Check if the device has the VT capability |
| private synchronized boolean hasVtCapability() { |
| return overrideCapability(FeatureTags.FEATURE_TAG_VIDEO, mMmTelCapabilities != null |
| && mMmTelCapabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VIDEO)); |
| } |
| |
| // Check if the device has the Call Composer capability |
| private synchronized boolean hasCallComposerCapability() { |
| return overrideCapability(FeatureTags.FEATURE_TAG_CALL_COMPOSER_VIA_TELEPHONY, |
| mMmTelCapabilities != null && mMmTelCapabilities.isCapable( |
| MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER)); |
| } |
| |
| /** |
| * @return the overridden value for the provided feature tag or the original capability if there |
| * is no override. |
| */ |
| private synchronized boolean overrideCapability(String featureTag, boolean originalCap) { |
| if (mOverrideRemoveFeatureTags.contains(featureTag)) { |
| return false; |
| } |
| |
| if (mOverrideAddFeatureTags.contains(featureTag)) { |
| return true; |
| } |
| |
| return originalCap; |
| } |
| |
| private synchronized MmTelCapabilities deepCopyCapabilities(MmTelCapabilities capabilities) { |
| MmTelCapabilities mmTelCapabilities = new MmTelCapabilities(); |
| if (capabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VOICE)) { |
| mmTelCapabilities.addCapabilities(MmTelCapabilities.CAPABILITY_TYPE_VOICE); |
| } |
| if (capabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VIDEO)) { |
| mmTelCapabilities.addCapabilities(MmTelCapabilities.CAPABILITY_TYPE_VIDEO); |
| } |
| if (capabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_UT)) { |
| mmTelCapabilities.addCapabilities(MmTelCapabilities.CAPABILITY_TYPE_UT); |
| } |
| if (capabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_SMS)) { |
| mmTelCapabilities.addCapabilities(MmTelCapabilities.CAPABILITY_TYPE_SMS); |
| } |
| if (capabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER)) { |
| mmTelCapabilities.addCapabilities(MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER); |
| } |
| return mmTelCapabilities; |
| } |
| |
| 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; |
| } |
| |
| public void dump(PrintWriter printWriter) { |
| IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); |
| pw.println("DeviceCapabilityInfo :"); |
| pw.increaseIndent(); |
| |
| mServiceCapRegTracker.dump(pw); |
| |
| pw.println("Log:"); |
| pw.increaseIndent(); |
| mLocalLog.dump(pw); |
| pw.decreaseIndent(); |
| |
| pw.decreaseIndent(); |
| } |
| } |