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