1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ims.rcs.uce.presence.publish;
18 
19 import static android.telephony.ims.RcsContactUceCapability.SOURCE_TYPE_CACHED;
20 
21 import android.content.Context;
22 import android.net.Uri;
23 import android.telecom.TelecomManager;
24 import android.telephony.AccessNetworkConstants;
25 import android.telephony.ims.ImsRegistrationAttributes;
26 import android.telephony.ims.RcsContactPresenceTuple;
27 import android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities;
28 import android.telephony.ims.RcsContactUceCapability;
29 import android.telephony.ims.RcsContactUceCapability.CapabilityMechanism;
30 import android.telephony.ims.RcsContactUceCapability.OptionsBuilder;
31 import android.telephony.ims.RcsContactUceCapability.PresenceBuilder;
32 import android.telephony.ims.feature.MmTelFeature;
33 import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
34 import android.util.IndentingPrintWriter;
35 import android.util.ArraySet;
36 import android.util.LocalLog;
37 import android.util.Log;
38 
39 import com.android.ims.rcs.uce.util.FeatureTags;
40 import com.android.ims.rcs.uce.util.UceUtils;
41 
42 import java.io.PrintWriter;
43 import java.util.Arrays;
44 import java.util.Collections;
45 import java.util.List;
46 import java.util.Objects;
47 import java.util.Set;
48 import java.util.stream.Collectors;
49 
50 /**
51  * Stores the device's capabilities information.
52  */
53 public class DeviceCapabilityInfo {
54     private static final String LOG_TAG = UceUtils.getLogPrefix() + "DeviceCapabilityInfo";
55 
56     private final int mSubId;
57 
58     private final LocalLog mLocalLog = new LocalLog(UceUtils.LOG_SIZE);
59 
60     // FT overrides to add to the IMS registration, which will be added to the existing
61     // capabilities.
62     private final Set<String> mOverrideAddFeatureTags = new ArraySet<>();
63 
64     // FT overrides to remove from the existing IMS registration, which will remove the related
65     // capabilities.
66     private final Set<String> mOverrideRemoveFeatureTags = new ArraySet<>();
67 
68     // Tracks capability status based on the IMS registration.
69     private PublishServiceDescTracker mServiceCapRegTracker;
70 
71     // The feature tags associated with the last IMS registration update.
72     private Set<String> mLastRegistrationFeatureTags = Collections.emptySet();
73     // The feature tags associated with the last IMS registration update, which also include
74     // overrides
75     private Set<String> mLastRegistrationOverrideFeatureTags = Collections.emptySet();
76 
77     // The mmtel feature is registered or not
78     private boolean mMmtelRegistered;
79 
80     // The network type which ims mmtel registers on.
81     private int mMmtelNetworkRegType;
82 
83     // The list of the mmtel associated uris
84     private List<Uri> mMmtelAssociatedUris = Collections.emptyList();
85 
86     // The rcs feature is registered or not
87     private boolean mRcsRegistered;
88 
89     // The list of the rcs associated uris
90     private List<Uri> mRcsAssociatedUris = Collections.emptyList();
91 
92     // Whether or not presence is reported as capable
93     private boolean mPresenceCapable;
94 
95     // The network type which ims rcs registers on.
96     private int mRcsNetworkRegType;
97 
98     // The MMTel capabilities of this subscription Id
99     private MmTelFeature.MmTelCapabilities mMmTelCapabilities;
100 
101     // Whether the settings are changed or not
102     private int mTtyPreferredMode;
103     private boolean mAirplaneMode;
104     private boolean mMobileData;
105     private boolean mVtSetting;
106 
DeviceCapabilityInfo(int subId, String[] capToRegistrationMap)107     public DeviceCapabilityInfo(int subId, String[] capToRegistrationMap) {
108         mSubId = subId;
109         mServiceCapRegTracker = PublishServiceDescTracker.fromCarrierConfig(capToRegistrationMap);
110         reset();
111     }
112 
113     /**
114      * Reset all the status.
115      */
reset()116     public synchronized void reset() {
117         logd("reset");
118         mMmtelRegistered = false;
119         mMmtelNetworkRegType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
120         mRcsRegistered = false;
121         mRcsNetworkRegType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
122         mTtyPreferredMode = TelecomManager.TTY_MODE_OFF;
123         mAirplaneMode = false;
124         mMobileData = true;
125         mVtSetting = true;
126         mMmTelCapabilities = new MmTelCapabilities();
127         mMmtelAssociatedUris = Collections.EMPTY_LIST;
128         mRcsAssociatedUris = Collections.EMPTY_LIST;
129     }
130 
131     /**
132      * Update the capability registration tracker feature tag override mapping.
133      * @return if true, this has caused a change in the Feature Tags associated with the device
134      * and a new PUBLISH should be generated.
135      */
updateCapabilityRegistrationTrackerMap(String[] newMap)136     public synchronized boolean updateCapabilityRegistrationTrackerMap(String[] newMap) {
137         Set<String> oldTags = mServiceCapRegTracker.copyRegistrationFeatureTags();
138         mServiceCapRegTracker = PublishServiceDescTracker.fromCarrierConfig(newMap);
139         mServiceCapRegTracker.updateImsRegistration(mLastRegistrationOverrideFeatureTags);
140         boolean changed = !oldTags.equals(mServiceCapRegTracker.copyRegistrationFeatureTags());
141         if (changed) logi("Carrier Config Change resulted in associated FT list change");
142         return changed;
143     }
144 
isImsRegistered()145     public synchronized boolean isImsRegistered() {
146         return mMmtelRegistered || mRcsRegistered;
147     }
148 
149     /**
150      * Update the status that IMS MMTEL is registered.
151      */
updateImsMmtelRegistered(int type)152     public synchronized void updateImsMmtelRegistered(int type) {
153         StringBuilder builder = new StringBuilder();
154         builder.append("IMS MMTEL registered: original state=").append(mMmtelRegistered)
155                 .append(", changes type from ").append(mMmtelNetworkRegType)
156                 .append(" to ").append(type);
157         logi(builder.toString());
158 
159         if (!mMmtelRegistered) {
160             mMmtelRegistered = true;
161         }
162 
163         if (mMmtelNetworkRegType != type) {
164             mMmtelNetworkRegType = type;
165         }
166     }
167 
168     /**
169      * Update the status that IMS MMTEL is unregistered.
170      */
updateImsMmtelUnregistered()171     public synchronized void updateImsMmtelUnregistered() {
172         logi("IMS MMTEL unregistered: original state=" + mMmtelRegistered);
173         if (mMmtelRegistered) {
174             mMmtelRegistered = false;
175         }
176         mMmtelNetworkRegType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
177     }
178 
179     /**
180      * Update the MMTel associated URIs which are provided by the IMS service.
181      */
updateMmTelAssociatedUri(Uri[] uris)182     public synchronized void updateMmTelAssociatedUri(Uri[] uris) {
183         int originalSize = mMmtelAssociatedUris.size();
184         if (uris != null) {
185             mMmtelAssociatedUris = Arrays.stream(uris)
186                     .filter(Objects::nonNull)
187                     .collect(Collectors.toList());
188         } else {
189             mMmtelAssociatedUris.clear();
190         }
191         int currentSize = mMmtelAssociatedUris.size();
192         logd("updateMmTelAssociatedUri: size from " + originalSize + " to " + currentSize);
193     }
194 
195     /**
196      * Get the MMTEL associated URI. When there are multiple uris in the list, take the first uri.
197      * Return null if the list of the MMTEL associated uri is empty.
198      */
getMmtelAssociatedUri()199     public synchronized Uri getMmtelAssociatedUri() {
200         if (!mMmtelAssociatedUris.isEmpty()) {
201             return mMmtelAssociatedUris.get(0);
202         }
203         return null;
204     }
205 
206     /**
207      * Update the status that IMS RCS is registered.
208      * @return true if the IMS registration status changed, false if it did not.
209      */
updateImsRcsRegistered(ImsRegistrationAttributes attr)210     public synchronized boolean updateImsRcsRegistered(ImsRegistrationAttributes attr) {
211         StringBuilder builder = new StringBuilder();
212         builder.append("IMS RCS registered: original state=").append(mRcsRegistered)
213                 .append(", changes type from ").append(mRcsNetworkRegType)
214                 .append(" to ").append(attr.getTransportType());
215         logi(builder.toString());
216 
217         boolean changed = false;
218         if (!mRcsRegistered) {
219             mRcsRegistered = true;
220             changed = true;
221         }
222 
223         if (mRcsNetworkRegType != attr.getTransportType()) {
224             mRcsNetworkRegType = attr.getTransportType();
225             changed = true;
226         }
227 
228         mLastRegistrationFeatureTags = attr.getFeatureTags();
229         changed |= updateRegistration(mLastRegistrationFeatureTags);
230 
231         return changed;
232     }
233 
234     /**
235      * Update the status that IMS RCS is unregistered.
236      */
updateImsRcsUnregistered()237     public synchronized boolean updateImsRcsUnregistered() {
238         logi("IMS RCS unregistered: original state=" + mRcsRegistered);
239         boolean changed = false;
240         if (mRcsRegistered) {
241             mRcsRegistered = false;
242             changed = true;
243         }
244         mRcsNetworkRegType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
245         return changed;
246     }
247 
248     /**
249      * Update the RCS associated URIs which is provided by the IMS service.
250      */
updateRcsAssociatedUri(Uri[] uris)251     public synchronized void updateRcsAssociatedUri(Uri[] uris) {
252         int originalSize = mRcsAssociatedUris.size();
253         if (uris != null) {
254             mRcsAssociatedUris = Arrays.stream(uris)
255                     .filter(Objects::nonNull)
256                     .collect(Collectors.toList());
257         } else {
258             mRcsAssociatedUris.clear();
259         }
260         int currentSize = mRcsAssociatedUris.size();
261         logd("updateRcsAssociatedUri: size from " + originalSize + " to " + currentSize);
262     }
263 
264     /**
265      * Get the RCS associated URI. When there are multiple uris in the list, take the first uri.
266      * Return null if the list of the RCS associated uri is empty.
267      */
getRcsAssociatedUri()268     public synchronized Uri getRcsAssociatedUri() {
269         if (!mRcsAssociatedUris.isEmpty()) {
270             return mRcsAssociatedUris.get(0);
271         }
272         return null;
273     }
274 
275     /**
276      * Get the IMS associated URI. It will first get the uri of MMTEL if it is not empty, otherwise
277      * it will try to get the uri of RCS. The null will be returned if both MMTEL and RCS are empty.
278      */
getImsAssociatedUri()279     public synchronized Uri getImsAssociatedUri() {
280         if (!mRcsAssociatedUris.isEmpty()) {
281             return mRcsAssociatedUris.get(0);
282         } else if (!mMmtelAssociatedUris.isEmpty()) {
283             return mMmtelAssociatedUris.get(0);
284         } else {
285             return null;
286         }
287     }
288 
addRegistrationOverrideCapabilities(Set<String> featureTags)289     public synchronized boolean addRegistrationOverrideCapabilities(Set<String> featureTags) {
290         logd("override - add: " + featureTags);
291         mOverrideRemoveFeatureTags.removeAll(featureTags);
292         mOverrideAddFeatureTags.addAll(featureTags);
293         // Call with the last feature tags so that the new ones will be potentially picked up.
294         return updateRegistration(mLastRegistrationFeatureTags);
295     };
296 
removeRegistrationOverrideCapabilities(Set<String> featureTags)297     public synchronized boolean removeRegistrationOverrideCapabilities(Set<String> featureTags) {
298         logd("override - remove: " + featureTags);
299         mOverrideAddFeatureTags.removeAll(featureTags);
300         mOverrideRemoveFeatureTags.addAll(featureTags);
301         // Call with the last feature tags so that the new ones will be potentially picked up.
302         return updateRegistration(mLastRegistrationFeatureTags);
303     };
304 
clearRegistrationOverrideCapabilities()305     public synchronized boolean clearRegistrationOverrideCapabilities() {
306         logd("override - clear");
307         mOverrideAddFeatureTags.clear();
308         mOverrideRemoveFeatureTags.clear();
309         // Call with the last feature tags so that base tags will be restored
310         return updateRegistration(mLastRegistrationFeatureTags);
311     };
312 
313     /**
314      * Update the IMS registration tracked by the PublishServiceDescTracker if needed.
315      * @return true if the registration changed, else otherwise.
316      */
updateRegistration(Set<String> baseTags)317     private boolean updateRegistration(Set<String> baseTags) {
318         Set<String> updatedTags = updateImsRegistrationFeatureTags(baseTags);
319         if (!mLastRegistrationOverrideFeatureTags.equals(updatedTags)) {
320             mLastRegistrationOverrideFeatureTags = updatedTags;
321             mServiceCapRegTracker.updateImsRegistration(updatedTags);
322             return true;
323         }
324         return false;
325     }
326 
327     /**
328      * Combine IMS registration with overrides to produce a new feature tag Set.
329      * @return true if the IMS registration changed, false otherwise.
330      */
updateImsRegistrationFeatureTags(Set<String> featureTags)331     private synchronized Set<String> updateImsRegistrationFeatureTags(Set<String> featureTags) {
332         Set<String> tags = new ArraySet<>(featureTags);
333         tags.addAll(mOverrideAddFeatureTags);
334         tags.removeAll(mOverrideRemoveFeatureTags);
335         return tags;
336     }
337 
338     /**
339      * Update the TTY preferred mode.
340      * @return {@code true} if tty preferred mode is changed, {@code false} otherwise.
341      */
updateTtyPreferredMode(int ttyMode)342     public synchronized boolean updateTtyPreferredMode(int ttyMode) {
343         if (mTtyPreferredMode != ttyMode) {
344             logd("TTY preferred mode changes from " + mTtyPreferredMode + " to " + ttyMode);
345             mTtyPreferredMode = ttyMode;
346             return true;
347         }
348         return false;
349     }
350 
351     /**
352      * Update airplane mode state.
353      * @return {@code true} if the airplane mode is changed, {@code false} otherwise.
354      */
updateAirplaneMode(boolean state)355     public synchronized boolean updateAirplaneMode(boolean state) {
356         if (mAirplaneMode != state) {
357             logd("Airplane mode changes from " + mAirplaneMode + " to " + state);
358             mAirplaneMode = state;
359             return true;
360         }
361         return false;
362     }
363 
364     /**
365      * Update mobile data setting.
366      * @return {@code true} if the mobile data setting is changed, {@code false} otherwise.
367      */
updateMobileData(boolean mobileData)368     public synchronized boolean updateMobileData(boolean mobileData) {
369         if (mMobileData != mobileData) {
370             logd("Mobile data changes from " + mMobileData + " to " + mobileData);
371             mMobileData = mobileData;
372             return true;
373         }
374         return false;
375     }
376 
377     /**
378      * Update VT setting.
379      * @return {@code true} if vt setting is changed, {@code false}.otherwise.
380      */
updateVtSetting(boolean vtSetting)381     public synchronized boolean updateVtSetting(boolean vtSetting) {
382         if (mVtSetting != vtSetting) {
383             logd("VT setting changes from " + mVtSetting + " to " + vtSetting);
384             mVtSetting = vtSetting;
385             return true;
386         }
387         return false;
388     }
389 
390     /**
391      * Update the MMTEL capabilities if the capabilities is changed.
392      * @return {@code true} if the mmtel capabilities are changed, {@code false} otherwise.
393      */
updateMmtelCapabilitiesChanged(MmTelCapabilities capabilities)394     public synchronized boolean updateMmtelCapabilitiesChanged(MmTelCapabilities capabilities) {
395         if (capabilities == null) {
396             return false;
397         }
398         boolean oldVolteAvailable = isVolteAvailable(mMmtelNetworkRegType, mMmTelCapabilities);
399         boolean oldVoWifiAvailable = isVoWifiAvailable(mMmtelNetworkRegType, mMmTelCapabilities);
400         boolean oldVtAvailable = isVtAvailable(mMmtelNetworkRegType, mMmTelCapabilities);
401         boolean oldViWifiAvailable = isViWifiAvailable(mMmtelNetworkRegType, mMmTelCapabilities);
402         boolean oldCallComposerAvailable = isCallComposerAvailable(mMmTelCapabilities);
403 
404         boolean volteAvailable = isVolteAvailable(mMmtelNetworkRegType, capabilities);
405         boolean voWifiAvailable = isVoWifiAvailable(mMmtelNetworkRegType, capabilities);
406         boolean vtAvailable = isVtAvailable(mMmtelNetworkRegType, capabilities);
407         boolean viWifiAvailable = isViWifiAvailable(mMmtelNetworkRegType, capabilities);
408         boolean callComposerAvailable = isCallComposerAvailable(capabilities);
409 
410         logd("updateMmtelCapabilitiesChanged: from " + mMmTelCapabilities + " to " + capabilities);
411 
412         // Update to the new mmtel capabilities
413         mMmTelCapabilities = deepCopyCapabilities(capabilities);
414 
415         if (oldVolteAvailable != volteAvailable
416                 || oldVoWifiAvailable != voWifiAvailable
417                 || oldVtAvailable != vtAvailable
418                 || oldViWifiAvailable != viWifiAvailable
419                 || oldCallComposerAvailable != callComposerAvailable) {
420             return true;
421         }
422         return false;
423     }
424 
updatePresenceCapable(boolean isCapable)425     public synchronized void updatePresenceCapable(boolean isCapable) {
426         mPresenceCapable = isCapable;
427     }
428 
isPresenceCapable()429     public synchronized boolean isPresenceCapable() {
430         return mPresenceCapable;
431     }
432 
isVolteAvailable(int networkRegType, MmTelCapabilities capabilities)433     private boolean isVolteAvailable(int networkRegType, MmTelCapabilities capabilities) {
434         return (networkRegType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
435                 && capabilities.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
436     }
437 
isVoWifiAvailable(int networkRegType, MmTelCapabilities capabilities)438     private boolean isVoWifiAvailable(int networkRegType, MmTelCapabilities capabilities) {
439         return (networkRegType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
440                 && capabilities.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
441     }
442 
isVtAvailable(int networkRegType, MmTelCapabilities capabilities)443     private boolean isVtAvailable(int networkRegType, MmTelCapabilities capabilities) {
444         return (networkRegType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
445                 && capabilities.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO);
446     }
447 
isViWifiAvailable(int networkRegType, MmTelCapabilities capabilities)448     private boolean isViWifiAvailable(int networkRegType, MmTelCapabilities capabilities) {
449         return (networkRegType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
450                 && capabilities.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO);
451     }
452 
isCallComposerAvailable(MmTelCapabilities capabilities)453     private boolean isCallComposerAvailable(MmTelCapabilities capabilities) {
454         return capabilities.isCapable(
455                 MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER);
456     }
457 
458     /**
459      * Get the device's capabilities.
460      */
getDeviceCapabilities( @apabilityMechanism int mechanism, Context context)461     public synchronized RcsContactUceCapability getDeviceCapabilities(
462             @CapabilityMechanism int mechanism, Context context) {
463         switch (mechanism) {
464             case RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE:
465                 return getPresenceCapabilities(context);
466             case RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS:
467                 return getOptionsCapabilities(context);
468             default:
469                 logw("getDeviceCapabilities: invalid mechanism " + mechanism);
470                 return null;
471         }
472     }
473 
474     // Get the device's capabilities with the PRESENCE mechanism.
getPresenceCapabilities(Context context)475     private RcsContactUceCapability getPresenceCapabilities(Context context) {
476         Uri uri = PublishUtils.getDeviceContactUri(context, mSubId, this);
477         if (uri == null) {
478             logw("getPresenceCapabilities: uri is empty");
479             return null;
480         }
481         Set<ServiceDescription> capableFromReg =
482                 mServiceCapRegTracker.copyRegistrationCapabilities();
483 
484         PresenceBuilder presenceBuilder = new PresenceBuilder(uri,
485                 RcsContactUceCapability.SOURCE_TYPE_CACHED,
486                 RcsContactUceCapability.REQUEST_RESULT_FOUND);
487         // RCS presence tag (added to all presence documents)
488         ServiceDescription presDescription = getCustomizedDescription(
489                 ServiceDescription.SERVICE_DESCRIPTION_PRESENCE, capableFromReg);
490         addCapability(presenceBuilder, presDescription.getTupleBuilder(), uri);
491         capableFromReg.remove(presDescription);
492 
493         // mmtel
494         ServiceDescription voiceDescription = getCustomizedDescription(
495                 ServiceDescription.SERVICE_DESCRIPTION_MMTEL_VOICE, capableFromReg);
496         ServiceDescription vtDescription = getCustomizedDescription(
497                 ServiceDescription.SERVICE_DESCRIPTION_MMTEL_VOICE_VIDEO, capableFromReg);
498         ServiceDescription descToUse = (hasVolteCapability() && hasVtCapability()) ?
499                 vtDescription : voiceDescription;
500         ServiceCapabilities servCaps = new ServiceCapabilities.Builder(
501                 hasVolteCapability(), hasVtCapability())
502                 .addSupportedDuplexMode(ServiceCapabilities.DUPLEX_MODE_FULL).build();
503         addCapability(presenceBuilder, descToUse.getTupleBuilder()
504                 .setServiceCapabilities(servCaps), uri);
505         capableFromReg.remove(voiceDescription);
506         capableFromReg.remove(vtDescription);
507 
508         // call composer via mmtel
509         ServiceDescription composerDescription = getCustomizedDescription(
510                 ServiceDescription.SERVICE_DESCRIPTION_CALL_COMPOSER_MMTEL, capableFromReg);
511         if (hasCallComposerCapability()) {
512             addCapability(presenceBuilder, composerDescription.getTupleBuilder(), uri);
513         }
514         capableFromReg.remove(composerDescription);
515 
516         // External features can only be found using registration states from other components.
517         // Count these features as capable and include in PIDF XML if they are registered.
518         for (ServiceDescription capability : capableFromReg) {
519             addCapability(presenceBuilder, capability.getTupleBuilder(), uri);
520         }
521 
522         return presenceBuilder.build();
523     }
524 
525     /**
526      * Search the refSet for the ServiceDescription that matches the service-id && version and
527      * return that or return the reference if there is no match.
528      */
getCustomizedDescription(ServiceDescription reference, Set<ServiceDescription> refSet)529     private ServiceDescription getCustomizedDescription(ServiceDescription reference,
530             Set<ServiceDescription> refSet) {
531         return refSet.stream().filter(s -> s.serviceId.equals(reference.serviceId)
532                 && s.version.equals(reference.version)).findFirst().orElse(reference);
533     }
534 
535     // Get the device's capabilities with the OPTIONS mechanism.
getOptionsCapabilities(Context context)536     private RcsContactUceCapability getOptionsCapabilities(Context context) {
537         Uri uri = PublishUtils.getDeviceContactUri(context, mSubId, this);
538         if (uri == null) {
539             logw("getOptionsCapabilities: uri is empty");
540             return null;
541         }
542 
543         Set<String> capableFromReg = mServiceCapRegTracker.copyRegistrationFeatureTags();
544 
545         OptionsBuilder optionsBuilder = new OptionsBuilder(uri, SOURCE_TYPE_CACHED);
546         optionsBuilder.setRequestResult(RcsContactUceCapability.REQUEST_RESULT_FOUND);
547         FeatureTags.addFeatureTags(optionsBuilder, hasVolteCapability(), hasVtCapability(),
548                 isPresenceCapable(), hasCallComposerCapability(), capableFromReg);
549         return optionsBuilder.build();
550     }
551 
addCapability(RcsContactUceCapability.PresenceBuilder presenceBuilder, RcsContactPresenceTuple.Builder tupleBuilder, Uri contactUri)552     private void addCapability(RcsContactUceCapability.PresenceBuilder presenceBuilder,
553             RcsContactPresenceTuple.Builder tupleBuilder, Uri contactUri) {
554         presenceBuilder.addCapabilityTuple(tupleBuilder.setContactUri(contactUri).build());
555     }
556 
557     // Check if the device has the VoLTE capability
hasVolteCapability()558     private synchronized boolean hasVolteCapability() {
559         return overrideCapability(FeatureTags.FEATURE_TAG_MMTEL, mMmTelCapabilities != null
560                 && mMmTelCapabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VOICE));
561     }
562 
563     // Check if the device has the VT capability
hasVtCapability()564     private synchronized boolean hasVtCapability() {
565         return overrideCapability(FeatureTags.FEATURE_TAG_VIDEO, mMmTelCapabilities != null
566                 && mMmTelCapabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VIDEO));
567     }
568 
569     // Check if the device has the Call Composer capability
hasCallComposerCapability()570     private synchronized boolean hasCallComposerCapability() {
571         return overrideCapability(FeatureTags.FEATURE_TAG_CALL_COMPOSER_VIA_TELEPHONY,
572                 mMmTelCapabilities != null && mMmTelCapabilities.isCapable(
573                         MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER));
574     }
575 
576     /**
577      * @return the overridden value for the provided feature tag or the original capability if there
578      * is no override.
579      */
overrideCapability(String featureTag, boolean originalCap)580     private synchronized boolean overrideCapability(String featureTag, boolean originalCap) {
581         if (mOverrideRemoveFeatureTags.contains(featureTag)) {
582             return false;
583         }
584 
585         if (mOverrideAddFeatureTags.contains(featureTag)) {
586             return true;
587         }
588 
589         return originalCap;
590     }
591 
deepCopyCapabilities(MmTelCapabilities capabilities)592     private synchronized MmTelCapabilities deepCopyCapabilities(MmTelCapabilities capabilities) {
593         MmTelCapabilities mmTelCapabilities = new MmTelCapabilities();
594         if (capabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VOICE)) {
595             mmTelCapabilities.addCapabilities(MmTelCapabilities.CAPABILITY_TYPE_VOICE);
596         }
597         if (capabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_VIDEO)) {
598             mmTelCapabilities.addCapabilities(MmTelCapabilities.CAPABILITY_TYPE_VIDEO);
599         }
600         if (capabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_UT)) {
601             mmTelCapabilities.addCapabilities(MmTelCapabilities.CAPABILITY_TYPE_UT);
602         }
603         if (capabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_SMS)) {
604             mmTelCapabilities.addCapabilities(MmTelCapabilities.CAPABILITY_TYPE_SMS);
605         }
606         if (capabilities.isCapable(MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER)) {
607             mmTelCapabilities.addCapabilities(MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER);
608         }
609         return mmTelCapabilities;
610     }
611 
logd(String log)612     private void logd(String log) {
613         Log.d(LOG_TAG, getLogPrefix().append(log).toString());
614         mLocalLog.log("[D] " + log);
615     }
616 
logi(String log)617     private void logi(String log) {
618         Log.i(LOG_TAG, getLogPrefix().append(log).toString());
619         mLocalLog.log("[I] " + log);
620     }
621 
logw(String log)622     private void logw(String log) {
623         Log.w(LOG_TAG, getLogPrefix().append(log).toString());
624         mLocalLog.log("[W] " + log);
625     }
626 
getLogPrefix()627     private StringBuilder getLogPrefix() {
628         StringBuilder builder = new StringBuilder("[");
629         builder.append(mSubId);
630         builder.append("] ");
631         return builder;
632     }
633 
dump(PrintWriter printWriter)634     public void dump(PrintWriter printWriter) {
635         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
636         pw.println("DeviceCapabilityInfo :");
637         pw.increaseIndent();
638 
639         mServiceCapRegTracker.dump(pw);
640 
641         pw.println("Log:");
642         pw.increaseIndent();
643         mLocalLog.dump(pw);
644         pw.decreaseIndent();
645 
646         pw.decreaseIndent();
647     }
648 }
649