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 import java.util.Arrays; 40 41 import com.android.ims.internal.Logger; 42 import com.android.ims.internal.uce.presence.PresTupleInfo; 43 import com.android.ims.internal.uce.presence.PresRlmiInfo; 44 import com.android.ims.internal.uce.presence.PresResInfo; 45 import com.android.ims.internal.uce.presence.PresResInstanceInfo; 46 import com.android.ims.RcsPresenceInfo; 47 import com.android.ims.RcsPresenceInfo.ServiceType; 48 import com.android.ims.RcsPresenceInfo.ServiceState; 49 import com.android.service.ims.presence.PresenceUtils; 50 51 public class PresenceInfoParser{ 52 /* 53 * The logger 54 */ 55 static private Logger logger = Logger.getLogger("PresenceInfoParser"); 56 PresenceInfoParser()57 public PresenceInfoParser() { 58 } 59 getPresenceInfoFromTuple(String pPresentityURI, PresTupleInfo[] pTupleInfo)60 static public RcsPresenceInfo getPresenceInfoFromTuple(String pPresentityURI, 61 PresTupleInfo[] pTupleInfo){ 62 logger.debug("getPresenceInfoFromTuple: pPresentityURI=" + pPresentityURI + 63 " pTupleInfo=" + Arrays.toString(pTupleInfo)); 64 65 if(pPresentityURI == null){ 66 logger.error("pPresentityURI=" + pPresentityURI); 67 return null; 68 } 69 70 String contactNumber = getPhoneFromUri(pPresentityURI); 71 // Now that we got notification if the content didn't indicate it is not Volte 72 // then it should be Volte enabled. So default to Volte enabled. 73 int volteStatus = RcsPresenceInfo.VolteStatus.VOLTE_ENABLED; 74 75 int ipVoiceCallState = RcsPresenceInfo.ServiceState.UNKNOWN; 76 String ipVoiceCallServiceNumber = null; 77 // We use the timestamp which when we received it instead of the one in PDU 78 long ipVoiceCallTimestamp = System.currentTimeMillis(); 79 80 int ipVideoCallState = RcsPresenceInfo.ServiceState.UNKNOWN; 81 String ipVideoCallServiceNumber = null; 82 long ipVideoCallTimestamp = System.currentTimeMillis(); 83 84 if( pTupleInfo == null){ 85 logger.debug("pTupleInfo=null"); 86 return (new RcsPresenceInfo(contactNumber, volteStatus, 87 ipVoiceCallState, ipVoiceCallServiceNumber, ipVoiceCallTimestamp, 88 ipVideoCallState, ipVideoCallServiceNumber, ipVideoCallTimestamp)); 89 } 90 91 for(int i = 0; i < pTupleInfo.length; i++){ 92 // Video call has high priority. If it supports video call it will support voice call. 93 String featureTag = pTupleInfo[i].getFeatureTag(); 94 logger.debug("getFeatureTag " + i + ": " + featureTag); 95 if(featureTag.equals( 96 "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\";video") || 97 featureTag.equals("+g.gsma.rcs.telephony=\"cs,volte\";video")) { 98 logger.debug("Got Volte and VT enabled tuple"); 99 ipVoiceCallState = RcsPresenceInfo.ServiceState.ONLINE; 100 ipVideoCallState = RcsPresenceInfo.ServiceState.ONLINE; 101 102 if(pTupleInfo[i].getContactUri() != null){ 103 ipVideoCallServiceNumber = getPhoneFromUri( 104 pTupleInfo[i].getContactUri().toString()); 105 } 106 } else if(featureTag.equals("+g.gsma.rcs.telephony=\"cs\";video")) { 107 logger.debug("Got Volte disabled but VT enabled tuple"); 108 ipVoiceCallState = RcsPresenceInfo.ServiceState.OFFLINE; 109 ipVideoCallState = RcsPresenceInfo.ServiceState.ONLINE; 110 111 if(pTupleInfo[i].getContactUri() != null){ 112 ipVideoCallServiceNumber = getPhoneFromUri( 113 pTupleInfo[i].getContactUri().toString()); 114 } 115 }else{ 116 if(featureTag.equals( 117 "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\"") || 118 featureTag.equals("+g.gsma.rcs.telephony=\"cs,volte\"")) { 119 120 logger.debug("Got Volte only tuple"); 121 ipVoiceCallState = RcsPresenceInfo.ServiceState.ONLINE; 122 // "OR" for multiple tuples. 123 if(RcsPresenceInfo.ServiceState.UNKNOWN == ipVideoCallState) { 124 ipVideoCallState = RcsPresenceInfo.ServiceState.OFFLINE; 125 } 126 127 if(pTupleInfo[i].getContactUri() != null){ 128 ipVoiceCallServiceNumber = getPhoneFromUri( 129 pTupleInfo[i].getContactUri().toString()); 130 } 131 }else{ 132 logger.debug("Ignoring feature tag: " + pTupleInfo[i].getFeatureTag()); 133 } 134 } 135 } 136 137 RcsPresenceInfo retPresenceInfo = new RcsPresenceInfo(contactNumber,volteStatus, 138 ipVoiceCallState, ipVoiceCallServiceNumber, ipVoiceCallTimestamp, 139 ipVideoCallState, ipVideoCallServiceNumber, ipVideoCallTimestamp); 140 141 logger.debug("getPresenceInfoFromTuple: " + retPresenceInfo); 142 143 return retPresenceInfo; 144 } 145 addPresenceInfo(ArrayList<RcsPresenceInfo> presenceInfoList, RcsPresenceInfo presenceInfo)146 static private void addPresenceInfo(ArrayList<RcsPresenceInfo> presenceInfoList, 147 RcsPresenceInfo presenceInfo){ 148 logger.debug("addPresenceInfo presenceInfoList=" + presenceInfoList + 149 " presenceInfo=" + presenceInfo); 150 151 if(presenceInfoList == null || presenceInfo == null){ 152 logger.debug("addPresenceInfo presenceInfoList=" + presenceInfoList + 153 " presenceInfo=" + presenceInfo); 154 return; 155 } 156 157 for(int i=0; i< presenceInfoList.size(); i++){ 158 RcsPresenceInfo presenceInfoTmp = presenceInfoList.get(i); 159 if((presenceInfoTmp != null) && (presenceInfoTmp.getContactNumber() != null) && 160 presenceInfoTmp.getContactNumber().equalsIgnoreCase( 161 presenceInfo.getContactNumber())){ 162 // merge the information 163 presenceInfoList.set(i, new RcsPresenceInfo(presenceInfoTmp.getContactNumber(), 164 presenceInfoTmp.getVolteStatus(), 165 ((ServiceState.ONLINE == presenceInfo.getServiceState( 166 ServiceType.VOLTE_CALL)) || 167 (ServiceState.ONLINE == presenceInfoTmp.getServiceState( 168 ServiceType.VOLTE_CALL)))?ServiceState.ONLINE: 169 presenceInfoTmp.getServiceState(ServiceType.VOLTE_CALL), 170 presenceInfoTmp.getServiceContact(ServiceType.VOLTE_CALL), 171 presenceInfoTmp.getTimeStamp(ServiceType.VOLTE_CALL), 172 ((ServiceState.ONLINE == presenceInfo.getServiceState( 173 ServiceType.VT_CALL)) || 174 (ServiceState.ONLINE == presenceInfoTmp.getServiceState( 175 ServiceType.VT_CALL)))?ServiceState.ONLINE: 176 presenceInfoTmp.getServiceState(ServiceType.VT_CALL), 177 presenceInfoTmp.getServiceContact(ServiceType.VT_CALL), 178 presenceInfoTmp.getTimeStamp(ServiceType.VT_CALL))); 179 return; 180 } 181 } 182 183 // didn't merge, so add the new one. 184 presenceInfoList.add(presenceInfo); 185 } 186 getPresenceInfosFromPresenceRes( PresRlmiInfo pRlmiInfo, PresResInfo[] pRcsPresenceInfo)187 static public RcsPresenceInfo[] getPresenceInfosFromPresenceRes( 188 PresRlmiInfo pRlmiInfo, PresResInfo[] pRcsPresenceInfo) { 189 if(pRcsPresenceInfo == null){ 190 logger.debug("getPresenceInfosFromPresenceRes pRcsPresenceInfo=null"); 191 return null; 192 } 193 194 ArrayList<RcsPresenceInfo> presenceInfoList = new ArrayList<RcsPresenceInfo>(); 195 for(int i=0; i < pRcsPresenceInfo.length; i++ ) { 196 if(pRcsPresenceInfo[i].getInstanceInfo() == null){ 197 logger.error("invalid data getInstanceInfo = null"); 198 continue; 199 } 200 201 String resUri = pRcsPresenceInfo[i].getResUri(); 202 if(resUri == null){ 203 logger.error("invalid data getResUri = null"); 204 continue; 205 } 206 207 String contactNumber = getPhoneFromUri(resUri); 208 if(contactNumber == null){ 209 logger.error("invalid data contactNumber=null"); 210 continue; 211 } 212 213 if(pRcsPresenceInfo[i].getInstanceInfo().getResInstanceState() == 214 PresResInstanceInfo.UCE_PRES_RES_INSTANCE_STATE_TERMINATED){ 215 logger.debug("instatance state is terminated"); 216 String reason = pRcsPresenceInfo[i].getInstanceInfo().getReason(); 217 if(reason != null){ 218 reason = reason.toLowerCase(); 219 // Noresource: 404. Device shall consider the resource as non-EAB capable 220 // Rejected: 403. Device shall consider the resource as non-EAB capable 221 // Deactivated: 408, 481, 603, other 4xx, 5xx 6xx. Ignore. 222 // Give Up: 480. Ignore. 223 // Probation: 503. Ignore. 224 if(reason.equals("rejected") || reason.equals("noresource")){ 225 RcsPresenceInfo presenceInfo = new RcsPresenceInfo(contactNumber, 226 RcsPresenceInfo.VolteStatus.VOLTE_DISABLED, 227 RcsPresenceInfo.ServiceState.OFFLINE, null, 228 System.currentTimeMillis(), 229 RcsPresenceInfo.ServiceState.OFFLINE, null, 230 System.currentTimeMillis()); 231 addPresenceInfo(presenceInfoList, presenceInfo); 232 logger.debug("reason=" + reason + " presenceInfo=" + presenceInfo); 233 continue; 234 } 235 } 236 } 237 238 RcsPresenceInfo presenceInfo = getPresenceInfoFromTuple(resUri, 239 pRcsPresenceInfo[i].getInstanceInfo().getTupleInfo()); 240 if(presenceInfo != null){ 241 addPresenceInfo(presenceInfoList, presenceInfo); 242 }else{ 243 logger.debug("presenceInfo["+ i + "] = null"); 244 addPresenceInfo(presenceInfoList, getPresenceInfoFromTuple(resUri, null)); 245 } 246 } 247 248 logger.debug("getPresenceInfoFromPresenceRes, presenceInfos:" + presenceInfoList); 249 if(presenceInfoList.size() == 0){ 250 return null; 251 } 252 253 RcsPresenceInfo[] theArray = new RcsPresenceInfo[presenceInfoList.size()]; 254 return (RcsPresenceInfo[])presenceInfoList.toArray(theArray); 255 } 256 getPhoneFromUri(String uriValue)257 static public String getPhoneFromUri(String uriValue) { 258 if(uriValue == null){ 259 return null; 260 } 261 262 int startIndex = uriValue.indexOf(":", 0); 263 int endIndex = uriValue.indexOf("@", startIndex); 264 265 logger.debug("getPhoneFromUri uriValue=" + uriValue + 266 " startIndex=" + startIndex + " endIndex=" + endIndex); 267 268 String number = uriValue; 269 if(endIndex != -1){ 270 number = uriValue.substring(0, endIndex); 271 } 272 273 if (startIndex != -1) { 274 return number = number.substring(startIndex + 1); 275 } 276 277 return number; 278 } 279 getUceCapability(RcsPresenceInfo info)280 public static RcsContactUceCapability getUceCapability(RcsPresenceInfo info) { 281 boolean volteCapable = false; 282 if (ServiceState.ONLINE == info.getServiceState(ServiceType.VOLTE_CALL)) { 283 volteCapable = true; 284 } 285 286 boolean vtCapable = false; 287 if (ServiceState.ONLINE == info.getServiceState(ServiceType.VT_CALL)) { 288 vtCapable = true; 289 } 290 291 ServiceCapabilities.Builder servCapsBuilder = new ServiceCapabilities.Builder( 292 volteCapable, vtCapable); 293 servCapsBuilder.addSupportedDuplexMode(ServiceCapabilities.DUPLEX_MODE_FULL); 294 295 Uri contactUri = PresenceUtils.convertContactNumber(info.getContactNumber()); 296 297 RcsContactPresenceTuple.Builder tupleBuilder = new RcsContactPresenceTuple.Builder( 298 RcsContactPresenceTuple.TUPLE_BASIC_STATUS_OPEN, 299 RcsContactPresenceTuple.SERVICE_ID_MMTEL, "1.0"); 300 tupleBuilder.setContactUri(contactUri).setServiceCapabilities(servCapsBuilder.build()); 301 302 PresenceBuilder presenceBuilder = new PresenceBuilder(contactUri, 303 RcsContactUceCapability.SOURCE_TYPE_CACHED, 304 RcsContactUceCapability.REQUEST_RESULT_FOUND); 305 presenceBuilder.addCapabilityTuple(tupleBuilder.build()); 306 307 return presenceBuilder.build(); 308 } 309 getRcsPresenceInfo(RcsContactUceCapability capability)310 public static RcsPresenceInfo getRcsPresenceInfo(RcsContactUceCapability capability) { 311 int volteCapable = ServiceState.OFFLINE; 312 int vtCapable = ServiceState.OFFLINE; 313 RcsContactPresenceTuple presenceTuple = capability.getCapabilityTuple( 314 RcsContactPresenceTuple.SERVICE_ID_MMTEL); 315 if (presenceTuple != null) { 316 ServiceCapabilities serviceCaps = presenceTuple.getServiceCapabilities(); 317 if (serviceCaps != null && serviceCaps.isAudioCapable()) { 318 volteCapable = ServiceState.ONLINE; 319 } 320 if (serviceCaps != null && serviceCaps.isVideoCapable()) { 321 vtCapable = ServiceState.ONLINE; 322 } 323 } 324 return new RcsPresenceInfo(capability.getContactUri().getSchemeSpecificPart(), 325 // Not sure what the difference is, just track voice capable. 326 (volteCapable == ServiceState.ONLINE) ? RcsPresenceInfo.VolteStatus.VOLTE_ENABLED : 327 RcsPresenceInfo.VolteStatus.VOLTE_DISABLED, volteCapable, 328 PresenceUtils.getNumber(capability.getContactUri()), 329 // We always use system current time instead of time from server 330 System.currentTimeMillis(), vtCapable, 331 PresenceUtils.getNumber(capability.getContactUri()), 332 // We always use system current time instead of time from server 333 System.currentTimeMillis()); 334 } 335 } 336