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.pidfparser; 18 19 import android.annotation.Nullable; 20 import android.net.Uri; 21 import android.telephony.ims.RcsContactPresenceTuple; 22 import android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities; 23 import android.telephony.ims.RcsContactUceCapability; 24 import android.text.TextUtils; 25 import android.util.Log; 26 27 28 import com.android.ims.rcs.uce.presence.pidfparser.capabilities.Audio; 29 import com.android.ims.rcs.uce.presence.pidfparser.capabilities.CapsConstant; 30 import com.android.ims.rcs.uce.presence.pidfparser.capabilities.Duplex; 31 import com.android.ims.rcs.uce.presence.pidfparser.capabilities.ServiceCaps; 32 import com.android.ims.rcs.uce.presence.pidfparser.capabilities.Video; 33 import com.android.ims.rcs.uce.presence.pidfparser.omapres.OmaPresConstant; 34 import com.android.ims.rcs.uce.presence.pidfparser.pidf.Basic; 35 import com.android.ims.rcs.uce.presence.pidfparser.pidf.PidfConstant; 36 import com.android.ims.rcs.uce.presence.pidfparser.pidf.Presence; 37 import com.android.ims.rcs.uce.presence.pidfparser.pidf.Tuple; 38 import com.android.ims.rcs.uce.presence.pidfparser.RcsContactUceCapabilityWrapper; 39 import com.android.ims.rcs.uce.util.UceUtils; 40 import com.android.internal.annotations.VisibleForTesting; 41 42 import java.io.IOException; 43 import java.io.Reader; 44 import java.io.StringReader; 45 import java.io.StringWriter; 46 import java.time.Instant; 47 import java.util.List; 48 import java.util.regex.Matcher; 49 import java.util.regex.Pattern; 50 51 import org.xmlpull.v1.XmlPullParser; 52 import org.xmlpull.v1.XmlPullParserException; 53 import org.xmlpull.v1.XmlPullParserFactory; 54 import org.xmlpull.v1.XmlSerializer; 55 56 /** 57 * Convert between the class RcsContactUceCapability and the pidf format. 58 */ 59 public class PidfParser { 60 61 private static final String LOG_TAG = UceUtils.getLogPrefix() + "PidfParser"; 62 63 private static final Pattern PIDF_PATTERN = Pattern.compile("\t|\r|\n"); 64 65 /** 66 * Testing interface used to get the timestamp. 67 */ 68 @VisibleForTesting 69 public interface TimestampProxy { getTimestamp()70 Instant getTimestamp(); 71 } 72 73 // The timestamp proxy to create the local timestamp. 74 private static final TimestampProxy sLocalTimestampProxy = () -> Instant.now(); 75 76 // Override timestamp proxy for testing only. 77 private static TimestampProxy sOverrideTimestampProxy; 78 79 @VisibleForTesting setTimestampProxy(TimestampProxy proxy)80 public static void setTimestampProxy(TimestampProxy proxy) { 81 sOverrideTimestampProxy = proxy; 82 } 83 getTimestampProxy()84 private static TimestampProxy getTimestampProxy() { 85 return (sOverrideTimestampProxy != null) ? sOverrideTimestampProxy : sLocalTimestampProxy; 86 } 87 88 /** 89 * Convert the RcsContactUceCapability to the string of pidf. 90 */ convertToPidf(RcsContactUceCapability capabilities)91 public static String convertToPidf(RcsContactUceCapability capabilities) { 92 StringWriter pidfWriter = new StringWriter(); 93 try { 94 // Init the instance of the XmlSerializer. 95 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 96 XmlSerializer serializer = factory.newSerializer(); 97 98 // setup output and namespace 99 serializer.setOutput(pidfWriter); 100 serializer.setPrefix("", PidfConstant.NAMESPACE); 101 serializer.setPrefix("op", OmaPresConstant.NAMESPACE); 102 serializer.setPrefix("caps", CapsConstant.NAMESPACE); 103 104 // Get the Presence element 105 Presence presence = PidfParserUtils.getPresence(capabilities); 106 107 // Start serializing. 108 serializer.startDocument(PidfParserConstant.ENCODING_UTF_8, true); 109 presence.serialize(serializer); 110 serializer.endDocument(); 111 serializer.flush(); 112 113 } catch (XmlPullParserException parserEx) { 114 parserEx.printStackTrace(); 115 return null; 116 } catch (IOException ioException) { 117 ioException.printStackTrace(); 118 return null; 119 } 120 return pidfWriter.toString(); 121 } 122 123 /** 124 * Get the RcsContactUceCapabilityWrapper from the given PIDF xml format. 125 */ getRcsContactUceCapabilityWrapper( String pidf)126 public static @Nullable RcsContactUceCapabilityWrapper getRcsContactUceCapabilityWrapper( 127 String pidf) { 128 if (TextUtils.isEmpty(pidf)) { 129 Log.w(LOG_TAG, "getRcsContactUceCapabilityWrapper: The given pidf is empty"); 130 return null; 131 } 132 133 // Filter the newline characters 134 Matcher matcher = PIDF_PATTERN.matcher(pidf); 135 String formattedPidf = matcher.replaceAll(""); 136 if (TextUtils.isEmpty(formattedPidf)) { 137 Log.w(LOG_TAG, "getRcsContactUceCapabilityWrapper: The formatted pidf is empty"); 138 return null; 139 } 140 141 Reader reader = null; 142 try { 143 // Init the instance of the parser 144 XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); 145 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); 146 reader = new StringReader(formattedPidf); 147 parser.setInput(reader); 148 149 // Start parsing 150 Presence presence = parsePidf(parser); 151 152 // Convert from the Presence to the RcsContactUceCapabilityWrapper 153 return convertToRcsContactUceCapability(presence); 154 155 } catch (XmlPullParserException | IOException e) { 156 e.printStackTrace(); 157 } finally { 158 if (reader != null) { 159 try { 160 reader.close(); 161 } catch (IOException e) { 162 e.printStackTrace(); 163 } 164 } 165 } 166 return null; 167 } 168 parsePidf(XmlPullParser parser)169 private static Presence parsePidf(XmlPullParser parser) throws IOException, 170 XmlPullParserException { 171 Presence presence = null; 172 int nextType = parser.next(); 173 boolean findPresenceTag = false; 174 do { 175 // Find the Presence start tag 176 if (nextType == XmlPullParser.START_TAG 177 && Presence.ELEMENT_NAME.equals(parser.getName())) { 178 findPresenceTag = true; 179 presence = new Presence(); 180 presence.parse(parser); 181 break; 182 } 183 nextType = parser.next(); 184 } while(nextType != XmlPullParser.END_DOCUMENT); 185 186 if (!findPresenceTag) { 187 Log.w(LOG_TAG, "parsePidf: The presence start tag not found."); 188 } 189 190 return presence; 191 } 192 193 /* 194 * Convert the given Presence to the RcsContactUceCapabilityWrapper 195 */ convertToRcsContactUceCapability( Presence presence)196 private static RcsContactUceCapabilityWrapper convertToRcsContactUceCapability( 197 Presence presence) { 198 if (presence == null) { 199 Log.w(LOG_TAG, "convertToRcsContactUceCapability: The presence is null"); 200 return null; 201 } 202 if (TextUtils.isEmpty(presence.getEntity())) { 203 Log.w(LOG_TAG, "convertToRcsContactUceCapability: The entity is empty"); 204 return null; 205 } 206 207 RcsContactUceCapabilityWrapper uceCapabilityWrapper = new RcsContactUceCapabilityWrapper( 208 Uri.parse(presence.getEntity()), RcsContactUceCapability.SOURCE_TYPE_NETWORK, 209 RcsContactUceCapability.REQUEST_RESULT_FOUND); 210 211 // Add all the capability tuples of this contact 212 presence.getTupleList().forEach(tuple -> { 213 // The tuple that fails parsing is invalid data, so discard it. 214 if (!tuple.getMalformed()) { 215 RcsContactPresenceTuple capabilityTuple = getRcsContactPresenceTuple(tuple); 216 if (capabilityTuple != null) { 217 uceCapabilityWrapper.addCapabilityTuple(capabilityTuple); 218 } 219 } else { 220 uceCapabilityWrapper.setMalformedContents(); 221 } 222 }); 223 uceCapabilityWrapper.setEntityUri(Uri.parse(presence.getEntity())); 224 return uceCapabilityWrapper; 225 } 226 227 /* 228 * Get the RcsContactPresenceTuple from the giving tuple element. 229 */ getRcsContactPresenceTuple(Tuple tuple)230 private static RcsContactPresenceTuple getRcsContactPresenceTuple(Tuple tuple) { 231 if (tuple == null) { 232 return null; 233 } 234 235 String status = RcsContactPresenceTuple.TUPLE_BASIC_STATUS_CLOSED; 236 if (Basic.OPEN.equals(PidfParserUtils.getTupleStatus(tuple))) { 237 status = RcsContactPresenceTuple.TUPLE_BASIC_STATUS_OPEN; 238 } 239 240 String serviceId = PidfParserUtils.getTupleServiceId(tuple); 241 String serviceVersion = PidfParserUtils.getTupleServiceVersion(tuple); 242 String serviceDescription = PidfParserUtils.getTupleServiceDescription(tuple); 243 244 RcsContactPresenceTuple.Builder builder = new RcsContactPresenceTuple.Builder(status, 245 serviceId, serviceVersion); 246 247 // Set contact uri 248 String contact = PidfParserUtils.getTupleContact(tuple); 249 if (!TextUtils.isEmpty(contact)) { 250 builder.setContactUri(Uri.parse(contact)); 251 } 252 253 // Use local time instead to prevent we receive the incorrect timestamp from the network. 254 builder.setTime(getTimestampProxy().getTimestamp()); 255 256 // Set service description 257 if (!TextUtils.isEmpty(serviceDescription)) { 258 builder.setServiceDescription(serviceDescription); 259 } 260 261 // Set service capabilities 262 ServiceCaps serviceCaps = tuple.getServiceCaps(); 263 if (serviceCaps != null) { 264 List<ElementBase> serviceCapsList = serviceCaps.getElements(); 265 if (serviceCapsList != null && !serviceCapsList.isEmpty()) { 266 boolean isAudioSupported = false; 267 boolean isVideoSupported = false; 268 List<String> supportedTypes = null; 269 List<String> notSupportedTypes = null; 270 271 for (ElementBase element : serviceCapsList) { 272 if (element instanceof Audio) { 273 isAudioSupported = ((Audio) element).isAudioSupported(); 274 } else if (element instanceof Video) { 275 isVideoSupported = ((Video) element).isVideoSupported(); 276 } else if (element instanceof Duplex) { 277 supportedTypes = ((Duplex) element).getSupportedTypes(); 278 notSupportedTypes = ((Duplex) element).getNotSupportedTypes(); 279 } 280 } 281 282 ServiceCapabilities.Builder capabilitiesBuilder 283 = new ServiceCapabilities.Builder(isAudioSupported, isVideoSupported); 284 285 if (supportedTypes != null && !supportedTypes.isEmpty()) { 286 for (String supportedType : supportedTypes) { 287 capabilitiesBuilder.addSupportedDuplexMode(supportedType); 288 } 289 } 290 291 if (notSupportedTypes != null && !notSupportedTypes.isEmpty()) { 292 for (String notSupportedType : notSupportedTypes) { 293 capabilitiesBuilder.addUnsupportedDuplexMode(notSupportedType); 294 } 295 } 296 builder.setServiceCapabilities(capabilitiesBuilder.build()); 297 } 298 } 299 return builder.build(); 300 } 301 } 302