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 android.telephony.ims; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.StringDef; 22 import android.annotation.SystemApi; 23 import android.net.Uri; 24 import android.os.Build; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 import android.text.TextUtils; 28 29 import java.lang.annotation.Retention; 30 import java.lang.annotation.RetentionPolicy; 31 import java.time.Instant; 32 import java.time.format.DateTimeFormatter; 33 import java.time.format.DateTimeParseException; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.List; 37 38 /** 39 * Represents a PIDF tuple element that is part of the presence element returned from the carrier 40 * network during a SUBSCRIBE request. See RFC3863 for more information. 41 * @hide 42 */ 43 @SystemApi 44 public final class RcsContactPresenceTuple implements Parcelable { 45 46 private static final String LOG_TAG = "RcsContactPresenceTuple"; 47 48 /** 49 * The service ID used to indicate that service discovery via presence is available. 50 * <p> 51 * See RCC.07 v5.0 specification for more information. 52 * @hide 53 */ 54 public static final String SERVICE_ID_PRESENCE = 55 "org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcse.dp"; 56 57 /** 58 * The service ID used to indicate that MMTEL service is available. 59 * <p> 60 * See the GSMA RCC.07 specification for more information. 61 */ 62 public static final String SERVICE_ID_MMTEL = "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.mmtel"; 63 64 /** 65 * The service ID used to indicate that the chat(v1.0) is available. 66 * <p> 67 * See the GSMA RCC.07 specification for more information. 68 */ 69 public static final String SERVICE_ID_CHAT_V1 = "org.openmobilealliance:IM-session"; 70 71 /** 72 * The service ID used to indicate that the chat(v2.0) is available. 73 * <p> 74 * See the GSMA RCC.07 specification for more information. 75 */ 76 public static final String SERVICE_ID_CHAT_V2 = "org.openmobilealliance:ChatSession"; 77 78 /** 79 * The service ID used to indicate that the File Transfer is available. 80 * <p> 81 * See the GSMA RCC.07 specification for more information. 82 */ 83 public static final String SERVICE_ID_FT = "org.openmobilealliance:File-Transfer-HTTP"; 84 85 /** 86 * The service ID used to indicate that the File Transfer over SMS is available. 87 * <p> 88 * See the GSMA RCC.07 specification for more information. 89 */ 90 public static final String SERVICE_ID_FT_OVER_SMS = 91 "org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.ftsms"; 92 93 /** 94 * The service ID used to indicate that the Geolocation Push is available. 95 * <p> 96 * See the GSMA RCC.07 specification for more information. 97 */ 98 public static final String SERVICE_ID_GEO_PUSH = 99 "org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.geopush"; 100 101 /** 102 * The service ID used to indicate that the Geolocation Push via SMS is available. 103 * <p> 104 * See the GSMA RCC.07 specification for more information. 105 */ 106 public static final String SERVICE_ID_GEO_PUSH_VIA_SMS = 107 "org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.geosms"; 108 109 /** 110 * The service ID used to indicate that the Call Composer is available. 111 * <p> 112 * See the GSMA RCC.07 specification for more information. 113 */ 114 public static final String SERVICE_ID_CALL_COMPOSER = 115 "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.callcomposer"; 116 117 /** 118 * The service ID used to indicate that the Post Call is available. 119 * <p> 120 * See the GSMA RCC.07 specification for more information. 121 */ 122 public static final String SERVICE_ID_POST_CALL = 123 "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.callunanswered"; 124 125 /** 126 * The service ID used to indicate that the Shared Map is available. 127 * <p> 128 * See the GSMA RCC.07 specification for more information. 129 */ 130 public static final String SERVICE_ID_SHARED_MAP = 131 "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.sharedmap"; 132 133 /** 134 * The service ID used to indicate that the Shared Sketch is available. 135 * <p> 136 * See the GSMA RCC.07 specification for more information. 137 */ 138 public static final String SERVICE_ID_SHARED_SKETCH = 139 "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.sharedsketch"; 140 141 /** 142 * The service ID used to indicate that the Chatbot using Session is available. 143 * <p> 144 * See the GSMA RCC.07 specification for more information. 145 */ 146 public static final String SERVICE_ID_CHATBOT = 147 "org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.chatbot"; 148 149 /** 150 * The service ID used to indicate that the Chatbot using Standalone Messaging is available. 151 * <p> 152 * See the GSMA RCC.07 specification for more information. 153 */ 154 public static final String SERVICE_ID_CHATBOT_STANDALONE = 155 " org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.chatbot.sa"; 156 157 /** 158 * The service ID used to indicate that the Chatbot Role is available. 159 * <p> 160 * See the GSMA RCC.07 specification for more information. 161 */ 162 public static final String SERVICE_ID_CHATBOT_ROLE = "org.gsma.rcs.isbot"; 163 164 /** 165 * The service ID used to indicate that the Standalone Messaging is available. 166 * <p> 167 * See the GSMA RCC.07 RCS5_1_advanced_communications_specification_v4.0 specification 168 * for more information. 169 */ 170 public static final String SERVICE_ID_SLM = "org.openmobilealliance:StandaloneMsg"; 171 172 /** @hide */ 173 @Retention(RetentionPolicy.SOURCE) 174 @StringDef(prefix = "SERVICE_ID_", value = { 175 SERVICE_ID_MMTEL, 176 SERVICE_ID_CHAT_V1, 177 SERVICE_ID_CHAT_V2, 178 SERVICE_ID_FT, 179 SERVICE_ID_FT_OVER_SMS, 180 SERVICE_ID_GEO_PUSH, 181 SERVICE_ID_GEO_PUSH_VIA_SMS, 182 SERVICE_ID_CALL_COMPOSER, 183 SERVICE_ID_POST_CALL, 184 SERVICE_ID_SHARED_MAP, 185 SERVICE_ID_SHARED_SKETCH, 186 SERVICE_ID_CHATBOT, 187 SERVICE_ID_CHATBOT_STANDALONE, 188 SERVICE_ID_CHATBOT_ROLE, 189 SERVICE_ID_SLM 190 }) 191 public @interface ServiceId {} 192 193 /** The service capabilities is available. */ 194 public static final String TUPLE_BASIC_STATUS_OPEN = "open"; 195 196 /** The service capabilities is unavailable. */ 197 public static final String TUPLE_BASIC_STATUS_CLOSED = "closed"; 198 199 /** @hide */ 200 @Retention(RetentionPolicy.SOURCE) 201 @StringDef(prefix = "TUPLE_BASIC_STATUS_", value = { 202 TUPLE_BASIC_STATUS_OPEN, 203 TUPLE_BASIC_STATUS_CLOSED 204 }) 205 public @interface BasicStatus {} 206 207 /** 208 * An optional addition to the PIDF Presence Tuple containing service capabilities, which is 209 * defined in the servcaps element. See RFC5196, section 3.2.1. 210 */ 211 public static final class ServiceCapabilities implements Parcelable { 212 213 /** The service can simultaneously send and receive data. */ 214 public static final String DUPLEX_MODE_FULL = "full"; 215 216 /** The service can alternate between sending and receiving data.*/ 217 public static final String DUPLEX_MODE_HALF = "half"; 218 219 /** The service can only receive data. */ 220 public static final String DUPLEX_MODE_RECEIVE_ONLY = "receive-only"; 221 222 /** The service can only send data. */ 223 public static final String DUPLEX_MODE_SEND_ONLY = "send-only"; 224 225 /** @hide */ 226 @Retention(RetentionPolicy.SOURCE) 227 @StringDef(prefix = "DUPLEX_MODE_", value = { 228 DUPLEX_MODE_FULL, 229 DUPLEX_MODE_HALF, 230 DUPLEX_MODE_RECEIVE_ONLY, 231 DUPLEX_MODE_SEND_ONLY 232 }) 233 public @interface DuplexMode {} 234 235 /** 236 * Builder to help construct {@link ServiceCapabilities} instances. 237 */ 238 public static final class Builder { 239 240 private ServiceCapabilities mCapabilities; 241 242 /** 243 * Create the ServiceCapabilities builder, which can be used to set service capabilities 244 * as well as custom capability extensions. 245 * @param isAudioCapable Whether the audio is capable or not. 246 * @param isVideoCapable Whether the video is capable or not. 247 */ Builder(boolean isAudioCapable, boolean isVideoCapable)248 public Builder(boolean isAudioCapable, boolean isVideoCapable) { 249 mCapabilities = new ServiceCapabilities(isAudioCapable, isVideoCapable); 250 } 251 252 /** 253 * Add the supported duplex mode. 254 * @param mode The supported duplex mode 255 */ addSupportedDuplexMode(@onNull @uplexMode String mode)256 public @NonNull Builder addSupportedDuplexMode(@NonNull @DuplexMode String mode) { 257 mCapabilities.mSupportedDuplexModeList.add(mode); 258 return this; 259 } 260 261 /** 262 * Add the unsupported duplex mode. 263 * @param mode The unsupported duplex mode 264 */ addUnsupportedDuplexMode(@onNull @uplexMode String mode)265 public @NonNull Builder addUnsupportedDuplexMode(@NonNull @DuplexMode String mode) { 266 mCapabilities.mUnsupportedDuplexModeList.add(mode); 267 return this; 268 } 269 270 /** 271 * @return the ServiceCapabilities instance. 272 */ build()273 public @NonNull ServiceCapabilities build() { 274 return mCapabilities; 275 } 276 } 277 278 private final boolean mIsAudioCapable; 279 private final boolean mIsVideoCapable; 280 private final @DuplexMode List<String> mSupportedDuplexModeList = new ArrayList<>(); 281 private final @DuplexMode List<String> mUnsupportedDuplexModeList = new ArrayList<>(); 282 283 /** 284 * Use {@link Builder} to build an instance of this interface. 285 * @param isAudioCapable Whether the audio is capable. 286 * @param isVideoCapable Whether the video is capable. 287 */ ServiceCapabilities(boolean isAudioCapable, boolean isVideoCapable)288 ServiceCapabilities(boolean isAudioCapable, boolean isVideoCapable) { 289 mIsAudioCapable = isAudioCapable; 290 mIsVideoCapable = isVideoCapable; 291 } 292 ServiceCapabilities(Parcel in)293 private ServiceCapabilities(Parcel in) { 294 mIsAudioCapable = in.readBoolean(); 295 mIsVideoCapable = in.readBoolean(); 296 in.readStringList(mSupportedDuplexModeList); 297 in.readStringList(mUnsupportedDuplexModeList); 298 } 299 300 @Override writeToParcel(@onNull Parcel out, int flags)301 public void writeToParcel(@NonNull Parcel out, int flags) { 302 out.writeBoolean(mIsAudioCapable); 303 out.writeBoolean(mIsVideoCapable); 304 out.writeStringList(mSupportedDuplexModeList); 305 out.writeStringList(mUnsupportedDuplexModeList); 306 } 307 308 @Override describeContents()309 public int describeContents() { 310 return 0; 311 } 312 313 public static final @NonNull Creator<ServiceCapabilities> CREATOR = 314 new Creator<ServiceCapabilities>() { 315 @Override 316 public ServiceCapabilities createFromParcel(Parcel in) { 317 return new ServiceCapabilities(in); 318 } 319 320 @Override 321 public ServiceCapabilities[] newArray(int size) { 322 return new ServiceCapabilities[size]; 323 } 324 }; 325 326 /** 327 * Query the audio capable. 328 * @return true if the audio is capable, false otherwise. 329 */ isAudioCapable()330 public boolean isAudioCapable() { 331 return mIsAudioCapable; 332 } 333 334 /** 335 * Query the video capable. 336 * @return true if the video is capable, false otherwise. 337 */ isVideoCapable()338 public boolean isVideoCapable() { 339 return mIsVideoCapable; 340 } 341 342 /** 343 * Get the supported duplex mode list. 344 * @return The list of supported duplex mode 345 */ getSupportedDuplexModes()346 public @NonNull @DuplexMode List<String> getSupportedDuplexModes() { 347 return Collections.unmodifiableList(mSupportedDuplexModeList); 348 } 349 350 /** 351 * Get the unsupported duplex mode list. 352 * @return The list of unsupported duplex mode 353 */ getUnsupportedDuplexModes()354 public @NonNull @DuplexMode List<String> getUnsupportedDuplexModes() { 355 return Collections.unmodifiableList(mUnsupportedDuplexModeList); 356 } 357 358 @Override toString()359 public String toString() { 360 return "servCaps{" + "a=" + mIsAudioCapable + ", v=" + mIsVideoCapable 361 + ", supported=" + mSupportedDuplexModeList + ", unsupported=" 362 + mUnsupportedDuplexModeList + '}'; 363 } 364 } 365 366 /** 367 * Builder to help construct {@link RcsContactPresenceTuple} instances. 368 */ 369 public static final class Builder { 370 371 private final RcsContactPresenceTuple mPresenceTuple; 372 373 /** 374 * Builds a RcsContactPresenceTuple instance. 375 * @param status The status associated with the service capability. See RFC3865 for more 376 * information. 377 * @param serviceId The OMA Presence service-id associated with this capability. See the 378 * OMA Presence SIMPLE specification v1.1, section 10.5.1. 379 * @param serviceVersion The OMA Presence version associated with the service capability. 380 * See the OMA Presence SIMPLE specification v1.1, section 10.5.1. 381 */ Builder(@onNull @asicStatus String status, @NonNull @ServiceId String serviceId, @NonNull String serviceVersion)382 public Builder(@NonNull @BasicStatus String status, @NonNull @ServiceId String serviceId, 383 @NonNull String serviceVersion) { 384 mPresenceTuple = new RcsContactPresenceTuple(status, serviceId, serviceVersion); 385 } 386 387 /** 388 * The optional SIP Contact URI associated with the PIDF tuple element if the network 389 * expects the user to use the URI instead of the contact URI to contact it. 390 */ setContactUri(@onNull Uri contactUri)391 public @NonNull Builder setContactUri(@NonNull Uri contactUri) { 392 mPresenceTuple.mContactUri = contactUri; 393 return this; 394 } 395 396 /** 397 * The optional timestamp indicating the data and time of the status change of this tuple. 398 * Per RFC3863 section 4.1.7, the timestamp is formatted as an IMPP datetime format 399 * string per RFC3339. 400 */ setTime(@onNull Instant timestamp)401 public @NonNull Builder setTime(@NonNull Instant timestamp) { 402 mPresenceTuple.mTimestamp = timestamp; 403 return this; 404 } 405 406 /** 407 * An optional parameter containing the description element of the service-description. See 408 * OMA Presence SIMPLE specification v1.1 409 */ setServiceDescription(@onNull String description)410 public @NonNull Builder setServiceDescription(@NonNull String description) { 411 mPresenceTuple.mServiceDescription = description; 412 return this; 413 } 414 415 /** 416 * An optional parameter containing the service capabilities of the presence tuple if they 417 * are present in the servcaps element. 418 */ setServiceCapabilities(@onNull ServiceCapabilities caps)419 public @NonNull Builder setServiceCapabilities(@NonNull ServiceCapabilities caps) { 420 mPresenceTuple.mServiceCapabilities = caps; 421 return this; 422 } 423 424 /** 425 * @return the constructed instance. 426 */ build()427 public @NonNull RcsContactPresenceTuple build() { 428 return mPresenceTuple; 429 } 430 } 431 432 private Uri mContactUri; 433 private Instant mTimestamp; 434 private @BasicStatus String mStatus; 435 436 // The service information in the service-description element. 437 private String mServiceId; 438 private String mServiceVersion; 439 private String mServiceDescription; 440 441 private ServiceCapabilities mServiceCapabilities; 442 RcsContactPresenceTuple(@onNull @asicStatus String status, @NonNull String serviceId, @NonNull String serviceVersion)443 private RcsContactPresenceTuple(@NonNull @BasicStatus String status, @NonNull String serviceId, 444 @NonNull String serviceVersion) { 445 mStatus = status; 446 mServiceId = serviceId; 447 mServiceVersion = serviceVersion; 448 } 449 RcsContactPresenceTuple(Parcel in)450 private RcsContactPresenceTuple(Parcel in) { 451 mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class); 452 mTimestamp = convertStringFormatTimeToInstant(in.readString()); 453 mStatus = in.readString(); 454 mServiceId = in.readString(); 455 mServiceVersion = in.readString(); 456 mServiceDescription = in.readString(); 457 mServiceCapabilities = in.readParcelable(ServiceCapabilities.class.getClassLoader(), android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities.class); 458 } 459 460 @Override writeToParcel(@onNull Parcel out, int flags)461 public void writeToParcel(@NonNull Parcel out, int flags) { 462 out.writeParcelable(mContactUri, flags); 463 out.writeString(convertInstantToStringFormat(mTimestamp)); 464 out.writeString(mStatus); 465 out.writeString(mServiceId); 466 out.writeString(mServiceVersion); 467 out.writeString(mServiceDescription); 468 out.writeParcelable(mServiceCapabilities, flags); 469 } 470 471 @Override describeContents()472 public int describeContents() { 473 return 0; 474 } 475 476 public static final @NonNull Creator<RcsContactPresenceTuple> CREATOR = 477 new Creator<RcsContactPresenceTuple>() { 478 @Override 479 public RcsContactPresenceTuple createFromParcel(Parcel in) { 480 return new RcsContactPresenceTuple(in); 481 } 482 483 @Override 484 public RcsContactPresenceTuple[] newArray(int size) { 485 return new RcsContactPresenceTuple[size]; 486 } 487 }; 488 489 // Convert the Instant to the string format convertInstantToStringFormat(Instant instant)490 private String convertInstantToStringFormat(Instant instant) { 491 if (instant == null) { 492 return ""; 493 } 494 return instant.toString(); 495 } 496 497 // Convert the time string format to Instant convertStringFormatTimeToInstant(String timestamp)498 private @Nullable Instant convertStringFormatTimeToInstant(String timestamp) { 499 if (TextUtils.isEmpty(timestamp)) { 500 return null; 501 } 502 try { 503 return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(timestamp, Instant::from); 504 } catch (DateTimeParseException e) { 505 return null; 506 } 507 } 508 509 /** @return the status of the tuple element. */ getStatus()510 public @NonNull @BasicStatus String getStatus() { 511 return mStatus; 512 } 513 514 /** @return the service-id element of the service-description */ getServiceId()515 public @NonNull String getServiceId() { 516 return mServiceId; 517 } 518 519 /** @return the version element of the service-description */ getServiceVersion()520 public @NonNull String getServiceVersion() { 521 return mServiceVersion; 522 } 523 524 /** @return the SIP URI contained in the contact element of the tuple if it exists. */ getContactUri()525 public @Nullable Uri getContactUri() { 526 return mContactUri; 527 } 528 529 /** @return the timestamp element contained in the tuple if it exists */ getTime()530 public @Nullable Instant getTime() { 531 return mTimestamp; 532 } 533 534 /** @return the description element contained in the service-description if it exists */ getServiceDescription()535 public @Nullable String getServiceDescription() { 536 return mServiceDescription; 537 } 538 539 /** @return the {@link ServiceCapabilities} of the tuple if it exists. */ getServiceCapabilities()540 public @Nullable ServiceCapabilities getServiceCapabilities() { 541 return mServiceCapabilities; 542 } 543 544 @Override toString()545 public String toString() { 546 StringBuilder builder = new StringBuilder("{"); 547 if (Build.IS_ENG) { 548 builder.append("u="); 549 builder.append(mContactUri); 550 } else { 551 builder.append("u="); 552 builder.append(mContactUri != null ? "XXX" : "null"); 553 } 554 builder.append(", id="); 555 builder.append(mServiceId); 556 builder.append(", v="); 557 builder.append(mServiceVersion); 558 builder.append(", s="); 559 builder.append(mStatus); 560 if (mTimestamp != null) { 561 builder.append(", timestamp="); 562 builder.append(mTimestamp); 563 } 564 if (mServiceDescription != null) { 565 builder.append(", servDesc="); 566 builder.append(mServiceDescription); 567 } 568 if (mServiceCapabilities != null) { 569 builder.append(", servCaps="); 570 builder.append(mServiceCapabilities); 571 } 572 builder.append("}"); 573 return builder.toString(); 574 } 575 } 576