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