• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 com.android.services.telephony.rcs;
18 
19 import static com.android.internal.telephony.TelephonyStatsLog.SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING;
20 
21 import static junit.framework.Assert.assertEquals;
22 import static junit.framework.Assert.assertNotNull;
23 import static junit.framework.Assert.assertTrue;
24 
25 import static org.mockito.ArgumentMatchers.any;
26 import static org.mockito.Mockito.doReturn;
27 import static org.mockito.Mockito.eq;
28 import static org.mockito.Mockito.mock;
29 import static org.mockito.Mockito.verify;
30 
31 import android.content.Context;
32 import android.net.Uri;
33 import android.os.RemoteException;
34 import android.telephony.BinderCacheManager;
35 import android.telephony.ims.ImsException;
36 import android.telephony.ims.SipDelegateManager;
37 import android.telephony.ims.SipDialogState;
38 import android.telephony.ims.SipDialogStateCallback;
39 import android.telephony.ims.SipMessage;
40 import android.telephony.ims.aidl.IImsRcsController;
41 import android.util.ArraySet;
42 import android.util.Base64;
43 
44 import androidx.test.ext.junit.runners.AndroidJUnit4;
45 
46 import com.android.TelephonyTestBase;
47 import com.android.internal.telephony.ISipDialogStateCallback;
48 import com.android.internal.telephony.ITelephony;
49 import com.android.internal.telephony.PhoneFactory;
50 import com.android.internal.telephony.metrics.RcsStats;
51 
52 import org.junit.Before;
53 import org.junit.Test;
54 import org.junit.runner.RunWith;
55 import org.mockito.ArgumentCaptor;
56 import org.mockito.Mock;
57 import org.mockito.MockitoAnnotations;
58 
59 import java.nio.ByteBuffer;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.Collections;
63 import java.util.List;
64 import java.util.Set;
65 import java.util.stream.Collectors;
66 
67 @RunWith(AndroidJUnit4.class)
68 public class SipSessionTrackerTest extends TelephonyTestBase {
69 
70     private class DialogAttributes {
71         public final String branchId;
72         public final String callId;
73         public final String fromHeader;
74         public final String fromTag;
75         public final String toUri;
76         public final String toHeader;
77         private final String mFromUri;
78         // This may be populated later.
79         public String toTag;
80 
DialogAttributes()81         DialogAttributes() {
82             branchId = getNextString();
83             callId = getNextString();
84             mFromUri = generateRandomSipUri();
85             fromHeader = generateContactUri(mFromUri);
86             fromTag = getNextString();
87             toUri = generateRandomSipUri();
88             toHeader = generateContactUri(toUri);
89         }
90 
DialogAttributes(String branchId, String callId, String fromUri, String fromTag, String toUri, String toTag)91         private DialogAttributes(String branchId, String callId, String fromUri,
92                 String fromTag, String toUri, String toTag) {
93             this.branchId = branchId;
94             this.callId = callId;
95             this.mFromUri = fromUri;
96             this.fromHeader = generateContactUri(fromUri);
97             this.fromTag = fromTag;
98             this.toUri = toUri;
99             this.toHeader = generateContactUri(toUri);
100             this.toTag = toTag;
101         }
102 
setToTag()103         public void setToTag() {
104             if (toTag == null) {
105                 toTag = getNextString();
106             }
107         }
108 
fromExisting()109         public DialogAttributes fromExisting() {
110             return new DialogAttributes(branchId, callId, mFromUri, fromTag, toUri, null);
111         }
112 
invertFromTo()113         public DialogAttributes invertFromTo() {
114             return new DialogAttributes(branchId, callId, toUri, fromTag, mFromUri, toTag);
115         }
116     }
117 
118     // Keep track of the string entry so we can generate unique strings.
119     private int mStringEntryCounter = 0;
120     private SipSessionTracker mTrackerUT;
121     private static final int TEST_SUB_ID = 1;
122     private static final String TEST_INVITE_SIP_METHOD = "INVITE";
123     private static final int TEST_SIP_RESPONSE_CODE = 200;
124     private static final int TEST_SIP_CLOSE_RESPONSE_CODE = 0;
125 
126     @Mock private RcsStats mRcsStats;
127     private boolean mUpdatedState = false;
128     private SipDialogStateCallback mCallback;
129     private SipDelegateManager mSipManager;
130     private ISipDialogStateCallback mCbBinder;
131     IImsRcsController mMockImsRcsInterface;
132     BinderCacheManager<ITelephony> mBinderCache;
133     BinderCacheManager<IImsRcsController> mRcsBinderCache;
134 
135     @Before
setUp()136     public void setUp() throws Exception {
137         super.setUp();
138 
139         mStringEntryCounter = 0;
140         MockitoAnnotations.initMocks(this);
141         mTrackerUT = new SipSessionTracker(TEST_SUB_ID, mRcsStats);
142         mMockImsRcsInterface = mock(IImsRcsController.class);
143         mBinderCache = mock(BinderCacheManager.class);
144         mRcsBinderCache = mock(BinderCacheManager.class);
145         doReturn(mMockImsRcsInterface).when(mRcsBinderCache)
146                 .listenOnBinder(any(), any(Runnable.class));
147         doReturn(mMockImsRcsInterface).when(mRcsBinderCache)
148                 .removeRunnable(any(SipDialogStateCallback.class));
149         doReturn(mMockImsRcsInterface).when(mRcsBinderCache).getBinder();
150     }
151 
152     @Test
testMetricsEndedGracefullyBye()153     public void testMetricsEndedGracefullyBye() {
154         DialogAttributes attr = new DialogAttributes();
155         // INVITE
156         SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
157         filterMessage(inviteRequest, attr);
158         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
159         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
160 
161         // confirmed dialog
162         attr.setToTag();
163         SipMessage inviteConfirm = generateSipResponse("200", "OK", attr);
164         filterMessage(inviteConfirm, attr);
165         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
166         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
167 
168         // Gracefully Ended
169         SipMessage inviteClose = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr);
170         filterMessage(inviteClose, attr);
171 
172         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
173         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
174         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr);
175 
176         // verify Metrics information
177         verify(mRcsStats).onSipTransportSessionClosed(eq(TEST_SUB_ID), eq(attr.callId),
178                 eq(TEST_SIP_CLOSE_RESPONSE_CODE), eq(true));
179     }
180 
181     @Test
testMetricsCloseCleanupSession()182     public void testMetricsCloseCleanupSession() {
183         //mTrackerUT.setRcsStats(mRcsStats);
184         DialogAttributes attr = new DialogAttributes();
185         // INVITE A -> B
186         SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
187         filterMessage(inviteRequest, attr);
188         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
189         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
190 
191         // confirmed dialog
192         attr.setToTag();
193         SipMessage inviteConfirm = generateSipResponse("200", "OK", attr);
194         filterMessage(inviteConfirm, attr);
195         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
196         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
197 
198         //forcefully close session
199         mTrackerUT.cleanupSession(attr.callId);
200         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
201         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
202         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
203 
204         // verify Metrics information
205         verify(mRcsStats).onSipTransportSessionClosed(eq(TEST_SUB_ID), eq(attr.callId),
206                 eq(TEST_SIP_CLOSE_RESPONSE_CODE), eq(false));
207     }
208 
209     @Test
testMetricsCloseClearAllSessions()210     public void testMetricsCloseClearAllSessions() {
211         //mTrackerUT.setRcsStats(mRcsStats);
212         DialogAttributes attr = new DialogAttributes();
213 
214         // INVITE
215         SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
216         filterMessage(inviteRequest, attr);
217         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
218         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
219 
220         // confirmed dialog
221         attr.setToTag();
222         SipMessage inviteConfirm = generateSipResponse("200", "OK", attr);
223         filterMessage(inviteConfirm, attr);
224         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
225         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
226 
227         //forcefully close session
228         mTrackerUT.clearAllSessions();
229         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
230         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
231         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
232 
233         // verify Metrics information
234         verify(mRcsStats).onSipTransportSessionClosed(eq(TEST_SUB_ID), eq(attr.callId),
235                 eq(TEST_SIP_CLOSE_RESPONSE_CODE), eq(false));
236     }
237 
238     @Test
testEarlyDialogToConfirmed()239     public void testEarlyDialogToConfirmed() {
240         DialogAttributes attr = new DialogAttributes();
241         // INVITE A -> B
242         SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
243         filterMessage(inviteRequest, attr);
244         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
245         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
246         // 100 TRYING A <- proxy
247         SipMessage inviteTrying = generateSipResponse("100", "Trying", attr);
248         filterMessage(inviteTrying, attr);
249         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
250         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
251         // INVITE proxy -> B
252         // (BOB generates To tag)
253         attr.setToTag();
254         // 180 RINGING proxy <- B
255         // 180 RINGING A <- proxy
256         SipMessage inviteRinging = generateSipResponse("180", "Ringing", attr);
257         filterMessage(inviteRinging, attr);
258         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
259         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
260         // User answers phone
261         // 200 OK proxy <- B
262         // 200 OK A <- proxy
263         SipMessage inviteConfirm = generateSipResponse("200", "OK", attr);
264         filterMessage(inviteConfirm, attr);
265         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
266         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
267     }
268 
269     @Test
testForkDialog()270     public void testForkDialog() {
271         DialogAttributes attrB1 = new DialogAttributes();
272         // INVITE A -> B
273         SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attrB1);
274         filterMessage(inviteRequest, attrB1);
275         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
276         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB1);
277         // INVITE proxy -> B
278         // (BOB generates To tag)
279         attrB1.setToTag();
280         // 180 RINGING proxy <- B1
281         // 180 RINGING A <- proxy
282         SipMessage inviteRingingB1 = generateSipResponse("180", "Ringing", attrB1);
283         filterMessage(inviteRingingB1, attrB1);
284         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
285         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB1);
286         // Now get another RINGING indication from another device associated with the same user.
287         // 180 RINGING proxy <- B2
288         // 180 RINGING A <- proxy
289         DialogAttributes attrB2 = attrB1.fromExisting();
290         // set different To tag
291         attrB2.setToTag();
292         SipMessage inviteRingingB2 = generateSipResponse("180", "Ringing", attrB2);
293         filterMessage(inviteRingingB2, attrB2);
294         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
295         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB1, attrB2);
296         // User answers B1
297         // 200 OK proxy <- B1
298         // 200 OK A <- proxy
299         SipMessage inviteConfirm = generateSipResponse("200", "OK", attrB1);
300         filterMessage(inviteConfirm, attrB1);
301         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB2);
302         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attrB1);
303         // Receive indication that B2 is terminated because user answered on B1
304         // 487 A <- proxy
305         SipMessage terminatedResponse = generateSipResponse("487",
306                 "Request Terminated", attrB2);
307         filterMessage(terminatedResponse, attrB2);
308         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
309         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attrB1);
310         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attrB2);
311         SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attrB1);
312         // Send BYE request for the open dialog.
313         filterMessage(byeRequest, attrB1);
314         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
315         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
316         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attrB1, attrB2);
317         // Clean up the session and ensure the close dialog is completely removed from the tracker.
318         mTrackerUT.cleanupSession(attrB1.callId);
319         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
320         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
321         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
322     }
323 
324     @Test
testCloseLocalDialog()325     public void testCloseLocalDialog() {
326         DialogAttributes attr = new DialogAttributes();
327         attr.setToTag();
328         createConfirmedDialog(attr);
329         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
330         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
331 
332         // Send BYE request for a dialog that was started locally and ensure that we see the call id
333         // move to the closed list.
334         SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr);
335         filterMessage(byeRequest, attr);
336         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
337         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
338         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr);
339         // Clean up the session and ensure the close dialog is completely removed from the tracker.
340         mTrackerUT.cleanupSession(attr.callId);
341         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
342         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
343         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
344     }
345 
346     @Test
testAcceptContactFts()347     public void testAcceptContactFts() {
348         DialogAttributes attr = new DialogAttributes();
349         attr.setToTag();
350         SipMessage inviteRequest = generateSipRequest(
351                 SipMessageUtils.INVITE_SIP_METHOD,
352                 attr);
353         // add accept contact header
354         inviteRequest = new SipMessage(inviteRequest.getStartLine(),
355                 inviteRequest.getHeaderSection() + "\nAccept-Contact:*;+test",
356                 new byte[0]);
357         filterMessage(inviteRequest, attr);
358         assertTrue(mTrackerUT.getCallIdsAssociatedWithFeatureTag(Collections.singleton("+test"))
359                 .contains(attr.callId));
360     }
361 
362     @Test
testCloseRemoteDialog()363     public void testCloseRemoteDialog() {
364         DialogAttributes remoteAttr = new DialogAttributes();
365         remoteAttr.setToTag();
366         createConfirmedDialog(remoteAttr);
367         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
368         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), remoteAttr);
369 
370         // Send BYE request on a dialog that was started from the remote party.
371         DialogAttributes localAttr = remoteAttr.invertFromTo();
372         SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, localAttr);
373         filterMessage(byeRequest, localAttr);
374         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
375         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
376         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), remoteAttr);
377         // Clean up the session and ensure the dialog is completely removed from the tracker.
378         mTrackerUT.cleanupSession(remoteAttr.callId);
379         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
380         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
381         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
382     }
383 
384     @Test
testCleanupConfirmedDialog()385     public void testCleanupConfirmedDialog() {
386         DialogAttributes attr = new DialogAttributes();
387         attr.setToTag();
388         createConfirmedDialog(attr);
389         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
390         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
391         // Clean up the session and ensure the dialog is completely removed from the tracker.
392         mTrackerUT.cleanupSession(attr.callId);
393         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
394         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
395         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
396     }
397 
398     @Test
testMultipleDialogs()399     public void testMultipleDialogs() {
400         DialogAttributes attr1 = new DialogAttributes();
401         createConfirmedDialog(attr1);
402         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
403         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1);
404         // add a second dialog
405         DialogAttributes attr2 = new DialogAttributes();
406         createConfirmedDialog(attr2);
407         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
408         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1, attr2);
409         // Send BYE request on dialogs
410         SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr1);
411         filterMessage(byeRequest, attr1);
412         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
413         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr2);
414         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr1);
415         mTrackerUT.cleanupSession(attr1.callId);
416         // Send BYE request on dialogs
417         byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr2);
418         filterMessage(byeRequest, attr2);
419         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
420         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
421         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr2);
422         mTrackerUT.cleanupSession(attr2.callId);
423         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
424         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
425         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
426     }
427 
428     @Test
testAcknowledgeMessageFailed()429     public void testAcknowledgeMessageFailed() {
430         DialogAttributes attr = new DialogAttributes();
431         SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
432         mTrackerUT.filterSipMessage(
433                 SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, inviteRequest);
434         // Do not acknowledge the request and ensure that the operation has not been applied yet.
435         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
436         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
437         // send message ack failed event, the operation shouldn't have been applied
438         mTrackerUT.pendingMessageFailed(attr.branchId);
439         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
440         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
441     }
442 
443     @Test
testAcknowledgeBatchEvents()444     public void testAcknowledgeBatchEvents() {
445         DialogAttributes attr = new DialogAttributes();
446         SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
447         attr.setToTag();
448         SipMessage inviteConfirm = generateSipResponse("200", "OK", attr);
449         // We unexpectedly received two filter requests for the same branchId without
450         // acknowledgePendingMessage being called in between. Ensure that when it is called, it
451         // applies both operations.
452         mTrackerUT.filterSipMessage(
453                 SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, inviteRequest);
454         mTrackerUT.filterSipMessage(
455                 SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, inviteConfirm);
456         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
457         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
458         // we should skip right to confirmed as both operations run back-to-back
459         mTrackerUT.acknowledgePendingMessage(attr.branchId);
460         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
461         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
462     }
463 
464     @Test
testActiveDialogsChanged()465     public void testActiveDialogsChanged() throws ImsException {
466         sipDialogStateCallback();
467 
468         // first dialog
469         DialogAttributes attr1 = new DialogAttributes();
470         createConfirmedDialog(attr1);
471         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
472         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1);
473 
474         verifyConfirmedStates(true);
475 
476         // add a second dialog
477         DialogAttributes attr2 = new DialogAttributes();
478         createConfirmedDialog(attr2);
479         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
480         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1, attr2);
481         verifyConfirmedStates(true);
482 
483         // Send BYE request on first dialog
484         SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr1);
485         filterMessage(byeRequest, attr1);
486         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
487         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr2);
488         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr1);
489         mTrackerUT.cleanupSession(attr1.callId);
490         verifyConfirmedStates(true);
491 
492         // Send BYE request on second dialog
493         byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr2);
494         filterMessage(byeRequest, attr2);
495         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
496         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
497         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr2);
498         mTrackerUT.cleanupSession(attr2.callId);
499         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
500         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
501         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
502         verifyConfirmedStates(false);
503         unRegisterCallback();
504     }
505 
506     @Test
testActiveSipDialogsChangedClearAll()507     public void testActiveSipDialogsChangedClearAll() throws ImsException {
508         sipDialogStateCallback();
509 
510         // first dialog
511         DialogAttributes attr1 = new DialogAttributes();
512         createConfirmedDialog(attr1);
513         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
514         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1);
515         verifyConfirmedStates(true);
516 
517         // add a second dialog
518         DialogAttributes attr2 = new DialogAttributes();
519         createConfirmedDialog(attr2);
520         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
521         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1, attr2);
522         verifyConfirmedStates(true);
523 
524         // cleanAllSessions
525         mTrackerUT.clearAllSessions();
526         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
527         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
528         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
529         verifyConfirmedStates(false);
530         unRegisterCallback();
531     }
532 
sipDialogStateCallback()533     private void sipDialogStateCallback() throws ImsException {
534         mCallback = new SipDialogStateCallback() {
535             @Override
536             public void onActiveSipDialogsChanged(List<SipDialogState> dialogs) {
537                 mUpdatedState = isSipDialogActiveState(dialogs);
538             }
539 
540             @Override
541             public void onError() { }
542         };
543         registerCallback();
544     }
545 
verifyConfirmedStates(boolean currentState)546     private void verifyConfirmedStates(boolean currentState) {
547         List<SipDialogState> dialogStates = new ArrayList<>();
548         for (SipDialog d : (ArraySet<SipDialog>) mTrackerUT.getTrackedDialogs()) {
549             SipDialogState dialog = new SipDialogState.Builder(d.getState()).build();
550             dialogStates.add(dialog);
551         }
552         try {
553             mCbBinder.onActiveSipDialogsChanged(dialogStates);
554         } catch (RemoteException e) {
555             //onActiveSipDialogsChanged error
556         }
557         assertEquals(currentState, mUpdatedState);
558     }
559 
registerCallback()560     private void registerCallback() throws ImsException {
561         // Capture the Runnable that was registered.
562         ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
563         // Capture the ISipDialogStateCallback that was registered.
564         ArgumentCaptor<ISipDialogStateCallback> callbackCaptor =
565                 ArgumentCaptor.forClass(ISipDialogStateCallback.class);
566 
567         Context context = PhoneFactory.getDefaultPhone().getContext();
568         mSipManager = new SipDelegateManager(context,
569                         TEST_SUB_ID, mRcsBinderCache, mBinderCache);
570 
571         mSipManager.registerSipDialogStateCallback(Runnable::run, mCallback);
572 
573         verify(mRcsBinderCache).listenOnBinder(any(), runnableCaptor.capture());
574         try {
575             verify(mMockImsRcsInterface).registerSipDialogStateCallback(
576                     eq(TEST_SUB_ID), callbackCaptor.capture());
577         } catch (RemoteException e) {
578             //registerSipDialogStateCallback error
579         }
580         mCbBinder = callbackCaptor.getValue();
581     }
582 
unRegisterCallback()583     private void unRegisterCallback() {
584         try {
585             mSipManager.unregisterSipDialogStateCallback(mCallback);
586         } catch (ImsException e) {
587             //unregisterSipDialogStateCallback error
588         }
589     }
590 
isSipDialogActiveState(List<SipDialogState> dialogs)591     private boolean isSipDialogActiveState(List<SipDialogState> dialogs) {
592         int confirmedSize = dialogs.stream().filter(
593                 d -> d.getState() == SipDialogState.STATE_CONFIRMED)
594                 .collect(Collectors.toSet()).size();
595         if (confirmedSize > 0) {
596             return true;
597         }
598         return false;
599     }
600 
filterMessage(SipMessage m, DialogAttributes attr)601     private void filterMessage(SipMessage m, DialogAttributes attr) {
602         mTrackerUT.filterSipMessage(
603                 SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, m);
604         mTrackerUT.acknowledgePendingMessage(attr.branchId);
605     }
verifyContainsCallIds(Set<SipDialog> callIdSet, DialogAttributes... attrs)606     private void verifyContainsCallIds(Set<SipDialog> callIdSet, DialogAttributes... attrs) {
607         Set<String> callIds = Arrays.stream(attrs).map(a -> a.callId).collect(
608                 Collectors.toSet());
609         assertTrue(callIdSet.stream().map(SipDialog::getCallId).collect(Collectors.toSet())
610                 .containsAll(callIds));
611     }
612 
generateSipRequest(String requestMethod, DialogAttributes attr)613     private SipMessage generateSipRequest(String requestMethod,
614             DialogAttributes attr) {
615         return SipMessageUtils.generateSipRequest(requestMethod, attr.fromHeader, attr.toHeader,
616                 attr.toUri, attr.branchId, attr.callId, attr.fromTag, attr.toTag);
617     }
generateSipResponse(String statusCode, String statusString, DialogAttributes attr)618     private SipMessage generateSipResponse(String statusCode, String statusString,
619             DialogAttributes attr) {
620         return SipMessageUtils.generateSipResponse(statusCode, statusString, attr.fromHeader,
621                 attr.toHeader, attr.branchId, attr.callId, attr.fromTag, attr.toTag);
622     }
623 
generateContactUri(String sipUri)624     private String generateContactUri(String sipUri) {
625         Uri uri = Uri.parse(sipUri);
626         assertNotNull(uri);
627         String[] user = uri.getSchemeSpecificPart().split("@", 2);
628         assertNotNull(user);
629         assertEquals(2, user.length);
630         return user[0] + " <" + sipUri + ">";
631     }
632 
generateRandomSipUri()633     private String generateRandomSipUri() {
634         return "sip:" + getNextString() + "@" + SipMessageUtils.BASE_ADDRESS;
635     }
636 
createConfirmedDialog(DialogAttributes attr)637     private void createConfirmedDialog(DialogAttributes attr) {
638         // INVITE ALICE -> BOB
639         SipMessage inviteRequest = generateSipRequest(
640                 SipMessageUtils.INVITE_SIP_METHOD,
641                 attr);
642         filterMessage(inviteRequest, attr);
643         attr.setToTag();
644         // skip to confirmed state for test.
645         SipMessage inviteConfirm = generateSipResponse("200", "OK",
646                 attr);
647         filterMessage(inviteConfirm, attr);
648     }
649 
getNextString()650     private String getNextString() {
651         // Get a string representation of the entry counter
652         byte[] idByteArray = ByteBuffer.allocate(4).putInt(mStringEntryCounter++).array();
653         return Base64.encodeToString(idByteArray,
654                 Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE);
655     }
656 }
657