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