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