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