blob: 9c28c36521f5b19a8129935763ffb5fa2e154f9d [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 android.telephony.ims;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.net.Uri;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Represents a PIDF tuple element that is part of the presence element returned from the carrier
* network during a SUBSCRIBE request. See RFC3863 for more information.
* @hide
*/
@SystemApi
public final class RcsContactPresenceTuple implements Parcelable {
private static final String LOG_TAG = "RcsContactPresenceTuple";
/**
* The service ID used to indicate that service discovery via presence is available.
* <p>
* See RCC.07 v5.0 specification for more information.
* @hide
*/
public static final String SERVICE_ID_PRESENCE =
"org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcse.dp";
/**
* The service ID used to indicate that MMTEL service is available.
* <p>
* See the GSMA RCC.07 specification for more information.
*/
public static final String SERVICE_ID_MMTEL = "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.mmtel";
/**
* The service ID used to indicate that the chat(v1.0) is available.
* <p>
* See the GSMA RCC.07 specification for more information.
*/
public static final String SERVICE_ID_CHAT_V1 = "org.openmobilealliance:IM-session";
/**
* The service ID used to indicate that the chat(v2.0) is available.
* <p>
* See the GSMA RCC.07 specification for more information.
*/
public static final String SERVICE_ID_CHAT_V2 = "org.openmobilealliance:ChatSession";
/**
* The service ID used to indicate that the File Transfer is available.
* <p>
* See the GSMA RCC.07 specification for more information.
*/
public static final String SERVICE_ID_FT = "org.openmobilealliance:File-Transfer-HTTP";
/**
* The service ID used to indicate that the File Transfer over SMS is available.
* <p>
* See the GSMA RCC.07 specification for more information.
*/
public static final String SERVICE_ID_FT_OVER_SMS =
"org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.ftsms";
/**
* The service ID used to indicate that the Geolocation Push is available.
* <p>
* See the GSMA RCC.07 specification for more information.
*/
public static final String SERVICE_ID_GEO_PUSH =
"org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.geopush";
/**
* The service ID used to indicate that the Geolocation Push via SMS is available.
* <p>
* See the GSMA RCC.07 specification for more information.
*/
public static final String SERVICE_ID_GEO_PUSH_VIA_SMS =
"org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.geosms";
/**
* The service ID used to indicate that the Call Composer is available.
* <p>
* See the GSMA RCC.07 specification for more information.
*/
public static final String SERVICE_ID_CALL_COMPOSER =
"org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.callcomposer";
/**
* The service ID used to indicate that the Post Call is available.
* <p>
* See the GSMA RCC.07 specification for more information.
*/
public static final String SERVICE_ID_POST_CALL =
"org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.callunanswered";
/**
* The service ID used to indicate that the Shared Map is available.
* <p>
* See the GSMA RCC.07 specification for more information.
*/
public static final String SERVICE_ID_SHARED_MAP =
"org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.sharedmap";
/**
* The service ID used to indicate that the Shared Sketch is available.
* <p>
* See the GSMA RCC.07 specification for more information.
*/
public static final String SERVICE_ID_SHARED_SKETCH =
"org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.sharedsketch";
/**
* The service ID used to indicate that the Chatbot using Session is available.
* <p>
* See the GSMA RCC.07 specification for more information.
*/
public static final String SERVICE_ID_CHATBOT =
"org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.chatbot";
/**
* The service ID used to indicate that the Standalone Messaging is available.
* <p>
* See the GSMA RCC.07 specification for more information.
*/
public static final String SERVICE_ID_CHATBOT_STANDALONE =
" org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.chatbot.sa";
/**
* The service ID used to indicate that the Chatbot Role is available.
* <p>
* See the GSMA RCC.07 specification for more information.
*/
public static final String SERVICE_ID_CHATBOT_ROLE = "org.gsma.rcs.isbot";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@StringDef(prefix = "SERVICE_ID_", value = {
SERVICE_ID_MMTEL,
SERVICE_ID_CHAT_V1,
SERVICE_ID_CHAT_V2,
SERVICE_ID_FT,
SERVICE_ID_FT_OVER_SMS,
SERVICE_ID_GEO_PUSH,
SERVICE_ID_GEO_PUSH_VIA_SMS,
SERVICE_ID_CALL_COMPOSER,
SERVICE_ID_POST_CALL,
SERVICE_ID_SHARED_MAP,
SERVICE_ID_SHARED_SKETCH,
SERVICE_ID_CHATBOT,
SERVICE_ID_CHATBOT_STANDALONE,
SERVICE_ID_CHATBOT_ROLE
})
public @interface ServiceId {}
/** The service capabilities is available. */
public static final String TUPLE_BASIC_STATUS_OPEN = "open";
/** The service capabilities is unavailable. */
public static final String TUPLE_BASIC_STATUS_CLOSED = "closed";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@StringDef(prefix = "TUPLE_BASIC_STATUS_", value = {
TUPLE_BASIC_STATUS_OPEN,
TUPLE_BASIC_STATUS_CLOSED
})
public @interface BasicStatus {}
/**
* An optional addition to the PIDF Presence Tuple containing service capabilities, which is
* defined in the servcaps element. See RFC5196, section 3.2.1.
*/
public static final class ServiceCapabilities implements Parcelable {
/** The service can simultaneously send and receive data. */
public static final String DUPLEX_MODE_FULL = "full";
/** The service can alternate between sending and receiving data.*/
public static final String DUPLEX_MODE_HALF = "half";
/** The service can only receive data. */
public static final String DUPLEX_MODE_RECEIVE_ONLY = "receive-only";
/** The service can only send data. */
public static final String DUPLEX_MODE_SEND_ONLY = "send-only";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@StringDef(prefix = "DUPLEX_MODE_", value = {
DUPLEX_MODE_FULL,
DUPLEX_MODE_HALF,
DUPLEX_MODE_RECEIVE_ONLY,
DUPLEX_MODE_SEND_ONLY
})
public @interface DuplexMode {}
/**
* Builder to help construct {@link ServiceCapabilities} instances.
*/
public static final class Builder {
private ServiceCapabilities mCapabilities;
/**
* Create the ServiceCapabilities builder, which can be used to set service capabilities
* as well as custom capability extensions.
* @param isAudioCapable Whether the audio is capable or not.
* @param isVideoCapable Whether the video is capable or not.
*/
public Builder(boolean isAudioCapable, boolean isVideoCapable) {
mCapabilities = new ServiceCapabilities(isAudioCapable, isVideoCapable);
}
/**
* Add the supported duplex mode.
* @param mode The supported duplex mode
*/
public @NonNull Builder addSupportedDuplexMode(@NonNull @DuplexMode String mode) {
mCapabilities.mSupportedDuplexModeList.add(mode);
return this;
}
/**
* Add the unsupported duplex mode.
* @param mode The unsupported duplex mode
*/
public @NonNull Builder addUnsupportedDuplexMode(@NonNull @DuplexMode String mode) {
mCapabilities.mUnsupportedDuplexModeList.add(mode);
return this;
}
/**
* @return the ServiceCapabilities instance.
*/
public @NonNull ServiceCapabilities build() {
return mCapabilities;
}
}
private final boolean mIsAudioCapable;
private final boolean mIsVideoCapable;
private final @DuplexMode List<String> mSupportedDuplexModeList = new ArrayList<>();
private final @DuplexMode List<String> mUnsupportedDuplexModeList = new ArrayList<>();
/**
* Use {@link Builder} to build an instance of this interface.
* @param isAudioCapable Whether the audio is capable.
* @param isVideoCapable Whether the video is capable.
*/
ServiceCapabilities(boolean isAudioCapable, boolean isVideoCapable) {
mIsAudioCapable = isAudioCapable;
mIsVideoCapable = isVideoCapable;
}
private ServiceCapabilities(Parcel in) {
mIsAudioCapable = in.readBoolean();
mIsVideoCapable = in.readBoolean();
in.readStringList(mSupportedDuplexModeList);
in.readStringList(mUnsupportedDuplexModeList);
}
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeBoolean(mIsAudioCapable);
out.writeBoolean(mIsVideoCapable);
out.writeStringList(mSupportedDuplexModeList);
out.writeStringList(mUnsupportedDuplexModeList);
}
@Override
public int describeContents() {
return 0;
}
public static final @NonNull Creator<ServiceCapabilities> CREATOR =
new Creator<ServiceCapabilities>() {
@Override
public ServiceCapabilities createFromParcel(Parcel in) {
return new ServiceCapabilities(in);
}
@Override
public ServiceCapabilities[] newArray(int size) {
return new ServiceCapabilities[size];
}
};
/**
* Query the audio capable.
* @return true if the audio is capable, false otherwise.
*/
public boolean isAudioCapable() {
return mIsAudioCapable;
}
/**
* Query the video capable.
* @return true if the video is capable, false otherwise.
*/
public boolean isVideoCapable() {
return mIsVideoCapable;
}
/**
* Get the supported duplex mode list.
* @return The list of supported duplex mode
*/
public @NonNull @DuplexMode List<String> getSupportedDuplexModes() {
return Collections.unmodifiableList(mSupportedDuplexModeList);
}
/**
* Get the unsupported duplex mode list.
* @return The list of unsupported duplex mode
*/
public @NonNull @DuplexMode List<String> getUnsupportedDuplexModes() {
return Collections.unmodifiableList(mUnsupportedDuplexModeList);
}
@Override
public String toString() {
return "servCaps{" + "a=" + mIsAudioCapable + ", v=" + mIsVideoCapable
+ ", supported=" + mSupportedDuplexModeList + ", unsupported="
+ mUnsupportedDuplexModeList + '}';
}
}
/**
* Builder to help construct {@link RcsContactPresenceTuple} instances.
*/
public static final class Builder {
private final RcsContactPresenceTuple mPresenceTuple;
/**
* Builds a RcsContactPresenceTuple instance.
* @param status The status associated with the service capability. See RFC3865 for more
* information.
* @param serviceId The OMA Presence service-id associated with this capability. See the
* OMA Presence SIMPLE specification v1.1, section 10.5.1.
* @param serviceVersion The OMA Presence version associated with the service capability.
* See the OMA Presence SIMPLE specification v1.1, section 10.5.1.
*/
public Builder(@NonNull @BasicStatus String status, @NonNull @ServiceId String serviceId,
@NonNull String serviceVersion) {
mPresenceTuple = new RcsContactPresenceTuple(status, serviceId, serviceVersion);
}
/**
* The optional SIP Contact URI associated with the PIDF tuple element if the network
* expects the user to use the URI instead of the contact URI to contact it.
*/
public @NonNull Builder setContactUri(@NonNull Uri contactUri) {
mPresenceTuple.mContactUri = contactUri;
return this;
}
/**
* The optional timestamp indicating the data and time of the status change of this tuple.
* Per RFC3863 section 4.1.7, the timestamp is formatted as an IMPP datetime format
* string per RFC3339.
*/
public @NonNull Builder setTime(@NonNull Instant timestamp) {
mPresenceTuple.mTimestamp = timestamp;
return this;
}
/**
* An optional parameter containing the description element of the service-description. See
* OMA Presence SIMPLE specification v1.1
*/
public @NonNull Builder setServiceDescription(@NonNull String description) {
mPresenceTuple.mServiceDescription = description;
return this;
}
/**
* An optional parameter containing the service capabilities of the presence tuple if they
* are present in the servcaps element.
*/
public @NonNull Builder setServiceCapabilities(@NonNull ServiceCapabilities caps) {
mPresenceTuple.mServiceCapabilities = caps;
return this;
}
/**
* @return the constructed instance.
*/
public @NonNull RcsContactPresenceTuple build() {
return mPresenceTuple;
}
}
private Uri mContactUri;
private Instant mTimestamp;
private @BasicStatus String mStatus;
// The service information in the service-description element.
private String mServiceId;
private String mServiceVersion;
private String mServiceDescription;
private ServiceCapabilities mServiceCapabilities;
private RcsContactPresenceTuple(@NonNull @BasicStatus String status, @NonNull String serviceId,
@NonNull String serviceVersion) {
mStatus = status;
mServiceId = serviceId;
mServiceVersion = serviceVersion;
}
private RcsContactPresenceTuple(Parcel in) {
mContactUri = in.readParcelable(Uri.class.getClassLoader());
mTimestamp = convertStringFormatTimeToInstant(in.readString());
mStatus = in.readString();
mServiceId = in.readString();
mServiceVersion = in.readString();
mServiceDescription = in.readString();
mServiceCapabilities = in.readParcelable(ServiceCapabilities.class.getClassLoader());
}
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeParcelable(mContactUri, flags);
out.writeString(convertInstantToStringFormat(mTimestamp));
out.writeString(mStatus);
out.writeString(mServiceId);
out.writeString(mServiceVersion);
out.writeString(mServiceDescription);
out.writeParcelable(mServiceCapabilities, flags);
}
@Override
public int describeContents() {
return 0;
}
public static final @NonNull Creator<RcsContactPresenceTuple> CREATOR =
new Creator<RcsContactPresenceTuple>() {
@Override
public RcsContactPresenceTuple createFromParcel(Parcel in) {
return new RcsContactPresenceTuple(in);
}
@Override
public RcsContactPresenceTuple[] newArray(int size) {
return new RcsContactPresenceTuple[size];
}
};
// Convert the Instant to the string format
private String convertInstantToStringFormat(Instant instant) {
if (instant == null) {
return "";
}
return instant.toString();
}
// Convert the time string format to Instant
private @Nullable Instant convertStringFormatTimeToInstant(String timestamp) {
if (TextUtils.isEmpty(timestamp)) {
return null;
}
try {
return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(timestamp, Instant::from);
} catch (DateTimeParseException e) {
return null;
}
}
/** @return the status of the tuple element. */
public @NonNull @BasicStatus String getStatus() {
return mStatus;
}
/** @return the service-id element of the service-description */
public @NonNull String getServiceId() {
return mServiceId;
}
/** @return the version element of the service-description */
public @NonNull String getServiceVersion() {
return mServiceVersion;
}
/** @return the SIP URI contained in the contact element of the tuple if it exists. */
public @Nullable Uri getContactUri() {
return mContactUri;
}
/** @return the timestamp element contained in the tuple if it exists */
public @Nullable Instant getTime() {
return mTimestamp;
}
/** @return the description element contained in the service-description if it exists */
public @Nullable String getServiceDescription() {
return mServiceDescription;
}
/** @return the {@link ServiceCapabilities} of the tuple if it exists. */
public @Nullable ServiceCapabilities getServiceCapabilities() {
return mServiceCapabilities;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("{");
if (Build.IS_ENG) {
builder.append("u=");
builder.append(mContactUri);
} else {
builder.append("u=");
builder.append(mContactUri != null ? "XXX" : "null");
}
builder.append(", id=");
builder.append(mServiceId);
builder.append(", v=");
builder.append(mServiceVersion);
builder.append(", s=");
builder.append(mStatus);
if (mTimestamp != null) {
builder.append(", timestamp=");
builder.append(mTimestamp);
}
if (mServiceDescription != null) {
builder.append(", servDesc=");
builder.append(mServiceDescription);
}
if (mServiceCapabilities != null) {
builder.append(", servCaps=");
builder.append(mServiceCapabilities);
}
builder.append("}");
return builder.toString();
}
}