aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/ims/internal/ConferenceParticipant.java
blob: d48ecf63b7ffb82c4194b70a2781e0d5f7c757c6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
/*
 * Copyright (C) 2019 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 com.android.ims.internal;

import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.telecom.Connection;
import android.telecom.PhoneAccount;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.PhoneConstants;
import com.android.telephony.Rlog;

/**
 * Parcelable representation of a participant's state in a conference call.
 * @hide
 */
public class ConferenceParticipant implements Parcelable {
    private static final String TAG = "ConferenceParticipant";

    /**
     * RFC5767 states that a SIP URI with an unknown number should use an address of
     * {@code anonymous@anonymous.invalid}.  E.g. the host name is anonymous.invalid.
     */
    private static final String ANONYMOUS_INVALID_HOST = "anonymous.invalid";
    /**
     * The conference participant's handle (e.g., phone number).
     */
    private final Uri mHandle;

    /**
     * The display name for the participant.
     */
    private final String mDisplayName;

    /**
     * The endpoint Uri which uniquely identifies this conference participant.  E.g. for an IMS
     * conference call, this is the endpoint URI for the participant on the IMS conference server.
     */
    private final Uri mEndpoint;

    /**
     * The state of the participant in the conference.
     *
     * @see android.telecom.Connection
     */
    private final int mState;

    /**
     * The connect time of the participant.
     */
    private long mConnectTime;

    /**
     * The connect elapsed time of the participant.
     */
    private long mConnectElapsedTime;

    /**
     * The direction of the call;
     * {@link android.telecom.Call.Details#DIRECTION_INCOMING} for incoming calls, or
     * {@link android.telecom.Call.Details#DIRECTION_OUTGOING} for outgoing calls.
     */
    private int mCallDirection;

    /**
     * Creates an instance of {@code ConferenceParticipant}.
     *
     * @param handle      The conference participant's handle (e.g., phone number).
     * @param displayName The display name for the participant.
     * @param endpoint    The enpoint Uri which uniquely identifies this conference participant.
     * @param state       The state of the participant in the conference.
     * @param callDirection The direction of the call (incoming/outgoing).
     */
    public ConferenceParticipant(Uri handle, String displayName, Uri endpoint, int state,
            int callDirection) {
        mHandle = handle;
        mDisplayName = displayName;
        mEndpoint = endpoint;
        mState = state;
        mCallDirection = callDirection;
    }

    /**
     * Responsible for creating {@code ConferenceParticipant} objects for deserialized Parcels.
     */
    public static final @android.annotation.NonNull Parcelable.Creator<ConferenceParticipant> CREATOR =
            new Parcelable.Creator<ConferenceParticipant>() {

                @Override
                public ConferenceParticipant createFromParcel(Parcel source) {
                    ClassLoader classLoader = ConferenceParticipant.class.getClassLoader();
                    Uri handle = source.readParcelable(classLoader);
                    String displayName = source.readString();
                    Uri endpoint = source.readParcelable(classLoader);
                    int state = source.readInt();
                    long connectTime = source.readLong();
                    long elapsedRealTime = source.readLong();
                    int callDirection = source.readInt();
                    ConferenceParticipant participant =
                            new ConferenceParticipant(handle, displayName, endpoint, state,
                                    callDirection);
                    participant.setConnectTime(connectTime);
                    participant.setConnectElapsedTime(elapsedRealTime);
                    return participant;
                }

                @Override
                public ConferenceParticipant[] newArray(int size) {
                    return new ConferenceParticipant[size];
                }
            };

    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * Determines the number presentation for a conference participant.  Per RFC5767, if the host
     * name contains {@code anonymous.invalid} we can assume that there is no valid caller ID
     * information for the caller, otherwise we'll assume that the URI can be shown.
     *
     * @return The number presentation.
     */
    @VisibleForTesting
    public int getParticipantPresentation() {
        Uri address = getHandle();
        if (address == null) {
            return PhoneConstants.PRESENTATION_RESTRICTED;
        }

        String number = address.getSchemeSpecificPart();
        // If no number, bail early and set restricted presentation.
        if (TextUtils.isEmpty(number)) {
            return PhoneConstants.PRESENTATION_RESTRICTED;
        }
        // Per RFC3261, the host name portion can also potentially include extra information:
        // E.g. sip:anonymous1@anonymous.invalid;legid=1
        // In this case, hostName will be anonymous.invalid and there is an extra parameter for
        // legid=1.
        // Parameters are optional, and the address (e.g. test@test.com) will always be the first
        // part, with any parameters coming afterwards.
        String [] hostParts = number.split("[;]");
        String addressPart = hostParts[0];

        // Get the number portion from the address part.
        // This will typically be formatted similar to: 6505551212@test.com
        String [] numberParts = addressPart.split("[@]");

        // If we can't parse the host name out of the URI, then there is probably other data
        // present, and is likely a valid SIP URI.
        if (numberParts.length != 2) {
            return PhoneConstants.PRESENTATION_ALLOWED;
        }
        String hostName = numberParts[1];

        // If the hostname portion of the SIP URI is the invalid host string, presentation is
        // restricted.
        if (hostName.equals(ANONYMOUS_INVALID_HOST)) {
            return PhoneConstants.PRESENTATION_RESTRICTED;
        }

        return PhoneConstants.PRESENTATION_ALLOWED;
    }

