blob: 634a302a21121aeb5d8bab93524126a82381c816 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.incallui.call;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.os.Trace;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.telecom.Call;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.util.ArrayMap;
import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
import com.android.dialer.common.concurrent.DialerExecutorComponent;
import com.android.dialer.enrichedcall.EnrichedCallComponent;
import com.android.dialer.enrichedcall.EnrichedCallManager;
import com.android.dialer.logging.DialerImpression;
import com.android.dialer.logging.Logger;
import com.android.dialer.metrics.Metrics;
import com.android.dialer.metrics.MetricsComponent;
import com.android.dialer.promotion.impl.RttPromotion;
import com.android.dialer.shortcuts.ShortcutUsageReporter;
import com.android.dialer.spam.SpamComponent;
import com.android.dialer.spam.status.SpamStatus;
import com.android.dialer.telecom.TelecomCallUtil;
import com.android.incallui.call.state.DialerCallState;
import com.android.incallui.latencyreport.LatencyReport;
import com.android.incallui.videotech.utils.SessionModificationState;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Maintains the list of active calls and notifies interested classes of changes to the call list as
* they are received from the telephony stack. Primary listener of changes to this class is
* InCallPresenter.
*/
public class CallList implements DialerCallDelegate {
private static final int DISCONNECTED_CALL_SHORT_TIMEOUT_MS = 200;
private static final int DISCONNECTED_CALL_MEDIUM_TIMEOUT_MS = 2000;
private static final int DISCONNECTED_CALL_LONG_TIMEOUT_MS = 5000;
private static final int EVENT_DISCONNECTED_TIMEOUT = 1;
private static CallList instance = new CallList();
private final Map<String, DialerCall> callById = new ArrayMap<>();
private final Map<android.telecom.Call, DialerCall> callByTelecomCall = new ArrayMap<>();
/**
* ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is load factor before
* resizing, 1 means we only expect a single thread to access the map so make only a single shard
*/
private final Set<Listener> listeners =
Collections.newSetFromMap(new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
private final Set<DialerCall> pendingDisconnectCalls =
Collections.newSetFromMap(new ConcurrentHashMap<DialerCall, Boolean>(8, 0.9f, 1));
private UiListener uiListeners;
/** Handles the timeout for destroying disconnected calls. */
private final Handler handler =
new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_DISCONNECTED_TIMEOUT:
LogUtil.d("CallList.handleMessage", "EVENT_DISCONNECTED_TIMEOUT ", msg.obj);
finishDisconnectedCall((DialerCall) msg.obj);
break;
default:
LogUtil.e("CallList.handleMessage", "Message not expected: " + msg.what);
break;
}
}
};
/**
* USED ONLY FOR TESTING Testing-only constructor. Instance should only be acquired through
* getRunningInstance().
*/
@VisibleForTesting
public CallList() {}
@VisibleForTesting
public static void setCallListInstance(CallList callList) {
instance = callList;
}
/** Static singleton accessor method. */
public static CallList getInstance() {
return instance;
}
public void onCallAdded(
final Context context, final android.telecom.Call telecomCall, LatencyReport latencyReport) {
Trace.beginSection("CallList.onCallAdded");
if (telecomCall.getState() == Call.STATE_CONNECTING) {
MetricsComponent.get(context)
.metrics()
.startTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING);
} else if (telecomCall.getState() == Call.STATE_RINGING) {
MetricsComponent.get(context)
.metrics()
.startTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING);
}
if (uiListeners != null) {
uiListeners.onCallAdded();
}
final DialerCall call =
new DialerCall(context, this, telecomCall, latencyReport, true /* registerCallback */);
if (getFirstCall() != null) {
logSecondIncomingCall(context, getFirstCall(), call);
}
EnrichedCallManager manager = EnrichedCallComponent.get(context).getEnrichedCallManager();
manager.registerCapabilitiesListener(call);
manager.registerStateChangedListener(call);
Trace.beginSection("checkSpam");
call.addListener(new DialerCallListenerImpl(call));
LogUtil.d("CallList.onCallAdded", "callState=" + call.getState());
if (SpamComponent.get(context).spamSettings().isSpamEnabled()) {
String number = TelecomCallUtil.getNumber(telecomCall);
ListenableFuture<SpamStatus> futureSpamStatus =
SpamComponent.get(context).spam().checkSpamStatus(number, call.getCountryIso());
Futures.addCallback(
futureSpamStatus,
new FutureCallback<SpamStatus>() {
@Override
public void onSuccess(@Nullable SpamStatus result) {
boolean isIncomingCall =
call.getState() == DialerCallState.INCOMING
|| call.getState() == DialerCallState.CALL_WAITING;
boolean isSpam = result.isSpam();
call.setSpamStatus(result);
if (isIncomingCall) {
Logger.get(context)
.logCallImpression(
isSpam
? DialerImpression.Type.INCOMING_SPAM_CALL
: DialerImpression.Type.INCOMING_NON_SPAM_CALL,
call.getUniqueCallId(),
call.getTimeAddedMs());
}
onUpdateCall(call);
notifyGenericListeners();
}
@Override
public void onFailure(Throwable t) {
LogUtil.e("CallList.onFailure", "unable to query spam status", t);
}
},
DialerExecutorComponent.get(context).uiExecutor());
Trace.beginSection("updateUserMarkedSpamStatus");
Trace.endSection();
}
Trace.endSection();
Trace.beginSection("checkBlock");
FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler =
new FilteredNumberAsyncQueryHandler(context);
filteredNumberAsyncQueryHandler.isBlockedNumber(
new FilteredNumberAsyncQueryHandler.OnCheckBlockedListener() {
@Override
public void onCheckComplete(Integer id) {
if (id != null && id != FilteredNumberAsyncQueryHandler.INVALID_ID) {
call.setBlockedStatus(true);
// No need to update UI since it's only used for logging.
}
}
},
call.getNumber(),
call.getCountryIso());
Trace.endSection();
if (call.getState() == DialerCallState.INCOMING
|| call.getState() == DialerCallState.CALL_WAITING) {
if (call.isActiveRttCall()) {
if (!call.isPhoneAccountRttCapable()) {
RttPromotion.setEnabled(context);
}
Logger.get(context)
.logCallImpression(
DialerImpression.Type.INCOMING_RTT_CALL,
call.getUniqueCallId(),
call.getTimeAddedMs());
}
onIncoming(call);
} else {
if (call.isActiveRttCall()) {
Logger.get(context)
.logCallImpression(
DialerImpression.Type.OUTGOING_RTT_CALL,
call.getUniqueCallId(),
call.getTimeAddedMs());
}
onUpdateCall(call);
notifyGenericListeners();
}
if (call.getState() != DialerCallState.INCOMING) {
// Only report outgoing calls
ShortcutUsageReporter.onOutgoingCallAdded(context, call.getNumber());
}
Trace.endSection();
}
private void logSecondIncomingCall(
@NonNull Context context, @NonNull DialerCall firstCall, @NonNull DialerCall incomingCall) {
DialerImpression.Type impression;
if (firstCall.isVideoCall()) {
if (incomingCall.isVideoCall()) {
impression = DialerImpression.Type.VIDEO_CALL_WITH_INCOMING_VIDEO_CALL;
} else {
impression = DialerImpression.Type.VIDEO_CALL_WITH_INCOMING_VOICE_CALL;
}
} else {
if (incomingCall.isVideoCall()) {
impression = DialerImpression.Type.VOICE_CALL_WITH_INCOMING_VIDEO_CALL;
} else {
impression = DialerImpression.Type.VOICE_CALL_WITH_INCOMING_VOICE_CALL;
}
}
Assert.checkArgument(impression != null);
Logger.get(context)
.logCallImpression(
impression, incomingCall.getUniqueCallId(), incomingCall.getTimeAddedMs());
}
@Override
public DialerCall getDialerCallFromTelecomCall(Call telecomCall) {
return callByTelecomCall.get(telecomCall);
}
public void onCallRemoved(Context context, android.telecom.Call telecomCall) {
if (callByTelecomCall.containsKey(telecomCall)) {
DialerCall call = callByTelecomCall.get(telecomCall);
Assert.checkArgument(!call.isExternalCall());
EnrichedCallManager manager = EnrichedCallComponent.get(context).getEnrichedCallManager();
manager.unregisterCapabilitiesListener(call);
manager.unregisterStateChangedListener(call);
// Don't log an already logged call. logCall() might be called multiple times
// for the same call due to a bug.
if (call.getLogState() != null && !call.getLogState().isLogged) {
getLegacyBindings(context).logCall(call);
call.getLogState().isLogged = true;
}
if (updateCallInMap(call)) {
LogUtil.w(
"CallList.onCallRemoved", "Removing call not previously disconnected " + call.getId());
}
call.onRemovedFromCallList();
}
if (!hasLiveCall()) {
DialerCall.clearRestrictedCount();
}
}
InCallUiLegacyBindings getLegacyBindings(Context context) {
Objects.requireNonNull(context);
Context application = context.getApplicationContext();
InCallUiLegacyBindings legacyInstance = null;
if (application instanceof InCallUiLegacyBindingsFactory) {
legacyInstance = ((InCallUiLegacyBindingsFactory) application).newInCallUiLegacyBindings();
}
if (legacyInstance == null) {
legacyInstance = new InCallUiLegacyBindingsStub();
}
return legacyInstance;
}
/**
* Handles the case where an internal call has become an exteral call. We need to
*
* @param context
* @param telecomCall
*/
public void onInternalCallMadeExternal(Context context, android.telecom.Call telecomCall) {
if (callByTelecomCall.containsKey(telecomCall)) {
DialerCall call = callByTelecomCall.get(telecomCall);
// Don't log an already logged call. logCall() might be called multiple times
// for the same call due to a bug.
if (call.getLogState() != null && !call.getLogState().isLogged) {
getLegacyBindings(context).logCall(call);
call.getLogState().isLogged = true;
}
// When removing a call from the call list because it became an external call, we need to
// ensure the callback is unregistered -- this is normally only done when calls disconnect.
// However, the call won't be disconnected in this case. Also, logic in updateCallInMap
// would just re-add the call anyways.
call.unregisterCallback();
callById.remove(call.getId());
callByTelecomCall.remove(telecomCall);
}
}
/** Called when a single call has changed. */
private void onIncoming(DialerCall call) {
Trace.beginSection("CallList.onIncoming");
if (updateCallInMap(call)) {
LogUtil.i("CallList.onIncoming", String.valueOf(call));
}
for (Listener listener : listeners) {
listener.onIncomingCall(call);
}
Trace.endSection();
}
public void addListener(@NonNull Listener listener) {
Objects.requireNonNull(listener);
listeners.add(listener);
// Let the listener know about the active calls immediately.
listener.onCallListChange(this);
}
public void setUiListener(UiListener uiListener) {
uiListeners = uiListener;
}
public void removeListener(@Nullable Listener listener) {
if (listener != null) {
listeners.remove(listener);
}
}
/**
* TODO: Change so that this function is not needed. Instead of assuming there is an active call,
* the code should rely on the status of a specific DialerCall and allow the presenters to update
* the DialerCall object when the active call changes.
*/
public DialerCall getIncomingOrActive() {
DialerCall retval = getIncomingCall();
if (retval == null) {
retval = getActiveCall();
}
return retval;
}
public DialerCall getOutgoingOrActive() {
DialerCall retval = getOutgoingCall();
if (retval == null) {
retval = getActiveCall();
}
return retval;
}
/** A call that is waiting for {@link PhoneAccount} selection */
public DialerCall getWaitingForAccountCall() {
return getFirstCallWithState(DialerCallState.SELECT_PHONE_ACCOUNT);
}
public DialerCall getPendingOutgoingCall() {
return getFirstCallWithState(DialerCallState.CONNECTING);
}
public DialerCall getOutgoingCall() {
DialerCall call = getFirstCallWithState(DialerCallState.DIALING);
if (call == null) {
call = getFirstCallWithState(DialerCallState.REDIALING);
}
if (call == null) {
call = getFirstCallWithState(DialerCallState.PULLING);
}
return call;
}
public DialerCall getActiveCall() {
return getFirstCallWithState(DialerCallState.ACTIVE);
}
public DialerCall getSecondActiveCall() {
return getCallWithState(DialerCallState.ACTIVE, 1);
}
public DialerCall getBackgroundCall() {
return getFirstCallWithState(DialerCallState.ONHOLD);
}
public DialerCall getDisconnectedCall() {
return getFirstCallWithState(DialerCallState.DISCONNECTED);
}
public DialerCall getDisconnectingCall() {
return getFirstCallWithState(DialerCallState.DISCONNECTING);
}
public DialerCall getSecondBackgroundCall() {
return getCallWithState(DialerCallState.ONHOLD, 1);
}
public DialerCall getActiveOrBackgroundCall() {
DialerCall call = getActiveCall();
if (call == null) {
call = getBackgroundCall();
}
return call;
}
public DialerCall getIncomingCall() {
DialerCall call = getFirstCallWithState(DialerCallState.INCOMING);
if (call == null) {
call = getFirstCallWithState(DialerCallState.CALL_WAITING);
}
return call;
}
public DialerCall getFirstCall() {
DialerCall result = getIncomingCall();
if (result == null) {
result = getPendingOutgoingCall();
}
if (result == null) {
result = getOutgoingCall();
}
if (result == null) {
result = getFirstCallWithState(DialerCallState.ACTIVE);
}
if (result == null) {
result = getDisconnectingCall();
}
if (result == null) {
result = getDisconnectedCall();
}
return result;
}
public boolean hasLiveCall() {
DialerCall call = getFirstCall();
return call != null && call != getDisconnectingCall() && call != getDisconnectedCall();
}
boolean hasActiveRttCall() {
for (DialerCall call : getAllCalls()) {
if (call.isActiveRttCall()) {
return true;
}
}
return false;
}
/**
* Returns the first call found in the call map with the upgrade to video modification state.
*
* @return The first call with the upgrade to video state.
*/
public DialerCall getVideoUpgradeRequestCall() {
for (DialerCall call : callById.values()) {
if (call.getVideoTech().getSessionModificationState()
== SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
return call;
}
}
return null;
}
public DialerCall getCallById(String callId) {
return callById.get(callId);
}
public Collection<DialerCall> getAllCalls() {
return callById.values();
}
/** Returns first call found in the call map with the specified state. */
public DialerCall getFirstCallWithState(int state) {
return getCallWithState(state, 0);
}
/**
* Returns the [position]th call found in the call map with the specified state. TODO: Improve
* this logic to sort by call time.
*/
public DialerCall getCallWithState(int state, int positionToFind) {
DialerCall retval = null;
int position = 0;
for (DialerCall call : callById.values()) {
if (call.getState() == state) {
if (position >= positionToFind) {
retval = call;
break;
} else {
position++;
}
}
}
return retval;
}
/**
* Return if there is any active or background call which was not a parent call (never had a child
* call)
*/
public boolean hasNonParentActiveOrBackgroundCall() {
for (DialerCall call : callById.values()) {
if ((call.getState() == DialerCallState.ACTIVE
|| call.getState() == DialerCallState.ONHOLD
|| call.getState() == DialerCallState.CONFERENCED)
&& !call.wasParentCall()) {
return true;
}
}
return false;
}
/**
* This is called when the service disconnects, either expectedly or unexpectedly. For the
* expected case, it's because we have no calls left. For the unexpected case, it is likely a
* crash of phone and we need to clean up our calls manually. Without phone, there can be no
* active calls, so this is relatively safe thing to do.
*/
public void clearOnDisconnect() {
for (DialerCall call : callById.values()) {
final int state = call.getState();
if (state != DialerCallState.IDLE
&& state != DialerCallState.INVALID
&& state != DialerCallState.DISCONNECTED) {
call.setState(DialerCallState.DISCONNECTED);
call.setDisconnectCause(new DisconnectCause(DisconnectCause.UNKNOWN));
updateCallInMap(call);
}
}
notifyGenericListeners();
}
/**
* Called when the user has dismissed an error dialog. This indicates acknowledgement of the
* disconnect cause, and that any pending disconnects should immediately occur.
*/
public void onErrorDialogDismissed() {
final Iterator<DialerCall> iterator = pendingDisconnectCalls.iterator();
while (iterator.hasNext()) {
DialerCall call = iterator.next();
iterator.remove();
finishDisconnectedCall(call);
}
}
/**
* Processes an update for a single call.
*
* @param call The call to update.
*/
@VisibleForTesting
void onUpdateCall(DialerCall call) {
Trace.beginSection("CallList.onUpdateCall");
LogUtil.d("CallList.onUpdateCall", String.valueOf(call));
if (!callById.containsKey(call.getId()) && call.isExternalCall()) {
// When a regular call becomes external, it is removed from the call list, and there may be
// pending updates to Telecom which are queued up on the Telecom call's handler which we no
// longer wish to cause updates to the call in the CallList. Bail here if the list of tracked
// calls doesn't contain the call which received the update.
return;
}
if (updateCallInMap(call)) {
LogUtil.i("CallList.onUpdateCall", String.valueOf(call));
}
Trace.endSection();
}
/**
* Sends a generic notification to all listeners that something has changed. It is up to the
* listeners to call back to determine what changed.
*/
private void notifyGenericListeners() {
Trace.beginSection("CallList.notifyGenericListeners");
for (Listener listener : listeners) {
listener.onCallListChange(this);
}
Trace.endSection();
}
private void notifyListenersOfDisconnect(DialerCall call) {
for (Listener listener : listeners) {
listener.onDisconnect(call);
}
}
/**
* Updates the call entry in the local map.
*
* @return false if no call previously existed and no call was added, otherwise true.
*/
private boolean updateCallInMap(DialerCall call) {
Trace.beginSection("CallList.updateCallInMap");
Objects.requireNonNull(call);
boolean updated = false;
if (call.getState() == DialerCallState.DISCONNECTED) {
// update existing (but do not add!!) disconnected calls
if (callById.containsKey(call.getId())) {
// For disconnected calls, we want to keep them alive for a few seconds so that the
// UI has a chance to display anything it needs when a call is disconnected.
// Set up a timer to destroy the call after X seconds.
final Message msg = handler.obtainMessage(EVENT_DISCONNECTED_TIMEOUT, call);
handler.sendMessageDelayed(msg, getDelayForDisconnect(call));
pendingDisconnectCalls.add(call);
callById.put(call.getId(), call);
callByTelecomCall.put(call.getTelecomCall(), call);
updated = true;
}
} else if (!isCallDead(call)) {
callById.put(call.getId(), call);
callByTelecomCall.put(call.getTelecomCall(), call);
updated = true;
} else if (callById.containsKey(call.getId())) {
callById.remove(call.getId());
callByTelecomCall.remove(call.getTelecomCall());
updated = true;
}
Trace.endSection();
return updated;
}
private int getDelayForDisconnect(DialerCall call) {
if (call.getState() != DialerCallState.DISCONNECTED) {
throw new IllegalStateException();
}
final int cause = call.getDisconnectCause().getCode();
final int delay;
switch (cause) {
case DisconnectCause.LOCAL:
delay = DISCONNECTED_CALL_SHORT_TIMEOUT_MS;
break;
case DisconnectCause.REMOTE:
case DisconnectCause.ERROR:
delay = DISCONNECTED_CALL_MEDIUM_TIMEOUT_MS;
break;
case DisconnectCause.REJECTED:
case DisconnectCause.MISSED:
case DisconnectCause.CANCELED:
// no delay for missed/rejected incoming calls and canceled outgoing calls.
delay = 0;
break;
default:
delay = DISCONNECTED_CALL_LONG_TIMEOUT_MS;
break;
}
return delay;
}
private boolean isCallDead(DialerCall call) {
final int state = call.getState();
return DialerCallState.IDLE == state || DialerCallState.INVALID == state;
}
/** Sets up a call for deletion and notifies listeners of change. */
private void finishDisconnectedCall(DialerCall call) {
if (pendingDisconnectCalls.contains(call)) {
pendingDisconnectCalls.remove(call);
}
call.setState(DialerCallState.IDLE);
updateCallInMap(call);
notifyGenericListeners();
}
/**
* Notifies all video calls of a change in device orientation.
*
* @param rotation The new rotation angle (in degrees).
*/
public void notifyCallsOfDeviceRotation(int rotation) {
for (DialerCall call : callById.values()) {
call.getVideoTech().setDeviceOrientation(rotation);
}
}
public void onInCallUiShown(boolean forFullScreenIntent) {
for (DialerCall call : callById.values()) {
call.getLatencyReport().onInCallUiShown(forFullScreenIntent);
}
if (uiListeners != null) {
uiListeners.onInCallUiShown();
}
}
/** Listener interface for any class that wants to be notified of changes to the call list. */
public interface Listener {
/**
* Called when a new incoming call comes in. This is the only method that gets called for
* incoming calls. Listeners that want to perform an action on incoming call should respond in
* this method because {@link #onCallListChange} does not automatically get called for incoming
* calls.
*/
void onIncomingCall(DialerCall call);
/**
* Called when a new modify call request comes in This is the only method that gets called for
* modify requests.
*/
void onUpgradeToVideo(DialerCall call);
/**
* Called when a new RTT call request comes in This is the only method that gets called for RTT
* requests.
*/
default void onUpgradeToRtt(DialerCall call, int rttRequestId) {}
/** Called when the SpeakEasy state of a Dialer call is mutated. */
default void onSpeakEasyStateChange() {}
/** Called when the session modification state of a call changes. */
void onSessionModificationStateChange(DialerCall call);
/**
* Called anytime there are changes to the call list. The change can be switching call states,
* updating information, etc. This method will NOT be called for new incoming calls and for
* calls that switch to disconnected state. Listeners must add actions to those method
* implementations if they want to deal with those actions.
*/
void onCallListChange(CallList callList);
/**
* Called when a call switches to the disconnected state. This is the only method that will get
* called upon disconnection.
*/
void onDisconnect(DialerCall call);
void onWiFiToLteHandover(DialerCall call);
/**
* Called when a user is in a video call and the call is unable to be handed off successfully to
* WiFi
*/
void onHandoverToWifiFailed(DialerCall call);
/** Called when the user initiates a call to an international number while on WiFi. */
void onInternationalCallOnWifi(@NonNull DialerCall call);
}
/** UiListener interface for measuring incall latency.(used by testing only) */
public interface UiListener {
/** Called when a new call gets added into call list from IncallServiceImpl */
void onCallAdded();
/** Called in the end of onResume method of IncallActivityCommon. */
void onInCallUiShown();
}
private class DialerCallListenerImpl implements DialerCallListener {
@NonNull private final DialerCall call;
DialerCallListenerImpl(@NonNull DialerCall call) {
this.call = Assert.isNotNull(call);
}
@Override
public void onDialerCallDisconnect() {
if (updateCallInMap(call)) {
LogUtil.i("DialerCallListenerImpl.onDialerCallDisconnect", String.valueOf(call));
// notify those listening for all disconnects
notifyListenersOfDisconnect(call);
}
}
@Override
public void onDialerCallUpdate() {
Trace.beginSection("CallList.onDialerCallUpdate");
onUpdateCall(call);
notifyGenericListeners();
Trace.endSection();
}
@Override
public void onDialerCallChildNumberChange() {}
@Override
public void onDialerCallLastForwardedNumberChange() {}
@Override
public void onDialerCallUpgradeToRtt(int rttRequestId) {
for (Listener listener : listeners) {
listener.onUpgradeToRtt(call, rttRequestId);
}
}
@Override
public void onDialerCallSpeakEasyStateChange() {
for (Listener listener : listeners) {
listener.onSpeakEasyStateChange();
}
}
@Override
public void onDialerCallUpgradeToVideo() {
for (Listener listener : listeners) {
listener.onUpgradeToVideo(call);
}
}
@Override
public void onWiFiToLteHandover() {
for (Listener listener : listeners) {
listener.onWiFiToLteHandover(call);
}
}
@Override
public void onHandoverToWifiFailure() {
for (Listener listener : listeners) {
listener.onHandoverToWifiFailed(call);
}
}
@Override
public void onInternationalCallOnWifi() {
LogUtil.enterBlock("DialerCallListenerImpl.onInternationalCallOnWifi");
for (Listener listener : listeners) {
listener.onInternationalCallOnWifi(call);
}
}
@Override
public void onEnrichedCallSessionUpdate() {}
@Override
public void onDialerCallSessionModificationStateChange() {
for (Listener listener : listeners) {
listener.onSessionModificationStateChange(call);
}
}
}
}