blob: dc2dc045f7ecdc1d83c8642fffcf4139c217fd1e [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ims.rcs.uce.presence.pidfparser;
import android.annotation.Nullable;
import android.net.Uri;
import android.telephony.ims.RcsContactPresenceTuple;
import android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities;
import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.RcsContactUceCapability.PresenceBuilder;
import android.text.TextUtils;
import android.util.Log;
import com.android.ims.rcs.uce.presence.pidfparser.capabilities.Audio;
import com.android.ims.rcs.uce.presence.pidfparser.capabilities.CapsConstant;
import com.android.ims.rcs.uce.presence.pidfparser.capabilities.Duplex;
import com.android.ims.rcs.uce.presence.pidfparser.capabilities.ServiceCaps;
import com.android.ims.rcs.uce.presence.pidfparser.capabilities.Video;
import com.android.ims.rcs.uce.presence.pidfparser.omapres.OmaPresConstant;
import com.android.ims.rcs.uce.presence.pidfparser.pidf.Basic;
import com.android.ims.rcs.uce.presence.pidfparser.pidf.PidfConstant;
import com.android.ims.rcs.uce.presence.pidfparser.pidf.Presence;
import com.android.ims.rcs.uce.presence.pidfparser.pidf.Tuple;
import com.android.ims.rcs.uce.util.UceUtils;
import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.time.Instant;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;
/**
* Convert between the class RcsContactUceCapability and the pidf format.
*/
public class PidfParser {
private static final String LOG_TAG = UceUtils.getLogPrefix() + "PidfParser";
private static final Pattern PIDF_PATTERN = Pattern.compile("\t|\r|\n");
/**
* Testing interface used to get the timestamp.
*/
@VisibleForTesting
public interface TimestampProxy {
Instant getTimestamp();
}
// The timestamp proxy to create the local timestamp.
private static final TimestampProxy sLocalTimestampProxy = () -> Instant.now();
// Override timestamp proxy for testing only.
private static TimestampProxy sOverrideTimestampProxy;
@VisibleForTesting
public static void setTimestampProxy(TimestampProxy proxy) {
sOverrideTimestampProxy = proxy;
}
private static TimestampProxy getTimestampProxy() {
return (sOverrideTimestampProxy != null) ? sOverrideTimestampProxy : sLocalTimestampProxy;
}
/**
* Convert the RcsContactUceCapability to the string of pidf.
*/
public static String convertToPidf(RcsContactUceCapability capabilities) {
StringWriter pidfWriter = new StringWriter();
try {
// Init the instance of the XmlSerializer.
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlSerializer serializer = factory.newSerializer();
// setup output and namespace
serializer.setOutput(pidfWriter);
serializer.setPrefix("", PidfConstant.NAMESPACE);
serializer.setPrefix("op", OmaPresConstant.NAMESPACE);
serializer.setPrefix("caps", CapsConstant.NAMESPACE);
// Get the Presence element
Presence presence = PidfParserUtils.getPresence(capabilities);
// Start serializing.
serializer.startDocument(PidfParserConstant.ENCODING_UTF_8, true);
presence.serialize(serializer);
serializer.endDocument();
serializer.flush();
} catch (XmlPullParserException parserEx) {
parserEx.printStackTrace();
return null;
} catch (IOException ioException) {
ioException.printStackTrace();
return null;
}
return pidfWriter.toString();
}
/**
* Get the RcsContactUceCapability from the given PIDF xml format.
*/
public static @Nullable RcsContactUceCapability getRcsContactUceCapability(String pidf) {
if (TextUtils.isEmpty(pidf)) {
Log.w(LOG_TAG, "getRcsContactUceCapability: The given pidf is empty");
return null;
}
// Filter the newline characters
Matcher matcher = PIDF_PATTERN.matcher(pidf);
String formattedPidf = matcher.replaceAll("");
if (TextUtils.isEmpty(formattedPidf)) {
Log.w(LOG_TAG, "getRcsContactUceCapability: The formatted pidf is empty");
return null;
}
Reader reader = null;
try {
// Init the instance of the parser
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
reader = new StringReader(formattedPidf);
parser.setInput(reader);
// Start parsing
Presence presence = parsePidf(parser);
// Convert from the Presence to the RcsContactUceCapability
return convertToRcsContactUceCapability(presence);
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
private static Presence parsePidf(XmlPullParser parser) throws IOException,
XmlPullParserException {
Presence presence = null;
int nextType = parser.next();
do {
// Find the Presence start tag
if (nextType == XmlPullParser.START_TAG
&& Presence.ELEMENT_NAME.equals(parser.getName())) {
presence = new Presence();
presence.parse(parser);
break;
}
nextType = parser.next();
} while(nextType != XmlPullParser.END_DOCUMENT);
return presence;
}
/*
* Convert the given Presence to the RcsContactUceCapability
*/
private static RcsContactUceCapability convertToRcsContactUceCapability(Presence presence) {
if (presence == null) {
Log.w(LOG_TAG, "convertToRcsContactUceCapability: The presence is null");
return null;
}
if (TextUtils.isEmpty(presence.getEntity())) {
Log.w(LOG_TAG, "convertToRcsContactUceCapability: The entity is empty");
return null;
}
PresenceBuilder presenceBuilder = new PresenceBuilder(Uri.parse(presence.getEntity()),
RcsContactUceCapability.SOURCE_TYPE_NETWORK,
RcsContactUceCapability.REQUEST_RESULT_FOUND);
// Add all the capability tuples of this contact
presence.getTupleList().forEach(tuple -> {
RcsContactPresenceTuple capabilityTuple = getRcsContactPresenceTuple(tuple);
if (capabilityTuple != null) {
presenceBuilder.addCapabilityTuple(capabilityTuple);
}
});
return presenceBuilder.build();
}
/*
* Get the RcsContactPresenceTuple from the giving tuple element.
*/
private static RcsContactPresenceTuple getRcsContactPresenceTuple(Tuple tuple) {
if (tuple == null) {
return null;
}
String status = RcsContactPresenceTuple.TUPLE_BASIC_STATUS_CLOSED;
if (Basic.OPEN.equals(PidfParserUtils.getTupleStatus(tuple))) {
status = RcsContactPresenceTuple.TUPLE_BASIC_STATUS_OPEN;
}
String serviceId = PidfParserUtils.getTupleServiceId(tuple);
String serviceVersion = PidfParserUtils.getTupleServiceVersion(tuple);
String serviceDescription = PidfParserUtils.getTupleServiceDescription(tuple);
RcsContactPresenceTuple.Builder builder = new RcsContactPresenceTuple.Builder(status,
serviceId, serviceVersion);
// Set contact uri
String contact = PidfParserUtils.getTupleContact(tuple);
if (!TextUtils.isEmpty(contact)) {
builder.setContactUri(Uri.parse(contact));
}
// Use local time instead to prevent we receive the incorrect timestamp from the network.
builder.setTime(getTimestampProxy().getTimestamp());
// Set service description
if (!TextUtils.isEmpty(serviceDescription)) {
builder.setServiceDescription(serviceDescription);
}
// Set service capabilities
ServiceCaps serviceCaps = tuple.getServiceCaps();
if (serviceCaps != null) {
List<ElementBase> serviceCapsList = serviceCaps.getElements();
if (serviceCapsList != null && !serviceCapsList.isEmpty()) {
boolean isAudioSupported = false;
boolean isVideoSupported = false;
List<String> supportedTypes = null;
List<String> notSupportedTypes = null;
for (ElementBase element : serviceCapsList) {
if (element instanceof Audio) {
isAudioSupported = ((Audio) element).isAudioSupported();
} else if (element instanceof Video) {
isVideoSupported = ((Video) element).isVideoSupported();
} else if (element instanceof Duplex) {
supportedTypes = ((Duplex) element).getSupportedTypes();
notSupportedTypes = ((Duplex) element).getNotSupportedTypes();
}
}
ServiceCapabilities.Builder capabilitiesBuilder
= new ServiceCapabilities.Builder(isAudioSupported, isVideoSupported);
if (supportedTypes != null && !supportedTypes.isEmpty()) {
for (String supportedType : supportedTypes) {
capabilitiesBuilder.addSupportedDuplexMode(supportedType);
}
}
if (notSupportedTypes != null && !notSupportedTypes.isEmpty()) {
for (String notSupportedType : notSupportedTypes) {
capabilitiesBuilder.addUnsupportedDuplexMode(notSupportedType);
}
}
builder.setServiceCapabilities(capabilitiesBuilder.build());
}
}
return builder.build();
}
}