1 /*
2  * Copyright (c) 2015, Motorola Mobility LLC
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *     - Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     - Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     - Neither the name of Motorola Mobility nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
18  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MOTOROLA MOBILITY LLC BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
26  * DAMAGE.
27  */
28 
29 package com.android.service.ims;
30 
31 import android.net.Uri;
32 import android.telephony.ims.RcsContactPresenceTuple;
33 import android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities;
34 import android.telephony.ims.RcsContactUceCapability;
35 import android.telephony.ims.RcsContactUceCapability.PresenceBuilder;
36 
37 import java.lang.String;
38 import java.util.ArrayList;
39 
40 import com.android.ims.internal.Logger;
41 import com.android.ims.internal.uce.presence.PresTupleInfo;
42 import com.android.ims.internal.uce.presence.PresRlmiInfo;
43 import com.android.ims.internal.uce.presence.PresResInfo;
44 import com.android.ims.internal.uce.presence.PresResInstanceInfo;
45 import com.android.ims.RcsPresenceInfo;
46 import com.android.ims.RcsPresenceInfo.ServiceType;
47 import com.android.ims.RcsPresenceInfo.ServiceState;
48 import com.android.service.ims.presence.PresenceUtils;
49 
50 public class PresenceInfoParser{
51     /*
52      * The logger
53      */
54     static private Logger logger = Logger.getLogger("PresenceInfoParser");
55 
PresenceInfoParser()56     public PresenceInfoParser() {
57     }
58 
getPresenceInfoFromTuple(String pPresentityURI, PresTupleInfo[] pTupleInfo)59     static public RcsPresenceInfo getPresenceInfoFromTuple(String pPresentityURI,
60             PresTupleInfo[] pTupleInfo){
61         logger.debug("getPresenceInfoFromTuple: pPresentityURI=" + pPresentityURI +
62                 " pTupleInfo=" + pTupleInfo);
63 
64         if(pPresentityURI == null){
65             logger.error("pPresentityURI=" + pPresentityURI);
66             return null;
67         }
68 
69         String contactNumber = getPhoneFromUri(pPresentityURI);
70         // Now that we got notification if the content didn't indicate it is not Volte
71         // then it should be Volte enabled. So default to Volte enabled.
72         int volteStatus = RcsPresenceInfo.VolteStatus.VOLTE_ENABLED;
73 
74         int ipVoiceCallState = RcsPresenceInfo.ServiceState.UNKNOWN;
75         String ipVoiceCallServiceNumber = null;
76          // We use the timestamp which when we received it instead of the one in PDU
77         long ipVoiceCallTimestamp = System.currentTimeMillis();
78 
79         int ipVideoCallState = RcsPresenceInfo.ServiceState.UNKNOWN;
80         String ipVideoCallServiceNumber = null;
81         long ipVideoCallTimestamp = System.currentTimeMillis();
82 
83         if( pTupleInfo == null){
84             logger.debug("pTupleInfo=null");
85             return (new RcsPresenceInfo(contactNumber, volteStatus,
86                 ipVoiceCallState, ipVoiceCallServiceNumber, ipVoiceCallTimestamp,
87                 ipVideoCallState, ipVideoCallServiceNumber, ipVideoCallTimestamp));
88         }
89 
90         for(int i = 0; i < pTupleInfo.length; i++){
91             // Video call has high priority. If it supports video call it will support voice call.
92             String featureTag = pTupleInfo[i].getFeatureTag();
93             logger.debug("getFeatureTag " + i + ": " + featureTag);
94             if(featureTag.equals(
95                 "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\";video") ||
96                 featureTag.equals("+g.gsma.rcs.telephony=\"cs,volte\";video")) {
97                 logger.debug("Got Volte and VT enabled tuple");
98                 ipVoiceCallState = RcsPresenceInfo.ServiceState.ONLINE;
99                 ipVideoCallState = RcsPresenceInfo.ServiceState.ONLINE;
100 
101                 if(pTupleInfo[i].getContactUri() != null){
102                     ipVideoCallServiceNumber = getPhoneFromUri(
103                             pTupleInfo[i].getContactUri().toString());
104                 }
105             } else if(featureTag.equals("+g.gsma.rcs.telephony=\"cs\";video")) {
106                 logger.debug("Got Volte disabled but VT enabled tuple");
107                 ipVoiceCallState = RcsPresenceInfo.ServiceState.OFFLINE;
108                 ipVideoCallState = RcsPresenceInfo.ServiceState.ONLINE;
109 
110                 if(pTupleInfo[i].getContactUri() != null){
111                     ipVideoCallServiceNumber = getPhoneFromUri(
112                             pTupleInfo[i].getContactUri().toString());
113                 }
114             }else{
115                 if(featureTag.equals(
116                     "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\"") ||
117                     featureTag.equals("+g.gsma.rcs.telephony=\"cs,volte\"")) {
118 
119                     logger.debug("Got Volte only tuple");
120                     ipVoiceCallState = RcsPresenceInfo.ServiceState.ONLINE;
121                     // "OR" for multiple tuples.
122                     if(RcsPresenceInfo.ServiceState.UNKNOWN == ipVideoCallState) {
123                         ipVideoCallState = RcsPresenceInfo.ServiceState.OFFLINE;
124                     }
125 
126                     if(pTupleInfo[i].getContactUri() != null){
127                         ipVoiceCallServiceNumber = getPhoneFromUri(
128                                 pTupleInfo[i].getContactUri().toString());
129                     }
130                 }else{
131                     logger.debug("Ignoring feature tag: " + pTupleInfo[i].getFeatureTag());
132                 }
133            }
134         }
135 
136         RcsPresenceInfo retPresenceInfo = new RcsPresenceInfo(contactNumber,volteStatus,
137                 ipVoiceCallState, ipVoiceCallServiceNumber, ipVoiceCallTimestamp,
138                 ipVideoCallState, ipVideoCallServiceNumber, ipVideoCallTimestamp);
139 
140         logger.debug("getPresenceInfoFromTuple: " + retPresenceInfo);
141 
142         return retPresenceInfo;
143     }
144 
addPresenceInfo(ArrayList<RcsPresenceInfo> presenceInfoList, RcsPresenceInfo presenceInfo)145     static private void addPresenceInfo(ArrayList<RcsPresenceInfo> presenceInfoList,
146             RcsPresenceInfo presenceInfo){
147         logger.debug("addPresenceInfo presenceInfoList=" + presenceInfoList +
148                     " presenceInfo=" + presenceInfo);
149 
150         if(presenceInfoList == null || presenceInfo == null){
151             logger.debug("addPresenceInfo presenceInfoList=" + presenceInfoList +
152                     " presenceInfo=" + presenceInfo);
153             return;
154         }
155 
156         for(int i=0; i< presenceInfoList.size(); i++){
157             RcsPresenceInfo presenceInfoTmp = presenceInfoList.get(i);
158             if((presenceInfoTmp != null) && (presenceInfoTmp.getContactNumber() != null) &&
159                     presenceInfoTmp.getContactNumber().equalsIgnoreCase(
160                     presenceInfo.getContactNumber())){
161                 // merge the information
162                 presenceInfoList.set(i, new RcsPresenceInfo(presenceInfoTmp.getContactNumber(),
163                         presenceInfoTmp.getVolteStatus(),
164                         ((ServiceState.ONLINE == presenceInfo.getServiceState(
165                                 ServiceType.VOLTE_CALL)) ||
166                                 (ServiceState.ONLINE == presenceInfoTmp.getServiceState(
167                                 ServiceType.VOLTE_CALL)))?ServiceState.ONLINE:
168                                     presenceInfoTmp.getServiceState(ServiceType.VOLTE_CALL),
169                         presenceInfoTmp.getServiceContact(ServiceType.VOLTE_CALL),
170                         presenceInfoTmp.getTimeStamp(ServiceType.VOLTE_CALL),
171                         ((ServiceState.ONLINE == presenceInfo.getServiceState(
172                                 ServiceType.VT_CALL)) ||
173                                 (ServiceState.ONLINE == presenceInfoTmp.getServiceState(
174                                 ServiceType.VT_CALL)))?ServiceState.ONLINE:
175                                     presenceInfoTmp.getServiceState(ServiceType.VT_CALL),
176                         presenceInfoTmp.getServiceContact(ServiceType.VT_CALL),
177                         presenceInfoTmp.getTimeStamp(ServiceType.VT_CALL)));
178                 return;
179             }
180         }
181 
182         // didn't merge, so add the new one.
183         presenceInfoList.add(presenceInfo);
184     }
185 
getPresenceInfosFromPresenceRes( PresRlmiInfo pRlmiInfo, PresResInfo[] pRcsPresenceInfo)186     static public RcsPresenceInfo[] getPresenceInfosFromPresenceRes(
187             PresRlmiInfo pRlmiInfo, PresResInfo[] pRcsPresenceInfo) {
188         if(pRcsPresenceInfo == null){
189             logger.debug("getPresenceInfosFromPresenceRes pRcsPresenceInfo=null");
190             return null;
191         }
192 
193         ArrayList<RcsPresenceInfo> presenceInfoList = new ArrayList<RcsPresenceInfo>();
194         for(int i=0; i < pRcsPresenceInfo.length; i++ ) {
195             if(pRcsPresenceInfo[i].getInstanceInfo() == null){
196                 logger.error("invalid data getInstanceInfo = null");
197                 continue;
198             }
199 
200             String resUri = pRcsPresenceInfo[i].getResUri();
201             if(resUri == null){
202                 logger.error("invalid data getResUri = null");
203                 continue;
204             }
205 
206             String contactNumber = getPhoneFromUri(resUri);
207             if(contactNumber == null){
208                 logger.error("invalid data contactNumber=null");
209                 continue;
210             }
211 
212             if(pRcsPresenceInfo[i].getInstanceInfo().getResInstanceState() ==
213                     PresResInstanceInfo.UCE_PRES_RES_INSTANCE_STATE_TERMINATED){
214                 logger.debug("instatance state is terminated");
215                 String reason = pRcsPresenceInfo[i].getInstanceInfo().getReason();
216                 if(reason != null){
217                     reason = reason.toLowerCase();
218                     // Noresource: 404. Device shall consider the resource as non-EAB capable
219                     // Rejected: 403. Device shall consider the resource as non-EAB  capable
220                     // Deactivated: 408, 481, 603, other 4xx, 5xx 6xx. Ignore.
221                     // Give Up: 480. Ignore.
222                     // Probation: 503. Ignore.
223                     if(reason.equals("rejected") || reason.equals("noresource")){
224                         RcsPresenceInfo presenceInfo = new RcsPresenceInfo(contactNumber,
225                                 RcsPresenceInfo.VolteStatus.VOLTE_DISABLED,
226                                 RcsPresenceInfo.ServiceState.OFFLINE, null,
227                                 System.currentTimeMillis(),
228                                 RcsPresenceInfo.ServiceState.OFFLINE, null,
229                                 System.currentTimeMillis());
230                         addPresenceInfo(presenceInfoList, presenceInfo);
231                         logger.debug("reason=" + reason + " presenceInfo=" + presenceInfo);
232                         continue;
233                     }
234                 }
235             }
236 
237             RcsPresenceInfo presenceInfo = getPresenceInfoFromTuple(resUri,
238                     pRcsPresenceInfo[i].getInstanceInfo().getTupleInfo());
239             if(presenceInfo != null){
240                 addPresenceInfo(presenceInfoList, presenceInfo);
241             }else{
242                 logger.debug("presenceInfo["+ i + "] = null");
243                 addPresenceInfo(presenceInfoList, getPresenceInfoFromTuple(resUri, null));
244             }
245         }
246 
247         logger.debug("getPresenceInfoFromPresenceRes, presenceInfos:" + presenceInfoList);
248         if(presenceInfoList.size() == 0){
249             return null;
250         }
251 
252         RcsPresenceInfo[] theArray = new RcsPresenceInfo[presenceInfoList.size()];
253         return (RcsPresenceInfo[])presenceInfoList.toArray(theArray);
254     }
255 
getPhoneFromUri(String uriValue)256     static public String getPhoneFromUri(String uriValue) {
257         if(uriValue == null){
258             return null;
259         }
260 
261         int startIndex = uriValue.indexOf(":", 0);
262         int endIndex = uriValue.indexOf("@", startIndex);
263 
264         logger.debug("getPhoneFromUri uriValue=" + uriValue +
265             " startIndex=" + startIndex + " endIndex=" + endIndex);
266 
267         String number = uriValue;
268         if(endIndex != -1){
269             number = uriValue.substring(0, endIndex);
270         }
271 
272         if (startIndex != -1) {
273             return number = number.substring(startIndex + 1);
274         }
275 
276         return number;
277     }
278 
getUceCapability(RcsPresenceInfo info)279     public static RcsContactUceCapability getUceCapability(RcsPresenceInfo info) {
280         boolean volteCapable = false;
281         if (ServiceState.ONLINE == info.getServiceState(ServiceType.VOLTE_CALL)) {
282             volteCapable = true;
283         }
284 
285         boolean vtCapable = false;
286         if (ServiceState.ONLINE == info.getServiceState(ServiceType.VT_CALL)) {
287             vtCapable = true;
288         }
289 
290         ServiceCapabilities.Builder servCapsBuilder = new ServiceCapabilities.Builder(
291             volteCapable, vtCapable);
292         servCapsBuilder.addSupportedDuplexMode(ServiceCapabilities.DUPLEX_MODE_FULL);
293 
294         Uri contactUri = PresenceUtils.convertContactNumber(info.getContactNumber());
295 
296         RcsContactPresenceTuple.Builder tupleBuilder = new RcsContactPresenceTuple.Builder(
297                 RcsContactPresenceTuple.TUPLE_BASIC_STATUS_OPEN,
298                 RcsContactPresenceTuple.SERVICE_ID_MMTEL, "1.0");
299         tupleBuilder.setContactUri(contactUri).setServiceCapabilities(servCapsBuilder.build());
300 
301         PresenceBuilder presenceBuilder = new PresenceBuilder(contactUri,
302                 RcsContactUceCapability.SOURCE_TYPE_CACHED,
303                 RcsContactUceCapability.REQUEST_RESULT_FOUND);
304         presenceBuilder.addCapabilityTuple(tupleBuilder.build());
305 
306         return presenceBuilder.build();
307     }
308 
getRcsPresenceInfo(RcsContactUceCapability capability)309     public static RcsPresenceInfo getRcsPresenceInfo(RcsContactUceCapability capability) {
310         int volteCapable = ServiceState.OFFLINE;
311         int vtCapable = ServiceState.OFFLINE;
312         RcsContactPresenceTuple presenceTuple = capability.getCapabilityTuple(
313                 RcsContactPresenceTuple.SERVICE_ID_MMTEL);
314         if (presenceTuple != null) {
315             ServiceCapabilities serviceCaps = presenceTuple.getServiceCapabilities();
316             if (serviceCaps != null && serviceCaps.isAudioCapable()) {
317                 volteCapable = ServiceState.ONLINE;
318             }
319             if (serviceCaps != null && serviceCaps.isVideoCapable()) {
320                 vtCapable = ServiceState.ONLINE;
321             }
322         }
323         return new RcsPresenceInfo(capability.getContactUri().getSchemeSpecificPart(),
324                 // Not sure what the difference is, just track voice capable.
325                 (volteCapable == ServiceState.ONLINE) ? RcsPresenceInfo.VolteStatus.VOLTE_ENABLED :
326                         RcsPresenceInfo.VolteStatus.VOLTE_DISABLED, volteCapable,
327                 PresenceUtils.getNumber(capability.getContactUri()),
328                 // We always use system current time instead of time from server
329                 System.currentTimeMillis(), vtCapable,
330                 PresenceUtils.getNumber(capability.getContactUri()),
331                 // We always use system current time instead of time from server
332                 System.currentTimeMillis());
333     }
334 }
335