    /**
     * Writes the {@code ConferenceParticipant} to a parcel.
     *
     * @param dest The Parcel in which the object should be written.
     * @param flags Additional flags about how the object should be written.
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(mHandle, 0);
        dest.writeString(mDisplayName);
        dest.writeParcelable(mEndpoint, 0);
        dest.writeInt(mState);
        dest.writeLong(mConnectTime);
        dest.writeLong(mConnectElapsedTime);
        dest.writeInt(mCallDirection);
    }

    /**
     * Builds a string representation of this instance.
     *
     * @return String representing the conference participant.
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[ConferenceParticipant Handle: ");
        sb.append(Rlog.pii(TAG, mHandle));
        sb.append(" DisplayName: ");
        sb.append(Rlog.pii(TAG, mDisplayName));
        sb.append(" Endpoint: ");
        sb.append(Rlog.pii(TAG, mEndpoint));
        sb.append(" State: ");
        sb.append(Connection.stateToString(mState));
        sb.append(" ConnectTime: ");
        sb.append(getConnectTime());
        sb.append(" ConnectElapsedTime: ");
        sb.append(getConnectElapsedTime());
        sb.append(" Direction: ");
        sb.append(getCallDirection() == android.telecom.Call.Details.DIRECTION_INCOMING ? "Incoming" : "Outgoing");
        sb.append("]");
        return sb.toString();
    }

    /**
     * The conference participant's handle (e.g., phone number).
     */
    public Uri getHandle() {
        return mHandle;
    }

    /**
     * The display name for the participant.
     */
    public String getDisplayName() {
        return mDisplayName;
    }

    /**
     * The enpoint Uri which uniquely identifies this conference participant.  E.g. for an IMS
     * conference call, this is the endpoint URI for the participant on the IMS conference server.
     */
    public Uri getEndpoint() {
        return mEndpoint;
    }

    /**
     * The state of the participant in the conference.
     *
     * @see android.telecom.Connection
     */
    public int getState() {
        return mState;
    }

    /**
     * The connect time of the participant to the conference.
     */
    public long getConnectTime() {
        return mConnectTime;
    }

    public void setConnectTime(long connectTime) {
        this.mConnectTime = connectTime;
    }

    /**
     * The connect elapsed time of the participant to the conference.
     */
    public long getConnectElapsedTime() {
        return mConnectElapsedTime;
    }

    public void setConnectElapsedTime(long connectElapsedTime) {
        mConnectElapsedTime = connectElapsedTime;
    }

    /**
     * @return The direction of the call (incoming/outgoing):
     *         {@link android.telecom.Call.Details#DIRECTION_INCOMING} for incoming calls, or
     *         {@link android.telecom.Call.Details#DIRECTION_OUTGOING} for outgoing calls.
     */
    public int getCallDirection() {
        return mCallDirection;
    }

    /**
     * Sets the direction of the call.
     * @param callDirection Whether the call is incoming or outgoing:
     *                      {@link android.telecom.Call.Details#DIRECTION_INCOMING} for
     *                      incoming calls, or
     *                      {@link android.telecom.Call.Details#DIRECTION_OUTGOING} for
     *                      outgoing calls.
     */
    public void setCallDirection(int callDirection) {
        mCallDirection = callDirection;
    }

    /**
     * Attempts to build a tel: style URI from a conference participant.
     * Conference event package data contains SIP URIs, so we try to extract the phone number and
     * format into a typical tel: style URI.
     *
     * @param address The conference participant's address.
     * @param countryIso The country ISO of the current subscription; used when formatting the
     *                   participant phone number to E.164 format.
     * @return The participant's address URI.
     * @hide
     */
    @VisibleForTesting
    public static Uri getParticipantAddress(Uri address, String countryIso) {
        if (address == null) {
            return address;
        }
        // Even if address is already in tel: format, still parse it and rebuild.
        // This is to recognize tel URIs such as:
        // tel:6505551212;phone-context=ims.mnc012.mcc034.3gppnetwork.org

        // Conference event package participants are identified using SIP URIs (see RFC3261).
        // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers
        // Per RFC3261, the "user" can be a telephone number.
        // For example: sip:1650555121;phone-context=blah.com@host.com
        // In this case, the phone number is in the user field of the URI, and the parameters can be
        // ignored.
        //
        // A SIP URI can also specify a phone number in a format similar to:
        // sip:+1-212-555-1212@something.com;user=phone
        // In this case, the phone number is again in user field and the parameters can be ignored.
        // We can get the user field in these instances by splitting the string on the @, ;, or :
        // and looking at the first found item.
        String number = address.getSchemeSpecificPart();
        if (TextUtils.isEmpty(number)) {
            return address;
        }

        String numberParts[] = number.split("[@;:]");
        if (numberParts.length == 0) {
            return address;
        }
        number = numberParts[0];

        // Attempt to format the number in E.164 format and use that as part of the TEL URI.
        // RFC2806 recommends to format telephone numbers using E.164 since it is independent of
        // how the dialing of said numbers takes place.
        // If conversion to E.164 fails, the returned value is null.  In that case, fallback to the
        // number which was in the CEP data.
        String formattedNumber = null;
        if (!TextUtils.isEmpty(countryIso)) {
            formattedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso);
        }

        return Uri.fromParts(PhoneAccount.SCHEME_TEL,
                formattedNumber != null ? formattedNumber : number, null);
    }
}