/* * 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 mOverrideAddFeatureTags = new ArraySet<>(); // FT overrides to remove from the existing IMS registration, which will remove the related // capabilities. private final Set 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 mLastRegistrationFeatureTags = Collections.emptySet(); // The feature tags associated with the last IMS registration update, which also include // overrides private Set 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 mMmtelAssociatedUris = Collections.emptyList(); // The rcs feature is registered or not private boolean mRcsRegistered; // The list of the rcs associated uris private List 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 mLastSuccessfulCapabilities = new ArraySet<>(); // The service description to temporarily store the presence capability being sent. private Set 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 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 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 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 baseTags) { Set 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 updateImsRegistrationFeatureTags(Set featureTags) { Set 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 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 getLastSuccessfulPresenceTuplesWithoutContactUri() { List 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 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 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 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 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(); } }