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 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 /** @hide */ 165 @Retention(RetentionPolicy.SOURCE) 166 @StringDef(prefix = "SERVICE_ID_", value = { 167 SERVICE_ID_MMTEL, 168 SERVICE_ID_CHAT_V1, 169 SERVICE_ID_CHAT_V2, 170 SERVICE_ID_FT, 171 SERVICE_ID_FT_OVER_SMS, 172 SERVICE_ID_GEO_PUSH, 173 SERVICE_ID_GEO_PUSH_VIA_SMS, 174 SERVICE_ID_CALL_COMPOSER, 175 SERVICE_ID_POST_CALL, 176 SERVICE_ID_SHARED_MAP, 177 SERVICE_ID_SHARED_SKETCH, 178 SERVICE_ID_CHATBOT, 179 SERVICE_ID_CHATBOT_STANDALONE, 180 SERVICE_ID_CHATBOT_ROLE 181 }) 182 public @interface ServiceId {} 183 184 /** The service capabilities is available. */ 185 public static final String TUPLE_BASIC_STATUS_OPEN = "open"; 186 187 /** The service capabilities is unavailable. */ 188 public static final String TUPLE_BASIC_STATUS_CLOSED = "closed"; 189 190 /** @hide */ 191 @Retention(RetentionPolicy.SOURCE) 192 @StringDef(prefix = "TUPLE_BASIC_STATUS_", value = { 193 TUPLE_BASIC_STATUS_OPEN, 194 TUPLE_BASIC_STATUS_CLOSED 195 }) 196 public @interface BasicStatus {} 197 198 /** 199 * An optional addition to the PIDF Presence Tuple containing service capabilities, which is 200 * defined in the servcaps element. See RFC5196, section 3.2.1. 201 */ 202 public static final class ServiceCapabilities implements Parcelable { 203 204 /** The service can simultaneously send and receive data. */ 205 public static final String DUPLEX_MODE_FULL = "full"; 206 207 /** The service can alternate between sending and receiving data.*/ 208 public static final String DUPLEX_MODE_HALF = "half"; 209 210 /** The service can only receive data. */ 211 public static final String DUPLEX_MODE_RECEIVE_ONLY = "receive-only"; 212 213 /** The service can only send data. */ 214 public static final String DUPLEX_MODE_SEND_ONLY = "send-only"; 215 216 /** @hide */ 217 @Retention(RetentionPolicy.SOURCE) 218 @StringDef(prefix = "DUPLEX_MODE_", value = { 219 DUPLEX_MODE_FULL, 220 DUPLEX_MODE_HALF, 221 DUPLEX_MODE_RECEIVE_ONLY, 222 DUPLEX_MODE_SEND_ONLY 223 }) 224 public @interface DuplexMode {} 225 226 /** 227 * Builder to help construct {@link ServiceCapabilities} instances. 228 */ 229 public static final class Builder { 230 231 private ServiceCapabilities mCapabilities; 232 233 /** 234 * Create the ServiceCapabilities builder, which can be used to set service capabilities 235 * as well as custom capability extensions. 236 * @param isAudioCapable Whether the audio is capable or not. 237 * @param isVideoCapable Whether the video is capable or not. 238 */ Builder(boolean isAudioCapable, boolean isVideoCapable)239 public Builder(boolean isAudioCapable, boolean isVideoCapable) { 240 mCapabilities = new ServiceCapabilities(isAudioCapable, isVideoCapable); 241 } 242 243 /** 244 * Add the supported duplex mode. 245 * @param mode The supported duplex mode 246 */ addSupportedDuplexMode(@onNull @uplexMode String mode)247 public @NonNull Builder addSupportedDuplexMode(@NonNull @DuplexMode String mode) { 248 mCapabilities.mSupportedDuplexModeList.add(mode); 249 return this; 250 } 251 252 /** 253 * Add the unsupported duplex mode. 254 * @param mode The unsupported duplex mode 255 */ addUnsupportedDuplexMode(@onNull @uplexMode String mode)256 public @NonNull Builder addUnsupportedDuplexMode(@NonNull @DuplexMode String mode) { 257 mCapabilities.mUnsupportedDuplexModeList.add(mode); 258 return this; 259 } 260 261 /** 262 * @return the ServiceCapabilities instance. 263 */ build()264 public @NonNull ServiceCapabilities build() { 265 return mCapabilities; 266 } 267 } 268 269 private final boolean mIsAudioCapable; 270 private final boolean mIsVideoCapable; 271 private final @DuplexMode List<String> mSupportedDuplexModeList = new ArrayList<>(); 272 private final @DuplexMode List<String> mUnsupportedDuplexModeList = new ArrayList<>(); 273 274 /** 275 * Use {@link Builder} to build an instance of this interface. 276 * @param isAudioCapable Whether the audio is capable. 277 * @param isVideoCapable Whether the video is capable. 278 */ ServiceCapabilities(boolean isAudioCapable, boolean isVideoCapable)279 ServiceCapabilities(boolean isAudioCapable, boolean isVideoCapable) { 280 mIsAudioCapable = isAudioCapable; 281 mIsVideoCapable = isVideoCapable; 282 } 283 ServiceCapabilities(Parcel in)284 private ServiceCapabilities(Parcel in) { 285 mIsAudioCapable = in.readBoolean(); 286 mIsVideoCapable = in.readBoolean(); 287 in.readStringList(mSupportedDuplexModeList); 288 in.readStringList(mUnsupportedDuplexModeList); 289 } 290 291 @Override writeToParcel(@onNull Parcel out, int flags)292 public void writeToParcel(@NonNull Parcel out, int flags) { 293 out.writeBoolean(mIsAudioCapable); 294 out.writeBoolean(mIsVideoCapable); 295 out.writeStringList(mSupportedDuplexModeList); 296 out.writeStringList(mUnsupportedDuplexModeList); 297 } 298 299 @Override describeContents()300 public int describeContents() { 301 return 0; 302 } 303 304 public static final @NonNull Creator<ServiceCapabilities> CREATOR = 305 new Creator<ServiceCapabilities>() { 306 @Override 307 public ServiceCapabilities createFromParcel(Parcel in) { 308 return new ServiceCapabilities(in); 309 } 310 311 @Override 312 public ServiceCapabilities[] newArray(int size) { 313 return new ServiceCapabilities[size]; 314 } 315 }; 316 317 /** 318 * Query the audio capable. 319 * @return true if the audio is capable, false otherwise. 320 */ isAudioCapable()321 public boolean isAudioCapable() { 322 return mIsAudioCapable; 323 } 324 325 /** 326 * Query the video capable. 327 * @return true if the video is capable, false otherwise. 328 */ isVideoCapable()329 public boolean isVideoCapable() { 330 return mIsVideoCapable; 331 } 332 333 /** 334 * Get the supported duplex mode list. 335 * @return The list of supported duplex mode 336 */ getSupportedDuplexModes()337 public @NonNull @DuplexMode List<String> getSupportedDuplexModes() { 338 return Collections.unmodifiableList(mSupportedDuplexModeList); 339 } 340 341 /** 342 * Get the unsupported duplex mode list. 343 * @return The list of unsupported duplex mode 344 */ getUnsupportedDuplexModes()345 public @NonNull @DuplexMode List<String> getUnsupportedDuplexModes() { 346 return Collections.unmodifiableList(mUnsupportedDuplexModeList); 347 } 348 349 @Override toString()350 public String toString() { 351 return "servCaps{" + "a=" + mIsAudioCapable + ", v=" + mIsVideoCapable 352 + ", supported=" + mSupportedDuplexModeList + ", unsupported=" 353 + mUnsupportedDuplexModeList + '}'; 354 } 355 } 356 357 /** 358 * Builder to help construct {@link RcsContactPresenceTuple} instances. 359 */ 360 public static final class Builder { 361 362 private final RcsContactPresenceTuple mPresenceTuple; 363 364 /** 365 * Builds a RcsContactPresenceTuple instance. 366 * @param status The status associated with the service capability. See RFC3865 for more 367 * information. 368 * @param serviceId The OMA Presence service-id associated with this capability. See the 369 * OMA Presence SIMPLE specification v1.1, section 10.5.1. 370 * @param serviceVersion The OMA Presence version associated with the service capability. 371 * See the OMA Presence SIMPLE specification v1.1, section 10.5.1. 372 */ Builder(@onNull @asicStatus String status, @NonNull @ServiceId String serviceId, @NonNull String serviceVersion)373 public Builder(@NonNull @BasicStatus String status, @NonNull @ServiceId String serviceId, 374 @NonNull String serviceVersion) { 375 mPresenceTuple = new RcsContactPresenceTuple(status, serviceId, serviceVersion); 376 } 377 378 /** 379 * The optional SIP Contact URI associated with the PIDF tuple element if the network 380 * expects the user to use the URI instead of the contact URI to contact it. 381 */ setContactUri(@onNull Uri contactUri)382 public @NonNull Builder setContactUri(@NonNull Uri contactUri) { 383 mPresenceTuple.mContactUri = contactUri; 384 return this; 385 } 386 387 /** 388 * The optional timestamp indicating the data and time of the status change of this tuple. 389 * Per RFC3863 section 4.1.7, the timestamp is formatted as an IMPP datetime format 390 * string per RFC3339. 391 */ setTime(@onNull Instant timestamp)392 public @NonNull Builder setTime(@NonNull Instant timestamp) { 393 mPresenceTuple.mTimestamp = timestamp; 394 return this; 395 } 396 397 /** 398 * An optional parameter containing the description element of the service-description. See 399 * OMA Presence SIMPLE specification v1.1 400 */ setServiceDescription(@onNull String description)401 public @NonNull Builder setServiceDescription(@NonNull String description) { 402 mPresenceTuple.mServiceDescription = description; 403 return this; 404 } 405 406 /** 407 * An optional parameter containing the service capabilities of the presence tuple if they 408 * are present in the servcaps element. 409 */ setServiceCapabilities(@onNull ServiceCapabilities caps)410 public @NonNull Builder setServiceCapabilities(@NonNull ServiceCapabilities caps) { 411 mPresenceTuple.mServiceCapabilities = caps; 412 return this; 413 } 414 415 /** 416 * @return the constructed instance. 417 */ build()418 public @NonNull RcsContactPresenceTuple build() { 419 return mPresenceTuple; 420 } 421 } 422 423 private Uri mContactUri; 424 private Instant mTimestamp; 425 private @BasicStatus String mStatus; 426 427 // The service information in the service-description element. 428 private String mServiceId; 429 private String mServiceVersion; 430 private String mServiceDescription; 431 432 private ServiceCapabilities mServiceCapabilities; 433 RcsContactPresenceTuple(@onNull @asicStatus String status, @NonNull String serviceId, @NonNull String serviceVersion)434 private RcsContactPresenceTuple(@NonNull @BasicStatus String status, @NonNull String serviceId, 435 @NonNull String serviceVersion) { 436 mStatus = status; 437 mServiceId = serviceId; 438 mServiceVersion = serviceVersion; 439 } 440 RcsContactPresenceTuple(Parcel in)441 private RcsContactPresenceTuple(Parcel in) { 442 mContactUri = in.readParcelable(Uri.class.getClassLoader()); 443 mTimestamp = convertStringFormatTimeToInstant(in.readString()); 444 mStatus = in.readString(); 445 mServiceId = in.readString(); 446 mServiceVersion = in.readString(); 447 mServiceDescription = in.readString(); 448 mServiceCapabilities = in.readParcelable(ServiceCapabilities.class.getClassLoader()); 449 } 450 451 @Override writeToParcel(@onNull Parcel out, int flags)452 public void writeToParcel(@NonNull Parcel out, int flags) { 453 out.writeParcelable(mContactUri, flags); 454 out.writeString(convertInstantToStringFormat(mTimestamp)); 455 out.writeString(mStatus); 456 out.writeString(mServiceId); 457 out.writeString(mServiceVersion); 458 out.writeString(mServiceDescription); 459 out.writeParcelable(mServiceCapabilities, flags); 460 } 461 462 @Override describeContents()463 public int describeContents() { 464 return 0; 465 } 466 467 public static final @NonNull Creator<RcsContactPresenceTuple> CREATOR = 468 new Creator<RcsContactPresenceTuple>() { 469 @Override 470 public RcsContactPresenceTuple createFromParcel(Parcel in) { 471 return new RcsContactPresenceTuple(in); 472 } 473 474 @Override 475 public RcsContactPresenceTuple[] newArray(int size) { 476 return new RcsContactPresenceTuple[size]; 477 } 478 }; 479 480 // Convert the Instant to the string format convertInstantToStringFormat(Instant instant)481 private String convertInstantToStringFormat(Instant instant) { 482 if (instant == null) { 483 return ""; 484 } 485 return instant.toString(); 486 } 487 488 // Convert the time string format to Instant convertStringFormatTimeToInstant(String timestamp)489 private @Nullable Instant convertStringFormatTimeToInstant(String timestamp) { 490 if (TextUtils.isEmpty(timestamp)) { 491 return null; 492 } 493 try { 494 return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(timestamp, Instant::from); 495 } catch (DateTimeParseException e) { 496 return null; 497 } 498 } 499 500 /** @return the status of the tuple element. */ getStatus()501 public @NonNull @BasicStatus String getStatus() { 502 return mStatus; 503 } 504 505 /** @return the service-id element of the service-description */ getServiceId()506 public @NonNull String getServiceId() { 507 return mServiceId; 508 } 509 510 /** @return the version element of the service-description */ getServiceVersion()511 public @NonNull String getServiceVersion() { 512 return mServiceVersion; 513 } 514 515 /** @return the SIP URI contained in the contact element of the tuple if it exists. */ getContactUri()516 public @Nullable Uri getContactUri() { 517 return mContactUri; 518 } 519 520 /** @return the timestamp element contained in the tuple if it exists */ getTime()521 public @Nullable Instant getTime() { 522 return mTimestamp; 523 } 524 525 /** @return the description element contained in the service-description if it exists */ getServiceDescription()526 public @Nullable String getServiceDescription() { 527 return mServiceDescription; 528 } 529 530 /** @return the {@link ServiceCapabilities} of the tuple if it exists. */ getServiceCapabilities()531 public @Nullable ServiceCapabilities getServiceCapabilities() { 532 return mServiceCapabilities; 533 } 534 535 @Override toString()536 public String toString() { 537 StringBuilder builder = new StringBuilder("{"); 538 if (Build.IS_ENG) { 539 builder.append("u="); 540 builder.append(mContactUri); 541 } else { 542 builder.append("u="); 543 builder.append(mContactUri != null ? "XXX" : "null"); 544 } 545 builder.append(", id="); 546 builder.append(mServiceId); 547 builder.append(", v="); 548 builder.append(mServiceVersion); 549 builder.append(", s="); 550 builder.append(mStatus); 551 if (mTimestamp != null) { 552 builder.append(", timestamp="); 553 builder.append(mTimestamp); 554 } 555 if (mServiceDescription != null) { 556 builder.append(", servDesc="); 557 builder.append(mServiceDescription); 558 } 559 if (mServiceCapabilities != null) { 560 builder.append(", servCaps="); 561 builder.append(mServiceCapabilities); 562 } 563 builder.append("}"); 564 return builder.toString(); 565 } 566 } 567