xref: /aosp_15_r20/cts/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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 android.telephony.ims.cts;
18 
19 import static org.junit.Assert.fail;
20 
21 import android.os.Bundle;
22 import android.os.DeadObjectException;
23 import android.os.Handler;
24 import android.os.HandlerThread;
25 import android.os.Looper;
26 import android.os.Message;
27 import android.telephony.ims.ImsCallProfile;
28 import android.telephony.ims.ImsCallSessionListener;
29 import android.telephony.ims.ImsConferenceState;
30 import android.telephony.ims.ImsReasonInfo;
31 import android.telephony.ims.ImsStreamMediaProfile;
32 import android.telephony.ims.stub.ImsCallSessionImplBase;
33 import android.util.Log;
34 
35 import java.util.concurrent.Executor;
36 
37 public class TestImsCallSessionImpl extends ImsCallSessionImplBase {
38 
39     private static final String LOG_TAG = "CtsTestImsCallSessionImpl";
40 
41     // The timeout to wait in current state in milliseconds
42     protected static final int WAIT_IN_CURRENT_STATE = 200;
43 
44     private final String mCallId = String.valueOf(this.hashCode());
45     private final Object mLock = new Object();
46 
47     private int mState = ImsCallSessionImplBase.State.IDLE;
48     private ImsCallProfile mCallProfile;
49     private ImsCallProfile mLocalCallProfile;
50     private ImsCallSessionListener mListener;
51 
52     private final MessageExecutor mCallExecutor = new MessageExecutor("CallExecutor");
53     private final MessageExecutor mCallBackExecutor = new MessageExecutor("CallBackExecutor");
54 
55     public static final int TEST_TYPE_NONE = 0;
56     public static final int TEST_TYPE_MO_ANSWER = 1 << 0;
57     public static final int TEST_TYPE_MO_FAILED = 1 << 1;
58     public static final int TEST_TYPE_HOLD_FAILED = 1 << 2;
59     public static final int TEST_TYPE_RESUME_FAILED = 1 << 3;
60     public static final int TEST_TYPE_CONFERENCE_FAILED = 1 << 4;
61     public static final int TEST_TYPE_HOLD_NO_RESPONSE = 1 << 5;
62     public static final int TEST_TYPE_CONFERENCE_FAILED_REMOTE_TERMINATED = 1 << 6;
63     public static final int TEST_TYPE_JOIN_EXIST_CONFERENCE = 1 << 7;
64     public static final int TEST_TYPE_JOIN_EXIST_CONFERENCE_AFTER_SWAP = 1 << 8;
65     public static final int TEST_TYPE_JOIN_EXIST_CONFERENCE_FAILED_AFTER_SWAP = 1 << 9;
66     public static final int TEST_TYPE_TRANSFERRED = 1 << 10;
67     public static final int TEST_TYPE_TRANSFER_FAILED = 1 << 11;
68     private int mTestType = TEST_TYPE_NONE;
69     private boolean mIsOnHold = false;
70     private boolean mIsTransferResultNotified = false;
71     private int[] mAnbrValues = new int[3];
72 
73     private TestImsCallSessionImpl mConfSession = null;
74     private ImsCallProfile mConfCallProfile = null;
75     private ConferenceHelper mConferenceHelper = null;
76     private String mCallee = null;
77 
TestImsCallSessionImpl(ImsCallProfile profile)78     public TestImsCallSessionImpl(ImsCallProfile profile) {
79         mCallProfile = profile;
80     }
81 
82     @Override
getCallId()83     public String getCallId() {
84         return mCallId;
85     }
86 
87     @Override
getCallProfile()88     public ImsCallProfile getCallProfile() {
89         return mCallProfile;
90     }
91 
92     @Override
getLocalCallProfile()93     public ImsCallProfile getLocalCallProfile() {
94         return mLocalCallProfile;
95     }
96 
97     @Override
getState()98     public int getState() {
99         return mState;
100     }
101 
102     @Override
isInCall()103     public boolean isInCall() {
104         return (mState == ImsCallSessionImplBase.State.ESTABLISHED) ? true : false;
105     }
106 
107     @Override
setListener(ImsCallSessionListener listener)108     public void setListener(ImsCallSessionListener listener) {
109         mListener = listener;
110     }
111 
112     @Override
isMultiparty()113     public boolean isMultiparty() {
114         boolean isMultiparty = (mCallProfile != null)
115                 ? mCallProfile.getCallExtraBoolean(ImsCallProfile.EXTRA_CONFERENCE) : false;
116         return isMultiparty;
117     }
118 
119     @Override
start(String callee, ImsCallProfile profile)120     public void start(String callee, ImsCallProfile profile) {
121         mCallee = callee;
122         mLocalCallProfile = profile;
123         int state = getState();
124 
125         if ((state != ImsCallSessionImplBase.State.IDLE)
126                 && (state != ImsCallSessionImplBase.State.INITIATED)) {
127             Log.d(LOG_TAG, "start :: Illegal state; callId = " + getCallId()
128                     + ", state=" + getState());
129         }
130 
131         mCallExecutor.execute(() -> {
132             ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE);
133 
134             if (isTestType(TEST_TYPE_MO_FAILED)) {
135                 startFailed();
136             } else {
137                 startInternal();
138             }
139         });
140     }
141 
startInternal()142     void startInternal() {
143         postAndRunTask(() -> {
144             try {
145                 if (mListener == null) {
146                     return;
147                 }
148                 Log.d(LOG_TAG, "invokeInitiating mCallId = " + mCallId);
149                 mListener.callSessionInitiating(mCallProfile);
150             } catch (Throwable t) {
151                 Throwable cause = t.getCause();
152                 if (t instanceof DeadObjectException
153                         || (cause != null && cause instanceof DeadObjectException)) {
154                     fail("starting cause Throwable to be thrown: " + t);
155                 }
156             }
157         });
158         setState(ImsCallSessionImplBase.State.INITIATED);
159 
160         ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
161                 ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
162                 ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE,
163                 ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
164                 ImsStreamMediaProfile.DIRECTION_INVALID,
165                 ImsStreamMediaProfile.RTT_MODE_DISABLED);
166 
167         ImsCallProfile profile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
168                 ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
169         mCallProfile.updateMediaProfile(profile);
170 
171         postAndRunTask(() -> {
172             try {
173                 if (mListener == null) {
174                     return;
175                 }
176                 Log.d(LOG_TAG, "invokeProgressing mCallId = " + mCallId);
177                 mListener.callSessionProgressing(mCallProfile.getMediaProfile());
178             } catch (Throwable t) {
179                 Throwable cause = t.getCause();
180                 if (t instanceof DeadObjectException
181                         || (cause != null && cause instanceof DeadObjectException)) {
182                     fail("starting cause Throwable to be thrown: " + t);
183                 }
184             }
185         });
186         setState(ImsCallSessionImplBase.State.ESTABLISHING);
187 
188         postAndRunTask(() -> {
189             ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE);
190             try {
191                 if (mListener == null) {
192                     return;
193                 }
194                 Log.d(LOG_TAG, "invokeStarted mCallId = " + mCallId);
195                 mListener.callSessionInitiated(mCallProfile);
196                 setState(ImsCallSessionImplBase.State.ESTABLISHED);
197             } catch (Throwable t) {
198                 Throwable cause = t.getCause();
199                 if (t instanceof DeadObjectException
200                         || (cause != null && cause instanceof DeadObjectException)) {
201                     fail("starting cause Throwable to be thrown: " + t);
202                 }
203             }
204         });
205     }
206 
startFailed()207     void startFailed() {
208         postAndRunTask(() -> {
209             ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE);
210             try {
211                 if (mListener == null) {
212                     return;
213                 }
214                 Log.d(LOG_TAG, "invokestartFailed mCallId = " + mCallId);
215                 mListener.callSessionInitiatingFailed(getReasonInfo(
216                         ImsReasonInfo.CODE_LOCAL_INTERNAL_ERROR, ImsReasonInfo.CODE_UNSPECIFIED));
217             } catch (Throwable t) {
218                 Throwable cause = t.getCause();
219                 if (t instanceof DeadObjectException
220                         || (cause != null && cause instanceof DeadObjectException)) {
221                     fail("starting cause Throwable to be thrown: " + t);
222                 }
223             }
224         });
225         setState(ImsCallSessionImplBase.State.TERMINATED);
226     }
227 
228     @Override
accept(int callType, ImsStreamMediaProfile profile)229     public void accept(int callType, ImsStreamMediaProfile profile) {
230         Log.i(LOG_TAG, "Accept Call");
231         postAndRunTask(() -> {
232             try {
233                 if (mListener == null) {
234                     return;
235                 }
236                 Log.d(LOG_TAG, "invokeStarted mCallId = " + mCallId);
237                 mListener.callSessionInitiated(mCallProfile);
238             } catch (Throwable t) {
239                 Throwable cause = t.getCause();
240                 if (t instanceof DeadObjectException
241                         || (cause != null && cause instanceof DeadObjectException)) {
242                     fail("starting cause Throwable to be thrown: " + t);
243                 }
244             }
245         });
246         setState(ImsCallSessionImplBase.State.ESTABLISHED);
247     }
248 
249     @Override
reject(int reason)250     public void reject(int reason) {
251         postAndRunTask(() -> {
252             try {
253                 if (mListener == null) {
254                     return;
255                 }
256                 Log.d(LOG_TAG, "invokeTerminated mCallId = " + mCallId);
257                 mListener.callSessionTerminated(getReasonInfo(
258                         ImsReasonInfo.CODE_LOCAL_CALL_DECLINE, ImsReasonInfo.CODE_UNSPECIFIED));
259             } catch (Throwable t) {
260                 Throwable cause = t.getCause();
261                 if (t instanceof DeadObjectException
262                         || (cause != null && cause instanceof DeadObjectException)) {
263                     fail("starting cause Throwable to be thrown: " + t);
264                 }
265             }
266         });
267         setState(ImsCallSessionImplBase.State.TERMINATED);
268     }
269 
270     @Override
update(int callType, ImsStreamMediaProfile mediaProfile)271     public void update(int callType, ImsStreamMediaProfile mediaProfile) {
272         ImsCallProfile callProfile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
273                 callType, new Bundle(), mediaProfile);
274         mCallProfile.updateMediaProfile(callProfile);
275 
276         postAndRunTask(() -> {
277             try {
278                 if (mListener == null) {
279                     return;
280                 }
281                 Log.d(LOG_TAG, "callSessionUpdated mCallId = " + mCallId);
282                 mListener.callSessionUpdated(callProfile);
283                 setState(ImsCallSessionImplBase.State.ESTABLISHED);
284             } catch (Throwable t) {
285                 Throwable cause = t.getCause();
286                 if (t instanceof DeadObjectException
287                         || (cause != null && cause instanceof DeadObjectException)) {
288                     fail("update cause Throwable to be thrown: " + t);
289                 }
290             }
291         });
292     }
293 
294     @Override
terminate(int reason)295     public void terminate(int reason) {
296         postAndRunTask(() -> {
297             try {
298                 if (mListener == null) {
299                     return;
300                 }
301                 Log.d(LOG_TAG, "invokeTerminated mCallId = " + mCallId);
302                 mListener.callSessionTerminated(getReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED,
303                         ImsReasonInfo.CODE_UNSPECIFIED));
304             } catch (Throwable t) {
305                 Throwable cause = t.getCause();
306                 if (t instanceof DeadObjectException
307                         || (cause != null && cause instanceof DeadObjectException)) {
308                     fail("starting cause Throwable to be thrown: " + t);
309                 }
310             }
311         });
312         setState(ImsCallSessionImplBase.State.TERMINATED);
313     }
314 
315     // End the Incoming Call
terminateIncomingCall()316     public void terminateIncomingCall() {
317         postAndRunTask(() -> {
318             ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE);
319             try {
320                 if (mListener == null) {
321                     return;
322                 }
323                 Log.d(LOG_TAG, "invokeTerminated mCallId = " + mCallId);
324                 mListener.callSessionTerminated(getReasonInfo(
325                         ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE,
326                         ImsReasonInfo.CODE_UNSPECIFIED));
327             } catch (Throwable t) {
328                 Throwable cause = t.getCause();
329                 if (t instanceof DeadObjectException
330                         || (cause != null && cause instanceof DeadObjectException)) {
331                     fail("starting cause Throwable to be thrown: " + t);
332                 }
333             }
334         });
335         setState(ImsCallSessionImplBase.State.TERMINATED);
336     }
337 
338     @Override
transfer(ImsCallSessionImplBase otherSession)339     public void transfer(ImsCallSessionImplBase otherSession) {
340         if (isTestType(TEST_TYPE_TRANSFER_FAILED)) {
341             transferFailed();
342         } else if (isTestType(TEST_TYPE_TRANSFERRED)) {
343             postAndRunTask(() -> {
344                 try {
345                     if (mListener == null) {
346                         return;
347                     }
348                     Log.d(LOG_TAG, "invokeTransferred");
349                     mListener.callSessionTransferred();
350                     mIsTransferResultNotified = true;
351                 } catch (Throwable t) {
352                     Throwable cause = t.getCause();
353                     if (t instanceof DeadObjectException
354                             || (cause != null && cause instanceof DeadObjectException)) {
355                         fail("starting cause Throwable to be thrown: " + t);
356                     }
357                 }
358             });
359         } else {
360             fail("unknown transfer test type");
361         }
362     }
363 
transferFailed()364     private void transferFailed() {
365         postAndRunTask(() -> {
366             try {
367                 if (mListener == null) {
368                     return;
369                 }
370                 Log.d(LOG_TAG, "invokeTransferFailed");
371                 mListener.callSessionTransferFailed(getReasonInfo(
372                         ImsReasonInfo.CODE_LOCAL_INTERNAL_ERROR, ImsReasonInfo.CODE_UNSPECIFIED));
373                 mIsTransferResultNotified = true;
374             } catch (Throwable t) {
375                 Throwable cause = t.getCause();
376                 if (t instanceof DeadObjectException
377                         || (cause != null && cause instanceof DeadObjectException)) {
378                     fail("starting cause Throwable to be thrown: " + t);
379                 }
380             }
381         });
382     }
383 
384     @Override
hold(ImsStreamMediaProfile profile)385     public void hold(ImsStreamMediaProfile profile) {
386         if (isTestType(TEST_TYPE_HOLD_FAILED)) {
387             holdFailed(profile);
388         } else {
389             int audioDirection = profile.getAudioDirection();
390             if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND) {
391                 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
392                         ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
393                         ImsStreamMediaProfile.DIRECTION_RECEIVE,
394                         ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
395                         ImsStreamMediaProfile.DIRECTION_INVALID,
396                         ImsStreamMediaProfile.RTT_MODE_DISABLED);
397                 ImsCallProfile mprofile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
398                         ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
399                 mCallProfile.updateMediaProfile(mprofile);
400             }
401             setState(ImsCallSessionImplBase.State.RENEGOTIATING);
402 
403             if (!isTestType(TEST_TYPE_HOLD_NO_RESPONSE)) sendHoldResponse();
404         }
405     }
406 
407     @Override
resume(ImsStreamMediaProfile profile)408     public void resume(ImsStreamMediaProfile profile) {
409         if (isTestType(TEST_TYPE_RESUME_FAILED)) {
410             resumeFailed(profile);
411         } else {
412             int audioDirection = profile.getAudioDirection();
413             if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE) {
414                 ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
415                         ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
416                         ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE,
417                         ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
418                         ImsStreamMediaProfile.DIRECTION_INVALID,
419                         ImsStreamMediaProfile.RTT_MODE_DISABLED);
420                 ImsCallProfile mprofile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
421                         ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
422                 mCallProfile.updateMediaProfile(mprofile);
423             }
424             setState(ImsCallSessionImplBase.State.RENEGOTIATING);
425 
426             postAndRunTask(() -> {
427                 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE);
428                 try {
429                     if (mListener == null) {
430                         return;
431                     }
432                     Log.d(LOG_TAG, "invokeResume mCallId = " + mCallId);
433                     mListener.callSessionResumed(mCallProfile);
434                     mIsOnHold = false;
435                 } catch (Throwable t) {
436                     Throwable cause = t.getCause();
437                     if (t instanceof DeadObjectException
438                             || (cause != null && cause instanceof DeadObjectException)) {
439                         fail("starting cause Throwable to be thrown: " + t);
440                     }
441                 }
442             });
443             setState(ImsCallSessionImplBase.State.ESTABLISHED);
444         }
445     }
446 
holdFailed(ImsStreamMediaProfile profile)447     private void holdFailed(ImsStreamMediaProfile profile) {
448         int audioDirection = profile.getAudioDirection();
449         if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND) {
450             postAndRunTask(() -> {
451                 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE);
452                 try {
453                     if (mListener == null) {
454                         return;
455                     }
456                     Log.d(LOG_TAG, "invokeHoldFailed mCallId = " + mCallId);
457                     mListener.callSessionHoldFailed(getReasonInfo(ImsReasonInfo
458                             .CODE_SESSION_MODIFICATION_FAILED, ImsReasonInfo.CODE_UNSPECIFIED));
459                 } catch (Throwable t) {
460                     Throwable cause = t.getCause();
461                     if (t instanceof DeadObjectException
462                             || (cause != null && cause instanceof DeadObjectException)) {
463                         fail("starting cause Throwable to be thrown: " + t);
464                     }
465                 }
466             });
467             setState(ImsCallSessionImplBase.State.ESTABLISHED);
468         }
469     }
470 
resumeFailed(ImsStreamMediaProfile profile)471     private void resumeFailed(ImsStreamMediaProfile profile) {
472         int audioDirection = profile.getAudioDirection();
473         if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE) {
474             postAndRunTask(() -> {
475                 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE);
476                 try {
477                     if (mListener == null) {
478                         return;
479                     }
480                     Log.d(LOG_TAG, "invokeResumeFailed mCallId = " + mCallId);
481                     mListener.callSessionResumeFailed(getReasonInfo(ImsReasonInfo
482                             .CODE_SESSION_MODIFICATION_FAILED, ImsReasonInfo.CODE_UNSPECIFIED));
483                 } catch (Throwable t) {
484                     Throwable cause = t.getCause();
485                     if (t instanceof DeadObjectException
486                             || (cause != null && cause instanceof DeadObjectException)) {
487                         fail("starting cause Throwable to be thrown: " + t);
488                     }
489                 }
490             });
491             setState(ImsCallSessionImplBase.State.ESTABLISHED);
492         }
493     }
494 
495     @Override
merge()496     public void merge() {
497         if (isTestType(TEST_TYPE_CONFERENCE_FAILED)
498                 || isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE_FAILED_AFTER_SWAP)
499                 || isTestType(TEST_TYPE_CONFERENCE_FAILED_REMOTE_TERMINATED)) {
500             mergeFailed();
501         } else if (isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE)
502                 || isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE_AFTER_SWAP)) {
503             mergeExistConference();
504         } else {
505             createConferenceSession();
506             mConfSession.setState(ImsCallSessionImplBase.State.ESTABLISHED);
507 
508             postAndRunTask(() -> {
509                 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE);
510                 try {
511                     if (mListener == null) {
512                         return;
513                     }
514                     mConferenceHelper.getBackGroundSession().invokeSessionTerminated();
515                     Log.d(LOG_TAG, "invokeCallSessionMergeComplete");
516                     mListener.callSessionMergeComplete(mConfSession);
517                 } catch (Throwable t) {
518                     Throwable cause = t.getCause();
519                     if (t instanceof DeadObjectException
520                             || (cause != null && cause instanceof DeadObjectException)) {
521                         fail("starting cause Throwable to be thrown: " + t);
522                     }
523                 }
524             });
525 
526             postAndRunTask(() -> {
527                 try {
528                     if (mListener == null) {
529                         return;
530                     }
531                     // after the conference call setup, the participant is two.
532                     mConfSession.sendConferenceStateUpdated("connected", 2);
533                 } catch (Throwable t) {
534                     Throwable cause = t.getCause();
535                     if (t instanceof DeadObjectException
536                             || (cause != null && cause instanceof DeadObjectException)) {
537                         fail("starting cause Throwable to be thrown: " + t);
538                     }
539                 }
540             });
541         }
542     }
543 
mergeFailed()544     private void mergeFailed() {
545         if (isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE_FAILED_AFTER_SWAP)) {
546             addExistConferenceSession();
547         } else {
548             createConferenceSession();
549         }
550 
551         postAndRunTask(() -> {
552             try {
553                 if (mListener == null) {
554                     return;
555                 }
556 
557                 TestImsCallSessionImpl confCallSession = mConfSession;
558                 if (isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE_FAILED_AFTER_SWAP)) {
559                     confCallSession = null;
560                 }
561                 Log.d(LOG_TAG, "invokeCallSessionMergeStarted");
562                 mListener.callSessionMergeStarted(confCallSession, mConfCallProfile);
563                 ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE);
564                 if (isTestType(TEST_TYPE_CONFERENCE_FAILED_REMOTE_TERMINATED)) {
565                     return;
566                 }
567                 Log.d(LOG_TAG, "invokeCallSessionMergeFailed");
568                 mListener.callSessionMergeFailed(getReasonInfo(
569                         ImsReasonInfo.CODE_REJECT_ONGOING_CONFERENCE_CALL,
570                         ImsReasonInfo.CODE_UNSPECIFIED));
571             } catch (Throwable t) {
572                 Throwable cause = t.getCause();
573                 if (t instanceof DeadObjectException
574                         || (cause != null && cause instanceof DeadObjectException)) {
575                     fail("starting cause Throwable to be thrown: " + t);
576                 }
577             }
578         });
579     }
580 
mergeExistConference()581     private void mergeExistConference() {
582         addExistConferenceSession();
583         mConfSession.setState(ImsCallSessionImplBase.State.ESTABLISHED);
584 
585         postAndRunTask(() -> {
586             ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE);
587             try {
588                 if (mListener == null) {
589                     return;
590                 }
591 
592                 Log.d(LOG_TAG, "invokeMergeComplete into an existing conference call");
593                 TestImsCallSessionImpl newSession = null;
594                 mListener.callSessionMergeComplete(newSession);
595 
596                 if (isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE_AFTER_SWAP)) {
597                     mConferenceHelper.getBackGroundSession().setState(State.TERMINATED);
598                     mConferenceHelper.getBackGroundSession().invokeTerminatedByRemote();
599                 } else {
600                     mConferenceHelper.getForeGroundSession().setState(State.TERMINATED);
601                     mConferenceHelper.getForeGroundSession().invokeTerminatedByRemote();
602                 }
603             } catch (Throwable t) {
604                 Throwable cause = t.getCause();
605                 if (t instanceof DeadObjectException
606                         || (cause != null && cause instanceof DeadObjectException)) {
607                     fail("starting cause Throwable to be thrown: " + t);
608                 }
609             }
610         });
611 
612         postAndRunTask(() -> {
613             try {
614                 if (mListener == null) {
615                     return;
616                 }
617                 // after joining an existing conference call, the participants are three.
618                 mConfSession.sendConferenceStateUpdated("connected", 3);
619             } catch (Throwable t) {
620                 Throwable cause = t.getCause();
621                 if (t instanceof DeadObjectException
622                         || (cause != null && cause instanceof DeadObjectException)) {
623                     fail("starting cause Throwable to be thrown: " + t);
624                 }
625             }
626         });
627     }
628 
createConferenceSession()629     private void createConferenceSession() {
630         mConferenceHelper.setForeGroundSession(this);
631         mConferenceHelper.setBackGroundSession(mConferenceHelper.getHoldSession());
632 
633         ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
634                 ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
635                 ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE,
636                 ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
637                 ImsStreamMediaProfile.DIRECTION_INVALID,
638                 ImsStreamMediaProfile.RTT_MODE_DISABLED);
639 
640         mConfCallProfile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
641                 ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
642         mConfCallProfile.setCallExtraBoolean(ImsCallProfile.EXTRA_CONFERENCE, true);
643 
644         mConfSession = new TestImsCallSessionImpl(mConfCallProfile);
645         mConfSession.setConferenceHelper(mConferenceHelper);
646         mConferenceHelper.addSession(mConfSession);
647         mConferenceHelper.setConferenceSession(mConfSession);
648     }
649 
addExistConferenceSession()650     private void addExistConferenceSession() {
651         mConferenceHelper.setForeGroundSession(this);
652         mConferenceHelper.setBackGroundSession(mConferenceHelper.getHoldSession());
653 
654         if (isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE_AFTER_SWAP)
655                 || isTestType(TEST_TYPE_JOIN_EXIST_CONFERENCE_FAILED_AFTER_SWAP)) {
656             mConfSession = this;
657         } else {
658             mConfSession = mConferenceHelper.getHoldSession();
659         }
660 
661         mConferenceHelper.setConferenceSession(mConfSession);
662     }
663 
invokeSessionTerminated()664     private void invokeSessionTerminated() {
665         Log.d(LOG_TAG, "invokeCallSessionTerminated");
666         mListener.callSessionTerminated(getReasonInfo(
667                 ImsReasonInfo.CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE,
668                 ImsReasonInfo.CODE_UNSPECIFIED));
669     }
670 
invokeTerminatedByRemote()671     private void invokeTerminatedByRemote() {
672         Log.d(LOG_TAG, "invokeCallSessionTerminated by remote");
673         mListener.callSessionTerminated(getReasonInfo(
674                 ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE,
675                 ImsReasonInfo.CODE_UNSPECIFIED));
676     }
677 
sendConferenceStateUpdated(String state, int count)678     private void sendConferenceStateUpdated(String state, int count) {
679         ImsConferenceState confState = new ImsConferenceState();
680         int counter = 5553639;
681         for (int i = 0; i < count; ++i) {
682             confState.mParticipants.put((String.valueOf(++counter)),
683                     createConferenceParticipant(("tel:" + String.valueOf(++counter)),
684                     ("tel:" + String.valueOf(++counter)), (String.valueOf(++counter)), state, 200));
685         }
686 
687         ImsUtils.waitInCurrentState(WAIT_IN_CURRENT_STATE);
688         Log.d(LOG_TAG, "invokeCallSessionConferenceStateUpdated");
689         mListener.callSessionConferenceStateUpdated(confState);
690     }
691 
692     /**
693      * Send a hold response for this listener.
694      */
sendHoldResponse()695     public void sendHoldResponse() {
696         postAndRunTask(() -> {
697             try {
698                 if (mListener == null) {
699                     return;
700                 }
701                 Log.d(LOG_TAG, "invokeHeld mCallId = " + mCallId);
702                 mListener.callSessionHeld(mCallProfile);
703                 mIsOnHold = true;
704             } catch (Throwable t) {
705                 Throwable cause = t.getCause();
706                 if (t instanceof DeadObjectException
707                         || (cause != null && cause instanceof DeadObjectException)) {
708                     fail("starting cause Throwable to be thrown: " + t);
709                 }
710             }
711         });
712         setState(ImsCallSessionImplBase.State.ESTABLISHED);
713     }
714 
sendHoldFailRemoteTerminated()715     public void sendHoldFailRemoteTerminated() {
716         postAndRunTask(() -> {
717             try {
718                 if (mListener == null) {
719                     return;
720                 }
721                 Log.d(LOG_TAG, "invokeHoldFailed mCallId = " + mCallId);
722                 mListener.callSessionHoldFailed(getReasonInfo(ImsReasonInfo.CODE_UNSPECIFIED,
723                         ImsReasonInfo.CODE_UNSPECIFIED));
724             } catch (Throwable t) {
725                 Throwable cause = t.getCause();
726                 if (t instanceof DeadObjectException
727                         || (cause != null && cause instanceof DeadObjectException)) {
728                     fail("starting cause Throwable to be thrown: " + t);
729                 }
730             }
731         });
732         setState(ImsCallSessionImplBase.State.ESTABLISHED);
733 
734         sendTerminatedByRemote();
735     }
736 
sendTerminatedByRemote()737     public void sendTerminatedByRemote() {
738         postAndRunTask(() -> {
739             try {
740                 if (mListener == null) {
741                     return;
742                 }
743                 invokeTerminatedByRemote();
744                 setState(ImsCallSessionImplBase.State.TERMINATED);
745             } catch (Throwable t) {
746                 Throwable cause = t.getCause();
747                 if (t instanceof DeadObjectException
748                         || (cause != null && cause instanceof DeadObjectException)) {
749                     fail("starting cause Throwable to be thrown: " + t);
750                 }
751             }
752         });
753     }
754 
sendMergedFailed()755     public void sendMergedFailed() {
756         postAndRunTask(() -> {
757             try {
758                 if (mListener == null) {
759                     return;
760                 }
761                 Log.d(LOG_TAG, "invokeMergedFailed mCallId = " + mCallId);
762                 mListener.callSessionMergeFailed(getReasonInfo(
763                         ImsReasonInfo.CODE_REJECT_ONGOING_CONFERENCE_CALL,
764                         ImsReasonInfo.CODE_UNSPECIFIED));
765             } catch (Throwable t) {
766                 Throwable cause = t.getCause();
767                 if (t instanceof DeadObjectException
768                         || (cause != null && cause instanceof DeadObjectException)) {
769                     fail("starting cause Throwable to be thrown: " + t);
770                 }
771             }
772         });
773     }
774 
sendHoldReceived()775     public void sendHoldReceived() {
776         postAndRunTask(() -> {
777             try {
778                 if (mListener == null) {
779                     return;
780                 }
781                 Log.d(LOG_TAG, "invokeHoldReceived mCallId = " + mCallId);
782                 mListener.callSessionHoldReceived(mCallProfile);
783             } catch (Throwable t) {
784                 Throwable cause = t.getCause();
785                 if (t instanceof DeadObjectException
786                         || (cause != null && cause instanceof DeadObjectException)) {
787                     fail("starting cause Throwable to be thrown: " + t);
788                 }
789             }
790         });
791         setState(ImsCallSessionImplBase.State.ESTABLISHED);
792     }
793 
sendResumeReceived()794     public void sendResumeReceived() {
795         postAndRunTask(() -> {
796             try {
797                 if (mListener == null) {
798                     return;
799                 }
800                 Log.d(LOG_TAG, "invokeResumeReceived mCallId = " + mCallId);
801                 mListener.callSessionResumeReceived(mCallProfile);
802             } catch (Throwable t) {
803                 Throwable cause = t.getCause();
804                 if (t instanceof DeadObjectException
805                         || (cause != null && cause instanceof DeadObjectException)) {
806                     fail("starting cause Throwable to be thrown: " + t);
807                 }
808             }
809         });
810         setState(ImsCallSessionImplBase.State.ESTABLISHED);
811     }
812 
createConferenceParticipant(String user, String endpoint, String displayText, String status, int sipStatusCode)813     public Bundle createConferenceParticipant(String user, String endpoint,
814             String displayText, String status, int sipStatusCode) {
815         Bundle participant = new Bundle();
816 
817         participant.putString(ImsConferenceState.STATUS, status);
818         participant.putString(ImsConferenceState.USER, user);
819         participant.putString(ImsConferenceState.ENDPOINT, endpoint);
820         participant.putString(ImsConferenceState.DISPLAY_TEXT, displayText);
821         participant.putInt(ImsConferenceState.SIP_STATUS_CODE, sipStatusCode);
822         return participant;
823     }
824 
setConferenceHelper(ConferenceHelper confHelper)825     public void setConferenceHelper(ConferenceHelper confHelper) {
826         mConferenceHelper = confHelper;
827     }
828 
isSessionOnHold()829     public boolean isSessionOnHold() {
830         return mIsOnHold;
831     }
832 
setState(int state)833     private void setState(int state) {
834         if (mState != state) {
835             Log.d(LOG_TAG, "ImsCallSession :: " + mState + " >> " + state);
836             mState = state;
837         }
838     }
839 
isInTerminated()840     public boolean isInTerminated() {
841         return (mState == ImsCallSessionImplBase.State.TERMINATED) ? true : false;
842     }
843 
isRenegotiating()844     public boolean isRenegotiating() {
845         return (mState == State.RENEGOTIATING) ? true : false;
846     }
847 
getReasonInfo(int code, int extraCode)848     private ImsReasonInfo getReasonInfo(int code, int extraCode) {
849         ImsReasonInfo reasonInfo = new ImsReasonInfo(code, extraCode, "");
850         return reasonInfo;
851     }
852 
addTestType(int type)853     public void addTestType(int type) {
854         mTestType |= type;
855     }
856 
removeTestType(int type)857     public void removeTestType(int type) {
858         mTestType &= ~type;
859     }
860 
isTestType(int type)861     public boolean isTestType(int type) {
862         return  ((mTestType & type) == type);
863     }
864 
getExecutor()865     public Executor getExecutor() {
866         return mCallBackExecutor;
867     }
868 
postAndRunTask(Runnable task)869     private void postAndRunTask(Runnable task) {
870         mCallBackExecutor.execute(task);
871     }
872 
createLooper(String name)873     private static Looper createLooper(String name) {
874         HandlerThread thread = new HandlerThread(name);
875         thread.start();
876 
877         Looper looper = thread.getLooper();
878 
879         if (looper == null) {
880             return Looper.getMainLooper();
881         }
882         return looper;
883     }
884      /**
885      * Executes the tasks in the other thread rather than the calling thread.
886      */
887     public class MessageExecutor extends Handler implements Executor {
MessageExecutor(String name)888         public MessageExecutor(String name) {
889             super(createLooper(name));
890         }
891 
892         @Override
execute(Runnable r)893         public void execute(Runnable r) {
894             Message m = Message.obtain(this, 0 /* don't care */, r);
895             m.sendToTarget();
896         }
897 
898         @Override
handleMessage(Message msg)899         public void handleMessage(Message msg) {
900             if (msg.obj instanceof Runnable) {
901                 executeInternal((Runnable) msg.obj);
902             } else {
903                 Log.d(LOG_TAG, "[MessageExecutor] handleMessage :: "
904                         + "Not runnable object; ignore the msg=" + msg);
905             }
906         }
907 
executeInternal(Runnable r)908         private void executeInternal(Runnable r) {
909             try {
910                 r.run();
911             } catch (Throwable t) {
912                 Log.d(LOG_TAG, "[MessageExecutor] executeInternal :: run task=" + r);
913                 t.printStackTrace();
914             }
915         }
916     }
917 
918     /**
919      * ANBR Query received.
920      */
callSessionSendAnbrQuery(int mediaType, int direction, int bitsPerSecond)921     public void callSessionSendAnbrQuery(int mediaType, int direction, int bitsPerSecond) {
922         if (mListener != null) {
923             mListener.callSessionSendAnbrQuery(mediaType, direction, bitsPerSecond);
924         } else {
925             Log.d(LOG_TAG, "callSessionSendAnbrQuery - listener is null");
926         }
927     }
928 
929     /**
930      * Deliver the bitrate for the indicated media type, direction and bitrate to the upper layer.
931      */
callSessionNotifyAnbr(int mediaType, int direction, int bitsPerSecond)932     public void callSessionNotifyAnbr(int mediaType, int direction, int bitsPerSecond) {
933         mAnbrValues[0] = mediaType;
934         mAnbrValues[1] = direction;
935         mAnbrValues[2] = bitsPerSecond;
936     }
937 
938     /**
939      * Returns the Anbr values received from NW.
940      */
getAnbrValues()941     public int[] getAnbrValues() {
942         if (mAnbrValues[0] > 0 && mAnbrValues[1] > 0 && mAnbrValues[2] >= 0) {
943             return mAnbrValues;
944         } else {
945             Log.d(LOG_TAG, "getAnbrValues - invalid values");
946             return null;
947         }
948     }
949 
950     /**
951      * Clears the Anbr values.
952      */
resetAnbrValues()953     public void resetAnbrValues() {
954         mAnbrValues[0] = -1;
955         mAnbrValues[1] = -1;
956         mAnbrValues[2] = -1;
957     }
958 
959     /**
960      * Returns whether IMS call session transfer result is notified.
961      */
isTransferResultNotified()962     public boolean isTransferResultNotified() {
963         return mIsTransferResultNotified;
964     }
965 }
966