1 /* 2 * Copyright (C) 2017 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 com.android.incallui.call; 18 19 import android.content.Context; 20 import android.os.Handler; 21 import android.os.Message; 22 import android.os.Trace; 23 import android.support.annotation.NonNull; 24 import android.support.annotation.Nullable; 25 import android.support.annotation.VisibleForTesting; 26 import android.support.v4.os.BuildCompat; 27 import android.telecom.Call; 28 import android.telecom.DisconnectCause; 29 import android.telecom.PhoneAccount; 30 import android.util.ArrayMap; 31 import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; 32 import com.android.dialer.blocking.FilteredNumbersUtil; 33 import com.android.dialer.common.Assert; 34 import com.android.dialer.common.LogUtil; 35 import com.android.dialer.enrichedcall.EnrichedCallComponent; 36 import com.android.dialer.enrichedcall.EnrichedCallManager; 37 import com.android.dialer.location.GeoUtil; 38 import com.android.dialer.logging.DialerImpression; 39 import com.android.dialer.logging.Logger; 40 import com.android.dialer.shortcuts.ShortcutUsageReporter; 41 import com.android.dialer.spam.Spam; 42 import com.android.dialer.spam.SpamBindings; 43 import com.android.incallui.call.DialerCall.State; 44 import com.android.incallui.latencyreport.LatencyReport; 45 import com.android.incallui.util.TelecomCallUtil; 46 import com.android.incallui.videotech.utils.SessionModificationState; 47 import java.util.Collections; 48 import java.util.Iterator; 49 import java.util.Map; 50 import java.util.Objects; 51 import java.util.Set; 52 import java.util.concurrent.ConcurrentHashMap; 53 54 /** 55 * Maintains the list of active calls and notifies interested classes of changes to the call list as 56 * they are received from the telephony stack. Primary listener of changes to this class is 57 * InCallPresenter. 58 */ 59 public class CallList implements DialerCallDelegate { 60 61 private static final int DISCONNECTED_CALL_SHORT_TIMEOUT_MS = 200; 62 private static final int DISCONNECTED_CALL_MEDIUM_TIMEOUT_MS = 2000; 63 private static final int DISCONNECTED_CALL_LONG_TIMEOUT_MS = 5000; 64 65 private static final int EVENT_DISCONNECTED_TIMEOUT = 1; 66 67 private static CallList sInstance = new CallList(); 68 69 private final Map<String, DialerCall> mCallById = new ArrayMap<>(); 70 private final Map<android.telecom.Call, DialerCall> mCallByTelecomCall = new ArrayMap<>(); 71 72 /** 73 * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is load factor before 74 * resizing, 1 means we only expect a single thread to access the map so make only a single shard 75 */ 76 private final Set<Listener> mListeners = 77 Collections.newSetFromMap(new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1)); 78 79 private final Set<DialerCall> mPendingDisconnectCalls = 80 Collections.newSetFromMap(new ConcurrentHashMap<DialerCall, Boolean>(8, 0.9f, 1)); 81 /** Handles the timeout for destroying disconnected calls. */ 82 private final Handler mHandler = 83 new Handler() { 84 @Override 85 public void handleMessage(Message msg) { 86 switch (msg.what) { 87 case EVENT_DISCONNECTED_TIMEOUT: 88 LogUtil.d("CallList.handleMessage", "EVENT_DISCONNECTED_TIMEOUT ", msg.obj); 89 finishDisconnectedCall((DialerCall) msg.obj); 90 break; 91 default: 92 LogUtil.e("CallList.handleMessage", "Message not expected: " + msg.what); 93 break; 94 } 95 } 96 }; 97 98 /** 99 * USED ONLY FOR TESTING Testing-only constructor. Instance should only be acquired through 100 * getRunningInstance(). 101 */ 102 @VisibleForTesting CallList()103 public CallList() {} 104 105 @VisibleForTesting setCallListInstance(CallList callList)106 public static void setCallListInstance(CallList callList) { 107 sInstance = callList; 108 } 109 110 /** Static singleton accessor method. */ getInstance()111 public static CallList getInstance() { 112 return sInstance; 113 } 114 onCallAdded( final Context context, final android.telecom.Call telecomCall, LatencyReport latencyReport)115 public void onCallAdded( 116 final Context context, final android.telecom.Call telecomCall, LatencyReport latencyReport) { 117 Trace.beginSection("onCallAdded"); 118 final DialerCall call = 119 new DialerCall(context, this, telecomCall, latencyReport, true /* registerCallback */); 120 logSecondIncomingCall(context, call); 121 122 EnrichedCallManager manager = EnrichedCallComponent.get(context).getEnrichedCallManager(); 123 manager.registerCapabilitiesListener(call); 124 manager.registerStateChangedListener(call); 125 126 final DialerCallListenerImpl dialerCallListener = new DialerCallListenerImpl(call); 127 call.addListener(dialerCallListener); 128 LogUtil.d("CallList.onCallAdded", "callState=" + call.getState()); 129 if (Spam.get(context).isSpamEnabled()) { 130 String number = TelecomCallUtil.getNumber(telecomCall); 131 Spam.get(context) 132 .checkSpamStatus( 133 number, 134 null, 135 new SpamBindings.Listener() { 136 @Override 137 public void onComplete(boolean isSpam) { 138 boolean isIncomingCall = 139 call.getState() == DialerCall.State.INCOMING 140 || call.getState() == DialerCall.State.CALL_WAITING; 141 if (isSpam) { 142 if (!isIncomingCall) { 143 LogUtil.i( 144 "CallList.onCallAdded", 145 "marking spam call as not spam because it's not an incoming call"); 146 isSpam = false; 147 } else if (isPotentialEmergencyCallback(context, call)) { 148 LogUtil.i( 149 "CallList.onCallAdded", 150 "marking spam call as not spam because an emergency call was made on this" 151 + " device recently"); 152 isSpam = false; 153 } 154 } 155 156 if (isIncomingCall) { 157 Logger.get(context) 158 .logCallImpression( 159 isSpam 160 ? DialerImpression.Type.INCOMING_SPAM_CALL 161 : DialerImpression.Type.INCOMING_NON_SPAM_CALL, 162 call.getUniqueCallId(), 163 call.getTimeAddedMs()); 164 } 165 call.setSpam(isSpam); 166 dialerCallListener.onDialerCallUpdate(); 167 } 168 }); 169 170 updateUserMarkedSpamStatus(call, context, number, dialerCallListener); 171 } 172 173 FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler = 174 new FilteredNumberAsyncQueryHandler(context); 175 176 filteredNumberAsyncQueryHandler.isBlockedNumber( 177 new FilteredNumberAsyncQueryHandler.OnCheckBlockedListener() { 178 @Override 179 public void onCheckComplete(Integer id) { 180 if (id != null && id != FilteredNumberAsyncQueryHandler.INVALID_ID) { 181 call.setBlockedStatus(true); 182 dialerCallListener.onDialerCallUpdate(); 183 } 184 } 185 }, 186 call.getNumber(), 187 GeoUtil.getCurrentCountryIso(context)); 188 189 if (call.getState() == DialerCall.State.INCOMING 190 || call.getState() == DialerCall.State.CALL_WAITING) { 191 onIncoming(call); 192 } else { 193 dialerCallListener.onDialerCallUpdate(); 194 } 195 196 if (call.getState() != State.INCOMING) { 197 // Only report outgoing calls 198 ShortcutUsageReporter.onOutgoingCallAdded(context, call.getNumber()); 199 } 200 201 Trace.endSection(); 202 } 203 logSecondIncomingCall(@onNull Context context, @NonNull DialerCall incomingCall)204 private void logSecondIncomingCall(@NonNull Context context, @NonNull DialerCall incomingCall) { 205 DialerCall firstCall = getFirstCall(); 206 if (firstCall != null) { 207 DialerImpression.Type impression; 208 if (firstCall.isVideoCall()) { 209 if (incomingCall.isVideoCall()) { 210 impression = DialerImpression.Type.VIDEO_CALL_WITH_INCOMING_VIDEO_CALL; 211 } else { 212 impression = DialerImpression.Type.VIDEO_CALL_WITH_INCOMING_VOICE_CALL; 213 } 214 } else { 215 if (incomingCall.isVideoCall()) { 216 impression = DialerImpression.Type.VOICE_CALL_WITH_INCOMING_VIDEO_CALL; 217 } else { 218 impression = DialerImpression.Type.VOICE_CALL_WITH_INCOMING_VOICE_CALL; 219 } 220 } 221 Assert.checkArgument(impression != null); 222 Logger.get(context) 223 .logCallImpression( 224 impression, incomingCall.getUniqueCallId(), incomingCall.getTimeAddedMs()); 225 } 226 } 227 isPotentialEmergencyCallback(Context context, DialerCall call)228 private static boolean isPotentialEmergencyCallback(Context context, DialerCall call) { 229 if (BuildCompat.isAtLeastO()) { 230 return call.isPotentialEmergencyCallback(); 231 } else { 232 long timestampMillis = FilteredNumbersUtil.getLastEmergencyCallTimeMillis(context); 233 return call.isInEmergencyCallbackWindow(timestampMillis); 234 } 235 } 236 237 @Override getDialerCallFromTelecomCall(Call telecomCall)238 public DialerCall getDialerCallFromTelecomCall(Call telecomCall) { 239 return mCallByTelecomCall.get(telecomCall); 240 } 241 updateUserMarkedSpamStatus( final DialerCall call, final Context context, String number, final DialerCallListenerImpl dialerCallListener)242 public void updateUserMarkedSpamStatus( 243 final DialerCall call, 244 final Context context, 245 String number, 246 final DialerCallListenerImpl dialerCallListener) { 247 248 Spam.get(context) 249 .checkUserMarkedNonSpamStatus( 250 number, 251 null, 252 new SpamBindings.Listener() { 253 @Override 254 public void onComplete(boolean isInUserWhiteList) { 255 call.setIsInUserWhiteList(isInUserWhiteList); 256 } 257 }); 258 259 Spam.get(context) 260 .checkGlobalSpamListStatus( 261 number, 262 null, 263 new SpamBindings.Listener() { 264 @Override 265 public void onComplete(boolean isInGlobalSpamList) { 266 call.setIsInGlobalSpamList(isInGlobalSpamList); 267 } 268 }); 269 270 Spam.get(context) 271 .checkUserMarkedSpamStatus( 272 number, 273 null, 274 new SpamBindings.Listener() { 275 @Override 276 public void onComplete(boolean isInUserSpamList) { 277 call.setIsInUserSpamList(isInUserSpamList); 278 } 279 }); 280 } 281 onCallRemoved(Context context, android.telecom.Call telecomCall)282 public void onCallRemoved(Context context, android.telecom.Call telecomCall) { 283 if (mCallByTelecomCall.containsKey(telecomCall)) { 284 DialerCall call = mCallByTelecomCall.get(telecomCall); 285 Assert.checkArgument(!call.isExternalCall()); 286 287 EnrichedCallManager manager = EnrichedCallComponent.get(context).getEnrichedCallManager(); 288 manager.unregisterCapabilitiesListener(call); 289 manager.unregisterStateChangedListener(call); 290 291 // Don't log an already logged call. logCall() might be called multiple times 292 // for the same call due to b/24109437. 293 if (call.getLogState() != null && !call.getLogState().isLogged) { 294 getLegacyBindings(context).logCall(call); 295 call.getLogState().isLogged = true; 296 } 297 298 if (updateCallInMap(call)) { 299 LogUtil.w( 300 "CallList.onCallRemoved", "Removing call not previously disconnected " + call.getId()); 301 } 302 303 call.onRemovedFromCallList(); 304 } 305 306 if (!hasLiveCall()) { 307 DialerCall.clearRestrictedCount(); 308 } 309 } 310 getLegacyBindings(Context context)311 InCallUiLegacyBindings getLegacyBindings(Context context) { 312 Objects.requireNonNull(context); 313 314 Context application = context.getApplicationContext(); 315 InCallUiLegacyBindings legacyInstance = null; 316 if (application instanceof InCallUiLegacyBindingsFactory) { 317 legacyInstance = ((InCallUiLegacyBindingsFactory) application).newInCallUiLegacyBindings(); 318 } 319 320 if (legacyInstance == null) { 321 legacyInstance = new InCallUiLegacyBindingsStub(); 322 } 323 return legacyInstance; 324 } 325 326 /** 327 * Handles the case where an internal call has become an exteral call. We need to 328 * 329 * @param context 330 * @param telecomCall 331 */ onInternalCallMadeExternal(Context context, android.telecom.Call telecomCall)332 public void onInternalCallMadeExternal(Context context, android.telecom.Call telecomCall) { 333 334 if (mCallByTelecomCall.containsKey(telecomCall)) { 335 DialerCall call = mCallByTelecomCall.get(telecomCall); 336 337 // Don't log an already logged call. logCall() might be called multiple times 338 // for the same call due to b/24109437. 339 if (call.getLogState() != null && !call.getLogState().isLogged) { 340 getLegacyBindings(context).logCall(call); 341 call.getLogState().isLogged = true; 342 } 343 344 // When removing a call from the call list because it became an external call, we need to 345 // ensure the callback is unregistered -- this is normally only done when calls disconnect. 346 // However, the call won't be disconnected in this case. Also, logic in updateCallInMap 347 // would just re-add the call anyways. 348 call.unregisterCallback(); 349 mCallById.remove(call.getId()); 350 mCallByTelecomCall.remove(telecomCall); 351 } 352 } 353 354 /** Called when a single call has changed. */ onIncoming(DialerCall call)355 private void onIncoming(DialerCall call) { 356 if (updateCallInMap(call)) { 357 LogUtil.i("CallList.onIncoming", String.valueOf(call)); 358 } 359 360 for (Listener listener : mListeners) { 361 listener.onIncomingCall(call); 362 } 363 } 364 addListener(@onNull Listener listener)365 public void addListener(@NonNull Listener listener) { 366 Objects.requireNonNull(listener); 367 368 mListeners.add(listener); 369 370 // Let the listener know about the active calls immediately. 371 listener.onCallListChange(this); 372 } 373 removeListener(@ullable Listener listener)374 public void removeListener(@Nullable Listener listener) { 375 if (listener != null) { 376 mListeners.remove(listener); 377 } 378 } 379 380 /** 381 * TODO: Change so that this function is not needed. Instead of assuming there is an active call, 382 * the code should rely on the status of a specific DialerCall and allow the presenters to update 383 * the DialerCall object when the active call changes. 384 */ getIncomingOrActive()385 public DialerCall getIncomingOrActive() { 386 DialerCall retval = getIncomingCall(); 387 if (retval == null) { 388 retval = getActiveCall(); 389 } 390 return retval; 391 } 392 getOutgoingOrActive()393 public DialerCall getOutgoingOrActive() { 394 DialerCall retval = getOutgoingCall(); 395 if (retval == null) { 396 retval = getActiveCall(); 397 } 398 return retval; 399 } 400 401 /** A call that is waiting for {@link PhoneAccount} selection */ getWaitingForAccountCall()402 public DialerCall getWaitingForAccountCall() { 403 return getFirstCallWithState(DialerCall.State.SELECT_PHONE_ACCOUNT); 404 } 405 getPendingOutgoingCall()406 public DialerCall getPendingOutgoingCall() { 407 return getFirstCallWithState(DialerCall.State.CONNECTING); 408 } 409 getOutgoingCall()410 public DialerCall getOutgoingCall() { 411 DialerCall call = getFirstCallWithState(DialerCall.State.DIALING); 412 if (call == null) { 413 call = getFirstCallWithState(DialerCall.State.REDIALING); 414 } 415 if (call == null) { 416 call = getFirstCallWithState(DialerCall.State.PULLING); 417 } 418 return call; 419 } 420 getActiveCall()421 public DialerCall getActiveCall() { 422 return getFirstCallWithState(DialerCall.State.ACTIVE); 423 } 424 getSecondActiveCall()425 public DialerCall getSecondActiveCall() { 426 return getCallWithState(DialerCall.State.ACTIVE, 1); 427 } 428 getBackgroundCall()429 public DialerCall getBackgroundCall() { 430 return getFirstCallWithState(DialerCall.State.ONHOLD); 431 } 432 getDisconnectedCall()433 public DialerCall getDisconnectedCall() { 434 return getFirstCallWithState(DialerCall.State.DISCONNECTED); 435 } 436 getDisconnectingCall()437 public DialerCall getDisconnectingCall() { 438 return getFirstCallWithState(DialerCall.State.DISCONNECTING); 439 } 440 getSecondBackgroundCall()441 public DialerCall getSecondBackgroundCall() { 442 return getCallWithState(DialerCall.State.ONHOLD, 1); 443 } 444 getActiveOrBackgroundCall()445 public DialerCall getActiveOrBackgroundCall() { 446 DialerCall call = getActiveCall(); 447 if (call == null) { 448 call = getBackgroundCall(); 449 } 450 return call; 451 } 452 getIncomingCall()453 public DialerCall getIncomingCall() { 454 DialerCall call = getFirstCallWithState(DialerCall.State.INCOMING); 455 if (call == null) { 456 call = getFirstCallWithState(DialerCall.State.CALL_WAITING); 457 } 458 459 return call; 460 } 461 getFirstCall()462 public DialerCall getFirstCall() { 463 DialerCall result = getIncomingCall(); 464 if (result == null) { 465 result = getPendingOutgoingCall(); 466 } 467 if (result == null) { 468 result = getOutgoingCall(); 469 } 470 if (result == null) { 471 result = getFirstCallWithState(DialerCall.State.ACTIVE); 472 } 473 if (result == null) { 474 result = getDisconnectingCall(); 475 } 476 if (result == null) { 477 result = getDisconnectedCall(); 478 } 479 return result; 480 } 481 hasLiveCall()482 public boolean hasLiveCall() { 483 DialerCall call = getFirstCall(); 484 return call != null && call != getDisconnectingCall() && call != getDisconnectedCall(); 485 } 486 487 /** 488 * Returns the first call found in the call map with the upgrade to video modification state. 489 * 490 * @return The first call with the upgrade to video state. 491 */ getVideoUpgradeRequestCall()492 public DialerCall getVideoUpgradeRequestCall() { 493 for (DialerCall call : mCallById.values()) { 494 if (call.getVideoTech().getSessionModificationState() 495 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) { 496 return call; 497 } 498 } 499 return null; 500 } 501 getCallById(String callId)502 public DialerCall getCallById(String callId) { 503 return mCallById.get(callId); 504 } 505 506 /** Returns first call found in the call map with the specified state. */ getFirstCallWithState(int state)507 public DialerCall getFirstCallWithState(int state) { 508 return getCallWithState(state, 0); 509 } 510 511 /** 512 * Returns the [position]th call found in the call map with the specified state. TODO: Improve 513 * this logic to sort by call time. 514 */ getCallWithState(int state, int positionToFind)515 public DialerCall getCallWithState(int state, int positionToFind) { 516 DialerCall retval = null; 517 int position = 0; 518 for (DialerCall call : mCallById.values()) { 519 if (call.getState() == state) { 520 if (position >= positionToFind) { 521 retval = call; 522 break; 523 } else { 524 position++; 525 } 526 } 527 } 528 529 return retval; 530 } 531 532 /** 533 * This is called when the service disconnects, either expectedly or unexpectedly. For the 534 * expected case, it's because we have no calls left. For the unexpected case, it is likely a 535 * crash of phone and we need to clean up our calls manually. Without phone, there can be no 536 * active calls, so this is relatively safe thing to do. 537 */ clearOnDisconnect()538 public void clearOnDisconnect() { 539 for (DialerCall call : mCallById.values()) { 540 final int state = call.getState(); 541 if (state != DialerCall.State.IDLE 542 && state != DialerCall.State.INVALID 543 && state != DialerCall.State.DISCONNECTED) { 544 545 call.setState(DialerCall.State.DISCONNECTED); 546 call.setDisconnectCause(new DisconnectCause(DisconnectCause.UNKNOWN)); 547 updateCallInMap(call); 548 } 549 } 550 notifyGenericListeners(); 551 } 552 553 /** 554 * Called when the user has dismissed an error dialog. This indicates acknowledgement of the 555 * disconnect cause, and that any pending disconnects should immediately occur. 556 */ onErrorDialogDismissed()557 public void onErrorDialogDismissed() { 558 final Iterator<DialerCall> iterator = mPendingDisconnectCalls.iterator(); 559 while (iterator.hasNext()) { 560 DialerCall call = iterator.next(); 561 iterator.remove(); 562 finishDisconnectedCall(call); 563 } 564 } 565 566 /** 567 * Processes an update for a single call. 568 * 569 * @param call The call to update. 570 */ 571 @VisibleForTesting onUpdateCall(DialerCall call)572 void onUpdateCall(DialerCall call) { 573 LogUtil.d("CallList.onUpdateCall", String.valueOf(call)); 574 if (!mCallById.containsKey(call.getId()) && call.isExternalCall()) { 575 // When a regular call becomes external, it is removed from the call list, and there may be 576 // pending updates to Telecom which are queued up on the Telecom call's handler which we no 577 // longer wish to cause updates to the call in the CallList. Bail here if the list of tracked 578 // calls doesn't contain the call which received the update. 579 return; 580 } 581 582 if (updateCallInMap(call)) { 583 LogUtil.i("CallList.onUpdateCall", String.valueOf(call)); 584 } 585 } 586 587 /** 588 * Sends a generic notification to all listeners that something has changed. It is up to the 589 * listeners to call back to determine what changed. 590 */ notifyGenericListeners()591 private void notifyGenericListeners() { 592 for (Listener listener : mListeners) { 593 listener.onCallListChange(this); 594 } 595 } 596 notifyListenersOfDisconnect(DialerCall call)597 private void notifyListenersOfDisconnect(DialerCall call) { 598 for (Listener listener : mListeners) { 599 listener.onDisconnect(call); 600 } 601 } 602 603 /** 604 * Updates the call entry in the local map. 605 * 606 * @return false if no call previously existed and no call was added, otherwise true. 607 */ updateCallInMap(DialerCall call)608 private boolean updateCallInMap(DialerCall call) { 609 Objects.requireNonNull(call); 610 611 boolean updated = false; 612 613 if (call.getState() == DialerCall.State.DISCONNECTED) { 614 // update existing (but do not add!!) disconnected calls 615 if (mCallById.containsKey(call.getId())) { 616 // For disconnected calls, we want to keep them alive for a few seconds so that the 617 // UI has a chance to display anything it needs when a call is disconnected. 618 619 // Set up a timer to destroy the call after X seconds. 620 final Message msg = mHandler.obtainMessage(EVENT_DISCONNECTED_TIMEOUT, call); 621 mHandler.sendMessageDelayed(msg, getDelayForDisconnect(call)); 622 mPendingDisconnectCalls.add(call); 623 624 mCallById.put(call.getId(), call); 625 mCallByTelecomCall.put(call.getTelecomCall(), call); 626 updated = true; 627 } 628 } else if (!isCallDead(call)) { 629 mCallById.put(call.getId(), call); 630 mCallByTelecomCall.put(call.getTelecomCall(), call); 631 updated = true; 632 } else if (mCallById.containsKey(call.getId())) { 633 mCallById.remove(call.getId()); 634 mCallByTelecomCall.remove(call.getTelecomCall()); 635 updated = true; 636 } 637 638 return updated; 639 } 640 getDelayForDisconnect(DialerCall call)641 private int getDelayForDisconnect(DialerCall call) { 642 if (call.getState() != DialerCall.State.DISCONNECTED) { 643 throw new IllegalStateException(); 644 } 645 646 final int cause = call.getDisconnectCause().getCode(); 647 final int delay; 648 switch (cause) { 649 case DisconnectCause.LOCAL: 650 delay = DISCONNECTED_CALL_SHORT_TIMEOUT_MS; 651 break; 652 case DisconnectCause.REMOTE: 653 case DisconnectCause.ERROR: 654 delay = DISCONNECTED_CALL_MEDIUM_TIMEOUT_MS; 655 break; 656 case DisconnectCause.REJECTED: 657 case DisconnectCause.MISSED: 658 case DisconnectCause.CANCELED: 659 // no delay for missed/rejected incoming calls and canceled outgoing calls. 660 delay = 0; 661 break; 662 default: 663 delay = DISCONNECTED_CALL_LONG_TIMEOUT_MS; 664 break; 665 } 666 667 return delay; 668 } 669 isCallDead(DialerCall call)670 private boolean isCallDead(DialerCall call) { 671 final int state = call.getState(); 672 return DialerCall.State.IDLE == state || DialerCall.State.INVALID == state; 673 } 674 675 /** Sets up a call for deletion and notifies listeners of change. */ finishDisconnectedCall(DialerCall call)676 private void finishDisconnectedCall(DialerCall call) { 677 if (mPendingDisconnectCalls.contains(call)) { 678 mPendingDisconnectCalls.remove(call); 679 } 680 call.setState(DialerCall.State.IDLE); 681 updateCallInMap(call); 682 notifyGenericListeners(); 683 } 684 685 /** 686 * Notifies all video calls of a change in device orientation. 687 * 688 * @param rotation The new rotation angle (in degrees). 689 */ notifyCallsOfDeviceRotation(int rotation)690 public void notifyCallsOfDeviceRotation(int rotation) { 691 for (DialerCall call : mCallById.values()) { 692 call.getVideoTech().setDeviceOrientation(rotation); 693 } 694 } 695 onInCallUiShown(boolean forFullScreenIntent)696 public void onInCallUiShown(boolean forFullScreenIntent) { 697 for (DialerCall call : mCallById.values()) { 698 call.getLatencyReport().onInCallUiShown(forFullScreenIntent); 699 } 700 } 701 702 /** Listener interface for any class that wants to be notified of changes to the call list. */ 703 public interface Listener { 704 705 /** 706 * Called when a new incoming call comes in. This is the only method that gets called for 707 * incoming calls. Listeners that want to perform an action on incoming call should respond in 708 * this method because {@link #onCallListChange} does not automatically get called for incoming 709 * calls. 710 */ onIncomingCall(DialerCall call)711 void onIncomingCall(DialerCall call); 712 713 /** 714 * Called when a new modify call request comes in This is the only method that gets called for 715 * modify requests. 716 */ onUpgradeToVideo(DialerCall call)717 void onUpgradeToVideo(DialerCall call); 718 719 /** Called when the session modification state of a call changes. */ onSessionModificationStateChange(DialerCall call)720 void onSessionModificationStateChange(DialerCall call); 721 722 /** 723 * Called anytime there are changes to the call list. The change can be switching call states, 724 * updating information, etc. This method will NOT be called for new incoming calls and for 725 * calls that switch to disconnected state. Listeners must add actions to those method 726 * implementations if they want to deal with those actions. 727 */ onCallListChange(CallList callList)728 void onCallListChange(CallList callList); 729 730 /** 731 * Called when a call switches to the disconnected state. This is the only method that will get 732 * called upon disconnection. 733 */ onDisconnect(DialerCall call)734 void onDisconnect(DialerCall call); 735 onWiFiToLteHandover(DialerCall call)736 void onWiFiToLteHandover(DialerCall call); 737 738 /** 739 * Called when a user is in a video call and the call is unable to be handed off successfully to 740 * WiFi 741 */ onHandoverToWifiFailed(DialerCall call)742 void onHandoverToWifiFailed(DialerCall call); 743 744 /** Called when the user initiates a call to an international number while on WiFi. */ onInternationalCallOnWifi(@onNull DialerCall call)745 void onInternationalCallOnWifi(@NonNull DialerCall call); 746 } 747 748 private class DialerCallListenerImpl implements DialerCallListener { 749 750 @NonNull private final DialerCall mCall; 751 DialerCallListenerImpl(@onNull DialerCall call)752 DialerCallListenerImpl(@NonNull DialerCall call) { 753 mCall = Assert.isNotNull(call); 754 } 755 756 @Override onDialerCallDisconnect()757 public void onDialerCallDisconnect() { 758 if (updateCallInMap(mCall)) { 759 LogUtil.i("DialerCallListenerImpl.onDialerCallDisconnect", String.valueOf(mCall)); 760 // notify those listening for all disconnects 761 notifyListenersOfDisconnect(mCall); 762 } 763 } 764 765 @Override onDialerCallUpdate()766 public void onDialerCallUpdate() { 767 Trace.beginSection("onUpdate"); 768 onUpdateCall(mCall); 769 notifyGenericListeners(); 770 Trace.endSection(); 771 } 772 773 @Override onDialerCallChildNumberChange()774 public void onDialerCallChildNumberChange() {} 775 776 @Override onDialerCallLastForwardedNumberChange()777 public void onDialerCallLastForwardedNumberChange() {} 778 779 @Override onDialerCallUpgradeToVideo()780 public void onDialerCallUpgradeToVideo() { 781 for (Listener listener : mListeners) { 782 listener.onUpgradeToVideo(mCall); 783 } 784 } 785 786 @Override onWiFiToLteHandover()787 public void onWiFiToLteHandover() { 788 for (Listener listener : mListeners) { 789 listener.onWiFiToLteHandover(mCall); 790 } 791 } 792 793 @Override onHandoverToWifiFailure()794 public void onHandoverToWifiFailure() { 795 for (Listener listener : mListeners) { 796 listener.onHandoverToWifiFailed(mCall); 797 } 798 } 799 800 @Override onInternationalCallOnWifi()801 public void onInternationalCallOnWifi() { 802 LogUtil.enterBlock("DialerCallListenerImpl.onInternationalCallOnWifi"); 803 for (Listener listener : mListeners) { 804 listener.onInternationalCallOnWifi(mCall); 805 } 806 } 807 808 @Override onEnrichedCallSessionUpdate()809 public void onEnrichedCallSessionUpdate() {} 810 811 @Override onDialerCallSessionModificationStateChange()812 public void onDialerCallSessionModificationStateChange() { 813 for (Listener listener : mListeners) { 814 listener.onSessionModificationStateChange(mCall); 815 } 816 } 817 } 818 } 819