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