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