1 /*
2  * Copyright (C) 2015 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.telecom.cts;
18 
19 import static org.junit.Assert.assertTrue;
20 
21 import android.content.Intent;
22 import android.os.Bundle;
23 import android.telecom.Call;
24 import android.telecom.CallAudioState;
25 import android.telecom.CallEndpoint;
26 import android.telecom.InCallService;
27 import android.util.ArrayMap;
28 import android.util.Log;
29 
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.concurrent.CountDownLatch;
35 import java.util.concurrent.Semaphore;
36 import java.util.concurrent.TimeUnit;
37 
38 public class MockInCallService extends InCallService {
39     private static final long TEST_TIMEOUT = 5000L;
40     private static String LOG_TAG = "MockInCallService";
41     private static final List<Call> sCalls = Collections.synchronizedList(new ArrayList<>());
42     private static Call sLastCall = null;
43     private final List<Call> mConferenceCalls = Collections.synchronizedList(new ArrayList<>());
44     private static InCallServiceCallbacks sCallbacks;
45     private Map<Call, MockVideoCallCallback> mVideoCallCallbacks =
46             new ArrayMap<Call, MockVideoCallCallback>();
47 
48     protected static final Object sLock = new Object();
49     private static boolean mIsServiceBound = false;
50     private static CountDownLatch sBindLatch = new CountDownLatch(1);
51     private static CountDownLatch sUnbindLatch = new CountDownLatch(1);
52     private boolean mEndpointIsMute = false;
53 
54     public static abstract class InCallServiceCallbacks {
55         private MockInCallService mService;
56         public Semaphore lock = new Semaphore(0);
57 
onCallAdded(Call call, int numCalls)58         public void onCallAdded(Call call, int numCalls) {};
onCallRemoved(Call call, int numCalls)59         public void onCallRemoved(Call call, int numCalls) {};
onCallStateChanged(Call call, int state)60         public void onCallStateChanged(Call call, int state) {};
onParentChanged(Call call, Call parent)61         public void onParentChanged(Call call, Call parent) {};
onChildrenChanged(Call call, List<Call> children)62         public void onChildrenChanged(Call call, List<Call> children) {};
onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls)63         public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {};
onCallDestroyed(Call call)64         public void onCallDestroyed(Call call) {};
onDetailsChanged(Call call, Call.Details details)65         public void onDetailsChanged(Call call, Call.Details details) {};
onCanAddCallsChanged(boolean canAddCalls)66         public void onCanAddCallsChanged(boolean canAddCalls) {}
onBringToForeground(boolean showDialpad)67         public void onBringToForeground(boolean showDialpad) {}
onCallAudioStateChanged(CallAudioState audioState)68         public void onCallAudioStateChanged(CallAudioState audioState) {}
onPostDialWait(Call call, String remainingPostDialSequence)69         public void onPostDialWait(Call call, String remainingPostDialSequence) {}
onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses)70         public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {}
onSilenceRinger()71         public void onSilenceRinger() {}
onConnectionEvent(Call call, String event, Bundle extras)72         public void onConnectionEvent(Call call, String event, Bundle extras) {}
onRttModeChanged(Call call, int mode)73         public void onRttModeChanged(Call call, int mode) {}
onRttStatusChanged(Call call, boolean enabled, Call.RttCall rttCall)74         public void onRttStatusChanged(Call call, boolean enabled, Call.RttCall rttCall) {}
onRttRequest(Call call, int id)75         public void onRttRequest(Call call, int id) {}
onRttInitiationFailure(Call call, int reason)76         public void onRttInitiationFailure(Call call, int reason) {}
onHandoverComplete(Call call)77         public void onHandoverComplete(Call call) {}
onHandoverFailed(Call call, int failureReason)78         public void onHandoverFailed(Call call, int failureReason) {}
onCallEndpointChanged(CallEndpoint callEndpoint)79         public void onCallEndpointChanged(CallEndpoint callEndpoint) {}
onAvailableCallEndpointsChanged(List<CallEndpoint> availableEndpoints)80         public void onAvailableCallEndpointsChanged(List<CallEndpoint> availableEndpoints) {}
onMuteStateChanged(boolean isMuted)81         public void onMuteStateChanged(boolean isMuted) {}
82 
getService()83         final public MockInCallService getService() {
84             return mService;
85         }
86 
setService(MockInCallService service)87         final public void setService(MockInCallService service) {
88             mService = service;
89         }
90 
resetLock()91         public void resetLock() {
92             lock = new Semaphore(0);
93         }
94 
resetLatch()95         public void resetLatch() {
96             sBindLatch = new CountDownLatch(1);
97             sUnbindLatch = new CountDownLatch(1);
98         }
99 
waitForUnbind()100         public boolean waitForUnbind() {
101             try {
102                 return sUnbindLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
103             } catch (InterruptedException e) {
104                 return false;
105             }
106         }
107     }
108 
109     /**
110      * Note that the super implementations of the callback methods are all no-ops, but we call
111      * them anyway to make sure that the CTS coverage tool detects that we are testing them.
112      */
113     private Call.Callback mCallCallback = new Call.Callback() {
114         @Override
115         public void onStateChanged(Call call, int state) {
116             super.onStateChanged(call, state);
117             if (getCallbacks() != null) {
118                 getCallbacks().onCallStateChanged(call, state);
119             }
120         }
121 
122         @Override
123         public void onVideoCallChanged(Call call, InCallService.VideoCall videoCall) {
124             super.onVideoCallChanged(call, videoCall);
125             saveVideoCall(call, videoCall);
126         }
127 
128         @Override
129         public void onParentChanged(Call call, Call parent) {
130             super.onParentChanged(call, parent);
131             if (getCallbacks() != null) {
132                 getCallbacks().onParentChanged(call, parent);
133             }
134         }
135 
136         @Override
137         public void onChildrenChanged(Call call, List<Call> children) {
138             super.onChildrenChanged(call, children);
139             if (getCallbacks() != null) {
140                 getCallbacks().onChildrenChanged(call, children);
141             }
142         }
143 
144         @Override
145         public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
146             super.onConferenceableCallsChanged(call, conferenceableCalls);
147             if (getCallbacks() != null) {
148                 getCallbacks().onConferenceableCallsChanged(call, conferenceableCalls);
149             }
150         }
151 
152         @Override
153         public void onCallDestroyed(Call call) {
154             super.onCallDestroyed(call);
155             if (getCallbacks() != null) {
156                 getCallbacks().onCallDestroyed(call);
157             }
158         }
159 
160         @Override
161         public void onDetailsChanged(Call call, Call.Details details) {
162             super.onDetailsChanged(call, details);
163             if (getCallbacks() != null) {
164                 getCallbacks().onDetailsChanged(call, details);
165             }
166         }
167 
168         @Override
169         public void onPostDialWait(Call call, String remainingPostDialSequence) {
170             super.onPostDialWait(call, remainingPostDialSequence);
171             if (getCallbacks() != null) {
172                 getCallbacks().onPostDialWait(call, remainingPostDialSequence);
173             }
174         }
175 
176         @Override
177         public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {
178             super.onCannedTextResponsesLoaded(call, cannedTextResponses);
179             if (getCallbacks() != null) {
180                 getCallbacks().onCannedTextResponsesLoaded(call, cannedTextResponses);
181             }
182         }
183 
184         @Override
185         public void onConnectionEvent(Call call, String event, Bundle extras) {
186             Log.i(LOG_TAG, String.format("onConnectionEvent: call=[%s], event=[%s]",
187                     call, event));
188             super.onConnectionEvent(call, event, extras);
189             if (getCallbacks() != null) {
190                 getCallbacks().onConnectionEvent(call, event, extras);
191             }
192         }
193 
194         @Override
195         public void onRttModeChanged(Call call, int mode) {
196             super.onRttModeChanged(call, mode);
197             if (getCallbacks() != null) {
198                 getCallbacks().onRttModeChanged(call, mode);
199             }
200         }
201 
202         @Override
203         public void onRttStatusChanged(Call call, boolean enabled, Call.RttCall rttCall) {
204             super.onRttStatusChanged(call, enabled, rttCall);
205             if (getCallbacks() != null) {
206                 getCallbacks().onRttStatusChanged(call, enabled, rttCall);
207             }
208         }
209 
210         @Override
211         public void onRttRequest(Call call, int id) {
212             super.onRttRequest(call, id);
213             if (getCallbacks() != null) {
214                 getCallbacks().onRttRequest(call, id);
215             }
216         }
217 
218         @Override
219         public void onRttInitiationFailure(Call call, int reason) {
220             super.onRttInitiationFailure(call, reason);
221             if (getCallbacks() != null) {
222                 getCallbacks().onRttInitiationFailure(call, reason);
223             }
224         }
225 
226         @Override
227         public void onHandoverComplete(Call call) {
228             super.onHandoverComplete(call);
229             if (getCallbacks() != null) {
230                 getCallbacks().onHandoverComplete(call);
231             }
232         }
233 
234         @Override
235         public void onHandoverFailed(Call call, int failureReason) {
236             super.onHandoverFailed(call, failureReason);
237             if (getCallbacks() != null) {
238                 getCallbacks().onHandoverFailed(call, failureReason);
239             }
240         }
241     };
242 
saveVideoCall(Call call, VideoCall videoCall)243     private void saveVideoCall(Call call, VideoCall videoCall) {
244         if (videoCall != null) {
245             if (!mVideoCallCallbacks.containsKey(call)) {
246                 MockVideoCallCallback listener = new MockVideoCallCallback(call);
247                 videoCall.registerCallback(listener);
248                 mVideoCallCallbacks.put(call, listener);
249             }
250         } else {
251             mVideoCallCallbacks.remove(call);
252         }
253     }
254 
255     @Override
onBind(android.content.Intent intent)256     public android.os.IBinder onBind(android.content.Intent intent) {
257         Log.i(LOG_TAG, "Service bounded");
258         if (getCallbacks() != null) {
259             getCallbacks().setService(this);
260         }
261         mIsServiceBound = true;
262         return super.onBind(intent);
263     }
264 
265     @Override
onCallAdded(Call call)266     public void onCallAdded(Call call) {
267         Log.i(LOG_TAG, String.format("onCallAdded: call=[%s]", call));
268         super.onCallAdded(call);
269         if (call.getDetails().hasProperty(Call.Details.PROPERTY_CONFERENCE) == true) {
270             if (!mConferenceCalls.contains(call)) {
271                 mConferenceCalls.add(call);
272                 call.registerCallback(mCallCallback);
273             }
274         } else {
275             if (!sCalls.contains(call)) {
276                 Log.i(LOG_TAG, "added call to list");
277                 sCalls.add(call);
278                 sLastCall = call;
279                 call.registerCallback(mCallCallback);
280                 VideoCall videoCall = call.getVideoCall();
281                 if (videoCall != null) {
282                     saveVideoCall(call, videoCall);
283                 }
284             }
285         }
286         if (getCallbacks() != null) {
287             getCallbacks().onCallAdded(call, sCalls.size() + mConferenceCalls.size());
288         }
289     }
290 
291     @Override
onCallRemoved(Call call)292     public void onCallRemoved(Call call) {
293         Log.i(LOG_TAG, String.format("onCallRemoved: call=[%s]", call));
294         super.onCallRemoved(call);
295         if (call.getDetails().hasProperty(Call.Details.PROPERTY_CONFERENCE) == true) {
296             mConferenceCalls.remove(call);
297         } else {
298             sCalls.remove(call);
299             if (call.equals(sLastCall)) {
300                 sLastCall = null;
301             }
302         }
303         if (getCallbacks() != null) {
304             getCallbacks().onCallRemoved(call, sCalls.size() + mConferenceCalls.size());
305             saveVideoCall(call, null /* remove videoCall */);
306         }
307     }
308 
309     @Override
onCanAddCallChanged(boolean canAddCall)310     public void onCanAddCallChanged(boolean canAddCall) {
311         super.onCanAddCallChanged(canAddCall);
312         if (getCallbacks() != null) {
313             getCallbacks().onCanAddCallsChanged(canAddCall);
314         }
315     }
316 
317     @Override
onBringToForeground(boolean showDialpad)318     public void onBringToForeground(boolean showDialpad) {
319         super.onBringToForeground(showDialpad);
320         if (getCallbacks() != null) {
321             getCallbacks().onBringToForeground(showDialpad);
322         }
323     }
324 
325     @Override
onCallAudioStateChanged(CallAudioState audioState)326     public void onCallAudioStateChanged(CallAudioState audioState) {
327         super.onCallAudioStateChanged(audioState);
328         if (getCallbacks() != null) {
329             getCallbacks().onCallAudioStateChanged(audioState);
330         }
331     }
332 
333     @Override
onCallEndpointChanged(CallEndpoint callEndpoint)334     public void onCallEndpointChanged(CallEndpoint callEndpoint) {
335         super.onCallEndpointChanged(callEndpoint);
336         if (getCallbacks() != null) {
337             getCallbacks().onCallEndpointChanged(callEndpoint);
338         }
339     }
340 
341     @Override
onAvailableCallEndpointsChanged(List<CallEndpoint> availableEndpoints)342     public void onAvailableCallEndpointsChanged(List<CallEndpoint> availableEndpoints) {
343         super.onAvailableCallEndpointsChanged(availableEndpoints);
344         if (getCallbacks() != null) {
345             getCallbacks().onAvailableCallEndpointsChanged(availableEndpoints);
346         }
347     }
348 
349     @Override
onMuteStateChanged(boolean isMuted)350     public void onMuteStateChanged(boolean isMuted) {
351         super.onMuteStateChanged(isMuted);
352         mEndpointIsMute = isMuted;
353         if (getCallbacks() != null) {
354             getCallbacks().onMuteStateChanged(isMuted);
355         }
356     }
357 
358     @Override
onSilenceRinger()359     public void onSilenceRinger(){
360         super.onSilenceRinger();
361         if(getCallbacks() != null) {
362             getCallbacks().onSilenceRinger();
363         }
364     }
365 
366     /**
367      * @return the number of calls currently added to the {@code InCallService}.
368      */
getCallCount()369     public int getCallCount() {
370         return sCalls.size();
371     }
372 
373     /**
374      * @return the number of conference calls currently added to the {@code InCallService}.
375      */
getConferenceCallCount()376     public int getConferenceCallCount() {
377         return mConferenceCalls.size();
378     }
379 
380     /**
381      * @return the most recently added call that exists inside the {@code InCallService}
382      */
getLastCall()383     public Call getLastCall() {
384         if (!sCalls.isEmpty()) {
385             return sCalls.get(sCalls.size() - 1);
386         }
387         return null;
388     }
389 
getAllCalls()390     public List<Call> getAllCalls() {
391         return sCalls;
392     }
393 
getCallWithId(String id)394     public Call getCallWithId(String id) {
395         for (Call call : sCalls) {
396             if (call.getDetails().getTelecomCallId().equals(id)) {
397                 return call;
398             }
399         }
400         return null;
401     }
402 
clearCallList()403     public void clearCallList() {
404         sCalls.clear();
405     }
406 
407     /**
408      * @return the most recently added conference call that exists inside the {@code InCallService}
409      */
getLastConferenceCall()410     public Call getLastConferenceCall() {
411         if (!mConferenceCalls.isEmpty()) {
412             return mConferenceCalls.get(mConferenceCalls.size() - 1);
413         }
414         return null;
415     }
416 
disconnectLastCall()417     public void disconnectLastCall() {
418         final Call call = getLastCall();
419         if (call != null) {
420             call.disconnect();
421         }
422     }
423 
disconnectLastConferenceCall()424     public void disconnectLastConferenceCall() {
425         final Call call = getLastConferenceCall();
426         if (call != null) {
427             call.disconnect();
428         }
429     }
430 
disconnectAllCalls()431     public void disconnectAllCalls() {
432         synchronized (sCalls) {
433             for (final Call call : sCalls) {
434                 call.disconnect();
435             }
436         }
437     }
438 
disconnectAllConferenceCalls()439     public void disconnectAllConferenceCalls() {
440         synchronized (mConferenceCalls) {
441             for (final Call call : mConferenceCalls) {
442                 call.disconnect();
443             }
444         }
445     }
446 
setCallbacks(InCallServiceCallbacks callbacks)447     public static void setCallbacks(InCallServiceCallbacks callbacks) {
448         synchronized (sLock) {
449             sCallbacks = callbacks;
450         }
451     }
452 
getCallbacks()453     private InCallServiceCallbacks getCallbacks() {
454         synchronized (sLock) {
455             if (sCallbacks != null) {
456                 sCallbacks.setService(this);
457             }
458             return sCallbacks;
459         }
460     }
461 
getEndpointMuteState()462     public boolean getEndpointMuteState() {
463         return mEndpointIsMute;
464     }
465 
466     /**
467      * Determines if a video callback has been registered for the passed in call.
468      *
469      * @param call The call.
470      * @return {@code true} if a video callback has been registered.
471      */
isVideoCallbackRegistered(Call call)472     public boolean isVideoCallbackRegistered(Call call) {
473         return mVideoCallCallbacks.containsKey(call);
474     }
475 
476     /**
477      * Retrieves the video callbacks associated with a call.
478      * @param call The call.
479      * @return The {@link MockVideoCallCallback} instance associated with the call.
480      */
getVideoCallCallback(Call call)481     public MockVideoCallCallback getVideoCallCallback(Call call) {
482         return mVideoCallCallbacks.get(call);
483     }
484 
485     @Override
onUnbind(Intent intent)486     public boolean onUnbind(Intent intent) {
487         Log.i(LOG_TAG, "Service has been unbound");
488         assertTrue(mIsServiceBound);
489         mIsServiceBound = false;
490         sCalls.clear();
491         return super.onUnbind(intent);
492     }
493 
isServiceBound()494     public static boolean isServiceBound() {
495         return mIsServiceBound;
496     }
497 
getCurrentCallCount()498     public static int getCurrentCallCount() {
499         return sCalls.size();
500     }
501 
getOngoingCalls()502     public static List<Call> getOngoingCalls() {
503         return sCalls;
504     }
505 
getLastAddedCall()506     public static Call getLastAddedCall() {
507         return sLastCall;
508     }
509 }
510