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