You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1194 lines
44 KiB
1194 lines
44 KiB
4 months ago
|
/*
|
||
|
* Copyright (C) 2013 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;
|
||
|
|
||
|
import static com.android.contacts.common.compat.CallCompat.Details.PROPERTY_ENTERPRISE_CALL;
|
||
|
|
||
|
import android.Manifest;
|
||
|
import android.content.Context;
|
||
|
import android.content.Intent;
|
||
|
import android.content.IntentFilter;
|
||
|
import android.content.pm.ApplicationInfo;
|
||
|
import android.content.pm.PackageManager;
|
||
|
import android.graphics.drawable.Drawable;
|
||
|
import android.hardware.display.DisplayManager;
|
||
|
import android.os.BatteryManager;
|
||
|
import android.os.Handler;
|
||
|
import android.os.Trace;
|
||
|
import android.support.annotation.NonNull;
|
||
|
import android.support.annotation.Nullable;
|
||
|
import android.support.v4.app.Fragment;
|
||
|
import android.support.v4.content.ContextCompat;
|
||
|
import android.telecom.Call.Details;
|
||
|
import android.telecom.StatusHints;
|
||
|
import android.telecom.TelecomManager;
|
||
|
import android.text.BidiFormatter;
|
||
|
import android.text.TextDirectionHeuristics;
|
||
|
import android.text.TextUtils;
|
||
|
import android.view.Display;
|
||
|
import android.view.View;
|
||
|
import android.view.accessibility.AccessibilityEvent;
|
||
|
import android.view.accessibility.AccessibilityManager;
|
||
|
import com.android.contacts.common.ContactsUtils;
|
||
|
import com.android.dialer.common.Assert;
|
||
|
import com.android.dialer.common.LogUtil;
|
||
|
import com.android.dialer.configprovider.ConfigProviderComponent;
|
||
|
import com.android.dialer.contacts.ContactsComponent;
|
||
|
import com.android.dialer.logging.DialerImpression;
|
||
|
import com.android.dialer.logging.Logger;
|
||
|
import com.android.dialer.multimedia.MultimediaData;
|
||
|
import com.android.dialer.oem.MotorolaUtils;
|
||
|
import com.android.dialer.phonenumberutil.PhoneNumberHelper;
|
||
|
import com.android.dialer.postcall.PostCall;
|
||
|
import com.android.dialer.preferredsim.suggestion.SuggestionProvider;
|
||
|
import com.android.incallui.ContactInfoCache.ContactCacheEntry;
|
||
|
import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
|
||
|
import com.android.incallui.InCallPresenter.InCallDetailsListener;
|
||
|
import com.android.incallui.InCallPresenter.InCallEventListener;
|
||
|
import com.android.incallui.InCallPresenter.InCallState;
|
||
|
import com.android.incallui.InCallPresenter.InCallStateListener;
|
||
|
import com.android.incallui.InCallPresenter.IncomingCallListener;
|
||
|
import com.android.incallui.call.CallList;
|
||
|
import com.android.incallui.call.DialerCall;
|
||
|
import com.android.incallui.call.DialerCallListener;
|
||
|
import com.android.incallui.call.state.DialerCallState;
|
||
|
import com.android.incallui.calllocation.CallLocation;
|
||
|
import com.android.incallui.calllocation.CallLocationComponent;
|
||
|
import com.android.incallui.incall.protocol.ContactPhotoType;
|
||
|
import com.android.incallui.incall.protocol.InCallScreen;
|
||
|
import com.android.incallui.incall.protocol.InCallScreenDelegate;
|
||
|
import com.android.incallui.incall.protocol.PrimaryCallState;
|
||
|
import com.android.incallui.incall.protocol.PrimaryCallState.ButtonState;
|
||
|
import com.android.incallui.incall.protocol.PrimaryInfo;
|
||
|
import com.android.incallui.incall.protocol.SecondaryInfo;
|
||
|
import com.android.incallui.videotech.utils.SessionModificationState;
|
||
|
import java.lang.ref.WeakReference;
|
||
|
|
||
|
/**
|
||
|
* Controller for the Call Card Fragment. This class listens for changes to InCallState and passes
|
||
|
* it along to the fragment.
|
||
|
*/
|
||
|
public class CallCardPresenter
|
||
|
implements InCallStateListener,
|
||
|
IncomingCallListener,
|
||
|
InCallDetailsListener,
|
||
|
InCallEventListener,
|
||
|
InCallScreenDelegate,
|
||
|
DialerCallListener {
|
||
|
|
||
|
/**
|
||
|
* Amount of time to wait before sending an announcement via the accessibility manager. When the
|
||
|
* call state changes to an outgoing or incoming state for the first time, the UI can often be
|
||
|
* changing due to call updates or contact lookup. This allows the UI to settle to a stable state
|
||
|
* to ensure that the correct information is announced.
|
||
|
*/
|
||
|
private static final long ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS = 500;
|
||
|
|
||
|
/** Flag to allow the user's current location to be shown during emergency calls. */
|
||
|
private static final String CONFIG_ENABLE_EMERGENCY_LOCATION = "config_enable_emergency_location";
|
||
|
|
||
|
private static final boolean CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT = true;
|
||
|
|
||
|
/**
|
||
|
* Make it possible to not get location during an emergency call if the battery is too low, since
|
||
|
* doing so could trigger gps and thus potentially cause the phone to die in the middle of the
|
||
|
* call.
|
||
|
*/
|
||
|
private static final String CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION =
|
||
|
"min_battery_percent_for_emergency_location";
|
||
|
|
||
|
private static final long CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT = 10;
|
||
|
|
||
|
private final Context context;
|
||
|
private final Handler handler = new Handler();
|
||
|
|
||
|
private DialerCall primary;
|
||
|
private String primaryNumber;
|
||
|
private DialerCall secondary;
|
||
|
private String secondaryNumber;
|
||
|
private ContactCacheEntry primaryContactInfo;
|
||
|
private ContactCacheEntry secondaryContactInfo;
|
||
|
private boolean isFullscreen = false;
|
||
|
private InCallScreen inCallScreen;
|
||
|
private boolean isInCallScreenReady;
|
||
|
private boolean shouldSendAccessibilityEvent;
|
||
|
|
||
|
@NonNull private final CallLocation callLocation;
|
||
|
private final Runnable sendAccessibilityEventRunnable =
|
||
|
new Runnable() {
|
||
|
@Override
|
||
|
public void run() {
|
||
|
shouldSendAccessibilityEvent = !sendAccessibilityEvent(context, getUi());
|
||
|
LogUtil.i(
|
||
|
"CallCardPresenter.sendAccessibilityEventRunnable",
|
||
|
"still should send: %b",
|
||
|
shouldSendAccessibilityEvent);
|
||
|
if (!shouldSendAccessibilityEvent) {
|
||
|
handler.removeCallbacks(this);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
public CallCardPresenter(Context context) {
|
||
|
LogUtil.i("CallCardPresenter.constructor", null);
|
||
|
this.context = Assert.isNotNull(context).getApplicationContext();
|
||
|
callLocation = CallLocationComponent.get(this.context).getCallLocation();
|
||
|
}
|
||
|
|
||
|
private static boolean hasCallSubject(DialerCall call) {
|
||
|
return !TextUtils.isEmpty(call.getCallSubject());
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onInCallScreenDelegateInit(InCallScreen inCallScreen) {
|
||
|
Assert.isNotNull(inCallScreen);
|
||
|
this.inCallScreen = inCallScreen;
|
||
|
|
||
|
// Call may be null if disconnect happened already.
|
||
|
DialerCall call = CallList.getInstance().getFirstCall();
|
||
|
if (call != null) {
|
||
|
primary = call;
|
||
|
if (shouldShowNoteSentToast(primary)) {
|
||
|
this.inCallScreen.showNoteSentToast();
|
||
|
}
|
||
|
call.addListener(this);
|
||
|
// start processing lookups right away.
|
||
|
if (!call.isConferenceCall()) {
|
||
|
startContactInfoSearch(call, true, call.getState() == DialerCallState.INCOMING);
|
||
|
} else {
|
||
|
updateContactEntry(null, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onInCallScreenReady() {
|
||
|
LogUtil.i("CallCardPresenter.onInCallScreenReady", null);
|
||
|
Assert.checkState(!isInCallScreenReady);
|
||
|
|
||
|
// Contact search may have completed before ui is ready.
|
||
|
if (primaryContactInfo != null) {
|
||
|
updatePrimaryDisplayInfo();
|
||
|
}
|
||
|
|
||
|
// Register for call state changes last
|
||
|
InCallPresenter.getInstance().addListener(this);
|
||
|
InCallPresenter.getInstance().addIncomingCallListener(this);
|
||
|
InCallPresenter.getInstance().addDetailsListener(this);
|
||
|
InCallPresenter.getInstance().addInCallEventListener(this);
|
||
|
isInCallScreenReady = true;
|
||
|
|
||
|
// Log location impressions
|
||
|
if (isOutgoingEmergencyCall(primary)) {
|
||
|
Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_NEW_EMERGENCY_CALL);
|
||
|
} else if (isIncomingEmergencyCall(primary) || isIncomingEmergencyCall(secondary)) {
|
||
|
Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_CALLBACK);
|
||
|
}
|
||
|
|
||
|
// Showing the location may have been skipped if the UI wasn't ready during previous layout.
|
||
|
if (shouldShowLocation()) {
|
||
|
inCallScreen.showLocationUi(getLocationFragment());
|
||
|
|
||
|
// Log location impressions
|
||
|
if (!hasLocationPermission()) {
|
||
|
Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_NO_LOCATION_PERMISSION);
|
||
|
} else if (isBatteryTooLowForEmergencyLocation()) {
|
||
|
Logger.get(context)
|
||
|
.logImpression(DialerImpression.Type.EMERGENCY_BATTERY_TOO_LOW_TO_GET_LOCATION);
|
||
|
} else if (!callLocation.canGetLocation(context)) {
|
||
|
Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_CANT_GET_LOCATION);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onInCallScreenUnready() {
|
||
|
LogUtil.i("CallCardPresenter.onInCallScreenUnready", null);
|
||
|
Assert.checkState(isInCallScreenReady);
|
||
|
|
||
|
// stop getting call state changes
|
||
|
InCallPresenter.getInstance().removeListener(this);
|
||
|
InCallPresenter.getInstance().removeIncomingCallListener(this);
|
||
|
InCallPresenter.getInstance().removeDetailsListener(this);
|
||
|
InCallPresenter.getInstance().removeInCallEventListener(this);
|
||
|
if (primary != null) {
|
||
|
primary.removeListener(this);
|
||
|
}
|
||
|
|
||
|
callLocation.close();
|
||
|
|
||
|
primary = null;
|
||
|
primaryContactInfo = null;
|
||
|
secondaryContactInfo = null;
|
||
|
isInCallScreenReady = false;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
|
||
|
// same logic should happen as with onStateChange()
|
||
|
onStateChange(oldState, newState, CallList.getInstance());
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
|
||
|
Trace.beginSection("CallCardPresenter.onStateChange");
|
||
|
LogUtil.v("CallCardPresenter.onStateChange", "oldState: %s, newState: %s", oldState, newState);
|
||
|
if (inCallScreen == null) {
|
||
|
Trace.endSection();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DialerCall primary = null;
|
||
|
DialerCall secondary = null;
|
||
|
|
||
|
if (newState == InCallState.INCOMING) {
|
||
|
primary = callList.getIncomingCall();
|
||
|
} else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
|
||
|
primary = callList.getOutgoingCall();
|
||
|
if (primary == null) {
|
||
|
primary = callList.getPendingOutgoingCall();
|
||
|
}
|
||
|
|
||
|
// getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
|
||
|
// highest priority call to display as the secondary call.
|
||
|
secondary = InCallPresenter.getCallToDisplay(callList, null, true);
|
||
|
} else if (newState == InCallState.INCALL) {
|
||
|
primary = InCallPresenter.getCallToDisplay(callList, null, false);
|
||
|
secondary = InCallPresenter.getCallToDisplay(callList, primary, true);
|
||
|
}
|
||
|
|
||
|
LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
|
||
|
LogUtil.v("CallCardPresenter.onStateChange", "secondary call: " + secondary);
|
||
|
String primaryNumber = null;
|
||
|
String secondaryNumber = null;
|
||
|
if (primary != null) {
|
||
|
primaryNumber = primary.getNumber();
|
||
|
}
|
||
|
if (secondary != null) {
|
||
|
secondaryNumber = secondary.getNumber();
|
||
|
}
|
||
|
|
||
|
final boolean primaryChanged =
|
||
|
!(DialerCall.areSame(this.primary, primary)
|
||
|
&& TextUtils.equals(this.primaryNumber, primaryNumber));
|
||
|
final boolean secondaryChanged =
|
||
|
!(DialerCall.areSame(this.secondary, secondary)
|
||
|
&& TextUtils.equals(this.secondaryNumber, secondaryNumber));
|
||
|
|
||
|
this.secondary = secondary;
|
||
|
this.secondaryNumber = secondaryNumber;
|
||
|
DialerCall previousPrimary = this.primary;
|
||
|
this.primary = primary;
|
||
|
this.primaryNumber = primaryNumber;
|
||
|
|
||
|
if (this.primary != null) {
|
||
|
inCallScreen.updateInCallScreenColors();
|
||
|
}
|
||
|
|
||
|
if (primaryChanged && shouldShowNoteSentToast(primary)) {
|
||
|
inCallScreen.showNoteSentToast();
|
||
|
}
|
||
|
|
||
|
// Refresh primary call information if either:
|
||
|
// 1. Primary call changed.
|
||
|
// 2. The call's ability to manage conference has changed.
|
||
|
if (shouldRefreshPrimaryInfo(primaryChanged)) {
|
||
|
// primary call has changed
|
||
|
if (previousPrimary != null) {
|
||
|
previousPrimary.removeListener(this);
|
||
|
}
|
||
|
this.primary.addListener(this);
|
||
|
|
||
|
primaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(context, this.primary);
|
||
|
updatePrimaryDisplayInfo();
|
||
|
maybeStartSearch(this.primary, true);
|
||
|
}
|
||
|
|
||
|
if (previousPrimary != null && this.primary == null) {
|
||
|
previousPrimary.removeListener(this);
|
||
|
}
|
||
|
|
||
|
if (secondaryChanged) {
|
||
|
if (this.secondary == null) {
|
||
|
// Secondary call may have ended. Update the ui.
|
||
|
secondaryContactInfo = null;
|
||
|
updateSecondaryDisplayInfo();
|
||
|
} else {
|
||
|
// secondary call has changed
|
||
|
secondaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(context, this.secondary);
|
||
|
updateSecondaryDisplayInfo();
|
||
|
maybeStartSearch(this.secondary, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Set the call state
|
||
|
int callState = DialerCallState.IDLE;
|
||
|
if (this.primary != null) {
|
||
|
callState = this.primary.getState();
|
||
|
updatePrimaryCallState();
|
||
|
} else {
|
||
|
getUi().setCallState(PrimaryCallState.empty());
|
||
|
}
|
||
|
|
||
|
maybeShowManageConferenceCallButton();
|
||
|
|
||
|
// Hide the end call button instantly if we're receiving an incoming call.
|
||
|
getUi()
|
||
|
.setEndCallButtonEnabled(
|
||
|
shouldShowEndCallButton(this.primary, callState),
|
||
|
callState != DialerCallState.INCOMING /* animate */);
|
||
|
|
||
|
maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
|
||
|
Trace.endSection();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onDetailsChanged(DialerCall call, Details details) {
|
||
|
updatePrimaryCallState();
|
||
|
|
||
|
if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
|
||
|
!= details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
|
||
|
maybeShowManageConferenceCallButton();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onDialerCallDisconnect() {}
|
||
|
|
||
|
@Override
|
||
|
public void onDialerCallUpdate() {
|
||
|
// No-op; specific call updates handled elsewhere.
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onWiFiToLteHandover() {}
|
||
|
|
||
|
@Override
|
||
|
public void onHandoverToWifiFailure() {}
|
||
|
|
||
|
@Override
|
||
|
public void onInternationalCallOnWifi() {}
|
||
|
|
||
|
@Override
|
||
|
public void onEnrichedCallSessionUpdate() {
|
||
|
LogUtil.enterBlock("CallCardPresenter.onEnrichedCallSessionUpdate");
|
||
|
updatePrimaryDisplayInfo();
|
||
|
}
|
||
|
|
||
|
/** Handles a change to the child number by refreshing the primary call info. */
|
||
|
@Override
|
||
|
public void onDialerCallChildNumberChange() {
|
||
|
LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
|
||
|
|
||
|
if (primary == null) {
|
||
|
return;
|
||
|
}
|
||
|
updatePrimaryDisplayInfo();
|
||
|
}
|
||
|
|
||
|
/** Handles a change to the last forwarding number by refreshing the primary call info. */
|
||
|
@Override
|
||
|
public void onDialerCallLastForwardedNumberChange() {
|
||
|
LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
|
||
|
|
||
|
if (primary == null) {
|
||
|
return;
|
||
|
}
|
||
|
updatePrimaryDisplayInfo();
|
||
|
updatePrimaryCallState();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onDialerCallUpgradeToVideo() {}
|
||
|
|
||
|
/** Handles a change to the session modification state for a call. */
|
||
|
@Override
|
||
|
public void onDialerCallSessionModificationStateChange() {
|
||
|
LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
|
||
|
|
||
|
if (primary == null) {
|
||
|
return;
|
||
|
}
|
||
|
getUi()
|
||
|
.setEndCallButtonEnabled(
|
||
|
primary.getVideoTech().getSessionModificationState()
|
||
|
!= SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
|
||
|
true /* shouldAnimate */);
|
||
|
updatePrimaryCallState();
|
||
|
}
|
||
|
|
||
|
private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
|
||
|
if (primary == null) {
|
||
|
return false;
|
||
|
}
|
||
|
return primaryChanged
|
||
|
|| inCallScreen.isManageConferenceVisible() != shouldShowManageConference();
|
||
|
}
|
||
|
|
||
|
private void updatePrimaryCallState() {
|
||
|
if (getUi() != null && primary != null) {
|
||
|
boolean isWorkCall =
|
||
|
primary.hasProperty(PROPERTY_ENTERPRISE_CALL)
|
||
|
|| (primaryContactInfo != null
|
||
|
&& primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
|
||
|
boolean isHdAudioCall =
|
||
|
isPrimaryCallActive() && primary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
|
||
|
boolean isAttemptingHdAudioCall =
|
||
|
!isHdAudioCall
|
||
|
&& !primary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
|
||
|
&& MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(context);
|
||
|
|
||
|
boolean isBusiness = primaryContactInfo != null && primaryContactInfo.isBusiness;
|
||
|
|
||
|
// Check for video state change and update the visibility of the contact photo. The contact
|
||
|
// photo is hidden when the incoming video surface is shown.
|
||
|
// The contact photo visibility can also change in setPrimary().
|
||
|
boolean shouldShowContactPhoto =
|
||
|
!VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());
|
||
|
getUi()
|
||
|
.setCallState(
|
||
|
PrimaryCallState.builder()
|
||
|
.setState(primary.getState())
|
||
|
.setIsVideoCall(primary.isVideoCall())
|
||
|
.setSessionModificationState(primary.getVideoTech().getSessionModificationState())
|
||
|
.setDisconnectCause(primary.getDisconnectCause())
|
||
|
.setConnectionLabel(getConnectionLabel())
|
||
|
.setPrimaryColor(
|
||
|
InCallPresenter.getInstance().getThemeColorManager().getPrimaryColor())
|
||
|
.setSimSuggestionReason(getSimSuggestionReason())
|
||
|
.setConnectionIcon(getCallStateIcon())
|
||
|
.setGatewayNumber(getGatewayNumber())
|
||
|
.setCallSubject(shouldShowCallSubject(primary) ? primary.getCallSubject() : null)
|
||
|
.setCallbackNumber(
|
||
|
PhoneNumberHelper.formatNumber(
|
||
|
context, primary.getCallbackNumber(), primary.getSimCountryIso()))
|
||
|
.setIsWifi(primary.hasProperty(Details.PROPERTY_WIFI))
|
||
|
.setIsConference(
|
||
|
primary.isConferenceCall()
|
||
|
&& !primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE))
|
||
|
.setIsWorkCall(isWorkCall)
|
||
|
.setIsHdAttempting(isAttemptingHdAudioCall)
|
||
|
.setIsHdAudioCall(isHdAudioCall)
|
||
|
.setIsForwardedNumber(
|
||
|
!TextUtils.isEmpty(primary.getLastForwardedNumber())
|
||
|
|| primary.isCallForwarded())
|
||
|
.setShouldShowContactPhoto(shouldShowContactPhoto)
|
||
|
.setConnectTimeMillis(primary.getConnectTimeMillis())
|
||
|
.setIsVoiceMailNumber(primary.isVoiceMailNumber())
|
||
|
.setIsRemotelyHeld(primary.isRemotelyHeld())
|
||
|
.setIsBusinessNumber(isBusiness)
|
||
|
.setSupportsCallOnHold(supports2ndCallOnHold())
|
||
|
.setSwapToSecondaryButtonState(getSwapToSecondaryButtonState())
|
||
|
.setIsAssistedDialed(primary.isAssistedDialed())
|
||
|
.setCustomLabel(null)
|
||
|
.setAssistedDialingExtras(primary.getAssistedDialingExtras())
|
||
|
.build());
|
||
|
|
||
|
InCallActivity activity =
|
||
|
(InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
|
||
|
if (activity != null) {
|
||
|
activity.onPrimaryCallStateChanged();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private @ButtonState int getSwapToSecondaryButtonState() {
|
||
|
if (secondary == null) {
|
||
|
return ButtonState.NOT_SUPPORT;
|
||
|
}
|
||
|
if (primary.getState() == DialerCallState.ACTIVE) {
|
||
|
return ButtonState.ENABLED;
|
||
|
}
|
||
|
return ButtonState.DISABLED;
|
||
|
}
|
||
|
|
||
|
/** Only show the conference call button if we can manage the conference. */
|
||
|
private void maybeShowManageConferenceCallButton() {
|
||
|
getUi().showManageConferenceCallButton(shouldShowManageConference());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determines if the manage conference button should be visible, based on the current primary
|
||
|
* call.
|
||
|
*
|
||
|
* @return {@code True} if the manage conference button should be visible.
|
||
|
*/
|
||
|
private boolean shouldShowManageConference() {
|
||
|
if (primary == null) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return primary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) && !isFullscreen;
|
||
|
}
|
||
|
|
||
|
private boolean supports2ndCallOnHold() {
|
||
|
DialerCall firstCall = CallList.getInstance().getActiveOrBackgroundCall();
|
||
|
DialerCall incomingCall = CallList.getInstance().getIncomingCall();
|
||
|
if (firstCall != null && incomingCall != null && firstCall != incomingCall) {
|
||
|
return incomingCall.can(Details.CAPABILITY_HOLD);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onCallStateButtonClicked() {
|
||
|
Intent broadcastIntent = Bindings.get(context).getCallStateButtonBroadcastIntent(context);
|
||
|
if (broadcastIntent != null) {
|
||
|
LogUtil.v(
|
||
|
"CallCardPresenter.onCallStateButtonClicked",
|
||
|
"sending call state button broadcast: " + broadcastIntent);
|
||
|
context.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onManageConferenceClicked() {
|
||
|
InCallActivity activity =
|
||
|
(InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
|
||
|
activity.showConferenceFragment(true);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onShrinkAnimationComplete() {
|
||
|
InCallPresenter.getInstance().onShrinkAnimationComplete();
|
||
|
}
|
||
|
|
||
|
private void maybeStartSearch(DialerCall call, boolean isPrimary) {
|
||
|
// no need to start search for conference calls which show generic info.
|
||
|
if (call != null && !call.isConferenceCall()) {
|
||
|
startContactInfoSearch(call, isPrimary, call.getState() == DialerCallState.INCOMING);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Starts a query for more contact data for the save primary and secondary calls. */
|
||
|
private void startContactInfoSearch(
|
||
|
final DialerCall call, final boolean isPrimary, boolean isIncoming) {
|
||
|
final ContactInfoCache cache = ContactInfoCache.getInstance(context);
|
||
|
|
||
|
cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
|
||
|
}
|
||
|
|
||
|
private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
|
||
|
final boolean entryMatchesExistingCall =
|
||
|
(isPrimary && primary != null && TextUtils.equals(callId, primary.getId()))
|
||
|
|| (!isPrimary && secondary != null && TextUtils.equals(callId, secondary.getId()));
|
||
|
if (entryMatchesExistingCall) {
|
||
|
updateContactEntry(entry, isPrimary);
|
||
|
} else {
|
||
|
LogUtil.e(
|
||
|
"CallCardPresenter.onContactInfoComplete",
|
||
|
"dropping stale contact lookup info for " + callId);
|
||
|
}
|
||
|
|
||
|
final DialerCall call = CallList.getInstance().getCallById(callId);
|
||
|
if (call != null) {
|
||
|
call.getLogState().contactLookupResult = entry.contactLookupResult;
|
||
|
}
|
||
|
if (entry.lookupUri != null) {
|
||
|
CallerInfoUtils.sendViewNotification(context, entry.lookupUri);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
|
||
|
if (getUi() == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (entry.photo != null) {
|
||
|
if (primary != null && callId.equals(primary.getId())) {
|
||
|
updateContactEntry(entry, true /* isPrimary */);
|
||
|
} else if (secondary != null && callId.equals(secondary.getId())) {
|
||
|
updateContactEntry(entry, false /* isPrimary */);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
|
||
|
if (isPrimary) {
|
||
|
primaryContactInfo = entry;
|
||
|
updatePrimaryDisplayInfo();
|
||
|
} else {
|
||
|
secondaryContactInfo = entry;
|
||
|
updateSecondaryDisplayInfo();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void updatePrimaryDisplayInfo() {
|
||
|
if (inCallScreen == null) {
|
||
|
// TODO: May also occur if search result comes back after ui is destroyed. Look into
|
||
|
// removing that case completely.
|
||
|
LogUtil.v(
|
||
|
"CallCardPresenter.updatePrimaryDisplayInfo",
|
||
|
"updatePrimaryDisplayInfo called but ui is null!");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (primary == null) {
|
||
|
// Clear the primary display info.
|
||
|
inCallScreen.setPrimary(PrimaryInfo.empty());
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Hide the contact photo if we are in a video call and the incoming video surface is
|
||
|
// showing.
|
||
|
boolean showContactPhoto =
|
||
|
!VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());
|
||
|
|
||
|
// DialerCall placed through a work phone account.
|
||
|
boolean hasWorkCallProperty = primary.hasProperty(PROPERTY_ENTERPRISE_CALL);
|
||
|
|
||
|
MultimediaData multimediaData = null;
|
||
|
if (primary.getEnrichedCallSession() != null) {
|
||
|
multimediaData = primary.getEnrichedCallSession().getMultimediaData();
|
||
|
}
|
||
|
|
||
|
if (primary.isConferenceCall()) {
|
||
|
LogUtil.v(
|
||
|
"CallCardPresenter.updatePrimaryDisplayInfo",
|
||
|
"update primary display info for conference call.");
|
||
|
|
||
|
inCallScreen.setPrimary(
|
||
|
PrimaryInfo.builder()
|
||
|
.setName(
|
||
|
CallerInfoUtils.getConferenceString(
|
||
|
context, primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)))
|
||
|
.setNameIsNumber(false)
|
||
|
.setPhotoType(ContactPhotoType.DEFAULT_PLACEHOLDER)
|
||
|
.setIsSipCall(false)
|
||
|
.setIsContactPhotoShown(showContactPhoto)
|
||
|
.setIsWorkCall(hasWorkCallProperty)
|
||
|
.setIsSpam(false)
|
||
|
.setIsLocalContact(false)
|
||
|
.setAnsweringDisconnectsOngoingCall(false)
|
||
|
.setShouldShowLocation(shouldShowLocation())
|
||
|
.setShowInCallButtonGrid(true)
|
||
|
.setNumberPresentation(primary.getNumberPresentation())
|
||
|
.build());
|
||
|
} else if (primaryContactInfo != null) {
|
||
|
LogUtil.v(
|
||
|
"CallCardPresenter.updatePrimaryDisplayInfo",
|
||
|
"update primary display info for " + primaryContactInfo);
|
||
|
|
||
|
String name = getNameForCall(primaryContactInfo);
|
||
|
String number;
|
||
|
|
||
|
boolean isChildNumberShown = !TextUtils.isEmpty(primary.getChildNumber());
|
||
|
boolean isForwardedNumberShown = !TextUtils.isEmpty(primary.getLastForwardedNumber());
|
||
|
boolean isCallSubjectShown = shouldShowCallSubject(primary);
|
||
|
|
||
|
if (isCallSubjectShown) {
|
||
|
number = null;
|
||
|
} else if (isChildNumberShown) {
|
||
|
number = context.getString(R.string.child_number, primary.getChildNumber());
|
||
|
} else if (isForwardedNumberShown) {
|
||
|
// Use last forwarded number instead of second line, if present.
|
||
|
number = primary.getLastForwardedNumber();
|
||
|
} else {
|
||
|
number = primaryContactInfo.number;
|
||
|
}
|
||
|
|
||
|
boolean nameIsNumber = name != null && name.equals(primaryContactInfo.number);
|
||
|
|
||
|
// DialerCall with caller that is a work contact.
|
||
|
boolean isWorkContact = (primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
|
||
|
inCallScreen.setPrimary(
|
||
|
PrimaryInfo.builder()
|
||
|
.setNumber(number)
|
||
|
.setName(primary.updateNameIfRestricted(name))
|
||
|
.setNameIsNumber(nameIsNumber)
|
||
|
.setLocation(
|
||
|
shouldShowLocationAsLabel(nameIsNumber, primaryContactInfo.shouldShowLocation)
|
||
|
? primaryContactInfo.location
|
||
|
: null)
|
||
|
.setLabel(isChildNumberShown || isCallSubjectShown ? null : primaryContactInfo.label)
|
||
|
.setPhoto(primaryContactInfo.photo)
|
||
|
.setPhotoUri(primaryContactInfo.displayPhotoUri)
|
||
|
.setPhotoType(primaryContactInfo.photoType)
|
||
|
.setIsSipCall(primaryContactInfo.isSipCall)
|
||
|
.setIsContactPhotoShown(showContactPhoto)
|
||
|
.setIsWorkCall(hasWorkCallProperty || isWorkContact)
|
||
|
.setIsSpam(primary.isSpam())
|
||
|
.setIsLocalContact(primaryContactInfo.isLocalContact())
|
||
|
.setAnsweringDisconnectsOngoingCall(primary.answeringDisconnectsForegroundVideoCall())
|
||
|
.setShouldShowLocation(shouldShowLocation())
|
||
|
.setContactInfoLookupKey(primaryContactInfo.lookupKey)
|
||
|
.setMultimediaData(multimediaData)
|
||
|
.setShowInCallButtonGrid(true)
|
||
|
.setNumberPresentation(primary.getNumberPresentation())
|
||
|
.build());
|
||
|
} else {
|
||
|
// Clear the primary display info.
|
||
|
inCallScreen.setPrimary(PrimaryInfo.empty());
|
||
|
}
|
||
|
|
||
|
if (isInCallScreenReady) {
|
||
|
inCallScreen.showLocationUi(getLocationFragment());
|
||
|
} else {
|
||
|
LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static boolean shouldShowLocationAsLabel(
|
||
|
boolean nameIsNumber, boolean shouldShowLocation) {
|
||
|
if (nameIsNumber) {
|
||
|
return true;
|
||
|
}
|
||
|
if (shouldShowLocation) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private Fragment getLocationFragment() {
|
||
|
if (!shouldShowLocation()) {
|
||
|
return null;
|
||
|
}
|
||
|
LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
|
||
|
return callLocation.getLocationFragment(context);
|
||
|
}
|
||
|
|
||
|
private boolean shouldShowLocation() {
|
||
|
if (!ConfigProviderComponent.get(context)
|
||
|
.getConfigProvider()
|
||
|
.getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
|
||
|
LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
|
||
|
return false;
|
||
|
}
|
||
|
if (!isPotentialEmergencyCall()) {
|
||
|
LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
|
||
|
return false;
|
||
|
}
|
||
|
if (!hasLocationPermission()) {
|
||
|
LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
|
||
|
return false;
|
||
|
}
|
||
|
if (isBatteryTooLowForEmergencyLocation()) {
|
||
|
LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
|
||
|
return false;
|
||
|
}
|
||
|
if (inCallScreen.getInCallScreenFragment().getActivity().isInMultiWindowMode()) {
|
||
|
LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
|
||
|
return false;
|
||
|
}
|
||
|
if (primary.isVideoCall()) {
|
||
|
LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
|
||
|
return false;
|
||
|
}
|
||
|
if (!callLocation.canGetLocation(context)) {
|
||
|
LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private boolean isPotentialEmergencyCall() {
|
||
|
if (isOutgoingEmergencyCall(primary)) {
|
||
|
LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
|
||
|
return true;
|
||
|
} else if (isIncomingEmergencyCall(primary)) {
|
||
|
LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
|
||
|
return true;
|
||
|
} else if (isIncomingEmergencyCall(secondary)) {
|
||
|
LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
|
||
|
return call != null && !call.isIncoming() && call.isEmergencyCall();
|
||
|
}
|
||
|
|
||
|
private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
|
||
|
return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
|
||
|
}
|
||
|
|
||
|
private boolean hasLocationPermission() {
|
||
|
return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
|
||
|
== PackageManager.PERMISSION_GRANTED;
|
||
|
}
|
||
|
|
||
|
private boolean isBatteryTooLowForEmergencyLocation() {
|
||
|
Intent batteryStatus =
|
||
|
context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
|
||
|
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
|
||
|
if (status == BatteryManager.BATTERY_STATUS_CHARGING
|
||
|
|| status == BatteryManager.BATTERY_STATUS_FULL) {
|
||
|
// Plugged in or full battery
|
||
|
return false;
|
||
|
}
|
||
|
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
|
||
|
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
|
||
|
float batteryPercent = (100f * level) / scale;
|
||
|
long threshold =
|
||
|
ConfigProviderComponent.get(context)
|
||
|
.getConfigProvider()
|
||
|
.getLong(
|
||
|
CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
|
||
|
CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
|
||
|
LogUtil.i(
|
||
|
"CallCardPresenter.isBatteryTooLowForEmergencyLocation",
|
||
|
"percent charged: " + batteryPercent + ", min required charge: " + threshold);
|
||
|
return batteryPercent < threshold;
|
||
|
}
|
||
|
|
||
|
private void updateSecondaryDisplayInfo() {
|
||
|
if (inCallScreen == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (secondary == null) {
|
||
|
// Clear the secondary display info.
|
||
|
inCallScreen.setSecondary(SecondaryInfo.builder().setIsFullscreen(isFullscreen).build());
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (secondary.isMergeInProcess()) {
|
||
|
LogUtil.i(
|
||
|
"CallCardPresenter.updateSecondaryDisplayInfo",
|
||
|
"secondary call is merge in process, clearing info");
|
||
|
inCallScreen.setSecondary(SecondaryInfo.builder().setIsFullscreen(isFullscreen).build());
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (secondary.isConferenceCall()) {
|
||
|
inCallScreen.setSecondary(
|
||
|
SecondaryInfo.builder()
|
||
|
.setShouldShow(true)
|
||
|
.setName(
|
||
|
CallerInfoUtils.getConferenceString(
|
||
|
context, secondary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)))
|
||
|
.setProviderLabel(secondary.getCallProviderLabel())
|
||
|
.setIsConference(true)
|
||
|
.setIsVideoCall(secondary.isVideoCall())
|
||
|
.setIsFullscreen(isFullscreen)
|
||
|
.build());
|
||
|
} else if (secondaryContactInfo != null) {
|
||
|
LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + secondaryContactInfo);
|
||
|
String name = getNameForCall(secondaryContactInfo);
|
||
|
boolean nameIsNumber = name != null && name.equals(secondaryContactInfo.number);
|
||
|
inCallScreen.setSecondary(
|
||
|
SecondaryInfo.builder()
|
||
|
.setShouldShow(true)
|
||
|
.setName(secondary.updateNameIfRestricted(name))
|
||
|
.setNameIsNumber(nameIsNumber)
|
||
|
.setLabel(secondaryContactInfo.label)
|
||
|
.setProviderLabel(secondary.getCallProviderLabel())
|
||
|
.setIsVideoCall(secondary.isVideoCall())
|
||
|
.setIsFullscreen(isFullscreen)
|
||
|
.build());
|
||
|
} else {
|
||
|
// Clear the secondary display info.
|
||
|
inCallScreen.setSecondary(SecondaryInfo.builder().setIsFullscreen(isFullscreen).build());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Returns the gateway number for any existing outgoing call. */
|
||
|
private String getGatewayNumber() {
|
||
|
if (hasOutgoingGatewayCall()) {
|
||
|
return DialerCall.getNumberFromHandle(primary.getGatewayInfo().getGatewayAddress());
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the label (line of text above the number/name) for any given call. For example,
|
||
|
* "calling via [Account/Google Voice]" for outgoing calls.
|
||
|
*/
|
||
|
private String getConnectionLabel() {
|
||
|
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)
|
||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||
|
return null;
|
||
|
}
|
||
|
StatusHints statusHints = primary.getStatusHints();
|
||
|
if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
|
||
|
return statusHints.getLabel().toString();
|
||
|
}
|
||
|
|
||
|
if (hasOutgoingGatewayCall() && getUi() != null) {
|
||
|
// Return the label for the gateway app on outgoing calls.
|
||
|
final PackageManager pm = context.getPackageManager();
|
||
|
try {
|
||
|
ApplicationInfo info =
|
||
|
pm.getApplicationInfo(primary.getGatewayInfo().getGatewayProviderPackageName(), 0);
|
||
|
return pm.getApplicationLabel(info).toString();
|
||
|
} catch (PackageManager.NameNotFoundException e) {
|
||
|
LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
return primary.getCallProviderLabel();
|
||
|
}
|
||
|
|
||
|
@Nullable
|
||
|
private SuggestionProvider.Reason getSimSuggestionReason() {
|
||
|
String value =
|
||
|
primary.getIntentExtras().getString(SuggestionProvider.EXTRA_SIM_SUGGESTION_REASON);
|
||
|
if (value == null) {
|
||
|
return null;
|
||
|
}
|
||
|
try {
|
||
|
return SuggestionProvider.Reason.valueOf(value);
|
||
|
} catch (IllegalArgumentException e) {
|
||
|
LogUtil.e("CallCardPresenter.getConnectionLabel", "unknown reason " + value);
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private Drawable getCallStateIcon() {
|
||
|
// Return connection icon if one exists.
|
||
|
StatusHints statusHints = primary.getStatusHints();
|
||
|
if (statusHints != null && statusHints.getIcon() != null) {
|
||
|
Drawable icon = statusHints.getIcon().loadDrawable(context);
|
||
|
if (icon != null) {
|
||
|
return icon;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
private boolean hasOutgoingGatewayCall() {
|
||
|
// We only display the gateway information while STATE_DIALING so return false for any other
|
||
|
// call state.
|
||
|
// TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
|
||
|
// is also called after a contact search completes (call is not present yet). Split the
|
||
|
// UI update so it can receive independent updates.
|
||
|
if (primary == null) {
|
||
|
return false;
|
||
|
}
|
||
|
return DialerCallState.isDialing(primary.getState())
|
||
|
&& primary.getGatewayInfo() != null
|
||
|
&& !primary.getGatewayInfo().isEmpty();
|
||
|
}
|
||
|
|
||
|
/** Gets the name to display for the call. */
|
||
|
private String getNameForCall(ContactCacheEntry contactInfo) {
|
||
|
String preferredName =
|
||
|
ContactsComponent.get(context)
|
||
|
.contactDisplayPreferences()
|
||
|
.getDisplayName(contactInfo.namePrimary, contactInfo.nameAlternative);
|
||
|
if (TextUtils.isEmpty(preferredName)) {
|
||
|
return TextUtils.isEmpty(contactInfo.number)
|
||
|
? null
|
||
|
: BidiFormatter.getInstance()
|
||
|
.unicodeWrap(contactInfo.number, TextDirectionHeuristics.LTR);
|
||
|
}
|
||
|
return preferredName;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onSecondaryInfoClicked() {
|
||
|
if (secondary == null) {
|
||
|
LogUtil.e(
|
||
|
"CallCardPresenter.onSecondaryInfoClicked",
|
||
|
"secondary info clicked but no secondary call.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Logger.get(context)
|
||
|
.logCallImpression(
|
||
|
DialerImpression.Type.IN_CALL_SWAP_SECONDARY_BUTTON_PRESSED,
|
||
|
primary.getUniqueCallId(),
|
||
|
primary.getTimeAddedMs());
|
||
|
LogUtil.i(
|
||
|
"CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + secondary);
|
||
|
secondary.unhold();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onEndCallClicked() {
|
||
|
LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + primary);
|
||
|
if (primary != null) {
|
||
|
primary.disconnect();
|
||
|
}
|
||
|
PostCall.onDisconnectPressed(context);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Handles a change to the fullscreen mode of the in-call UI.
|
||
|
*
|
||
|
* @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
|
||
|
*/
|
||
|
@Override
|
||
|
public void onFullscreenModeChanged(boolean isFullscreenMode) {
|
||
|
isFullscreen = isFullscreenMode;
|
||
|
if (inCallScreen == null) {
|
||
|
return;
|
||
|
}
|
||
|
maybeShowManageConferenceCallButton();
|
||
|
}
|
||
|
|
||
|
private boolean isPrimaryCallActive() {
|
||
|
return primary != null && primary.getState() == DialerCallState.ACTIVE;
|
||
|
}
|
||
|
|
||
|
private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
|
||
|
if (primary == null) {
|
||
|
return false;
|
||
|
}
|
||
|
if ((!DialerCallState.isConnectingOrConnected(callState)
|
||
|
&& callState != DialerCallState.DISCONNECTING
|
||
|
&& callState != DialerCallState.DISCONNECTED)
|
||
|
|| callState == DialerCallState.INCOMING) {
|
||
|
return false;
|
||
|
}
|
||
|
if (this.primary.getVideoTech().getSessionModificationState()
|
||
|
== SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onInCallScreenResumed() {
|
||
|
updatePrimaryDisplayInfo();
|
||
|
|
||
|
if (shouldSendAccessibilityEvent) {
|
||
|
handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onInCallScreenPaused() {}
|
||
|
|
||
|
static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
|
||
|
AccessibilityManager am =
|
||
|
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||
|
if (!am.isEnabled()) {
|
||
|
LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
|
||
|
return false;
|
||
|
}
|
||
|
if (inCallScreen == null) {
|
||
|
LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
|
||
|
return false;
|
||
|
}
|
||
|
Fragment fragment = inCallScreen.getInCallScreenFragment();
|
||
|
if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
|
||
|
LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
DisplayManager displayManager =
|
||
|
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
|
||
|
Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
|
||
|
boolean screenIsOn = display.getState() == Display.STATE_ON;
|
||
|
LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
|
||
|
if (!screenIsOn) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
|
||
|
inCallScreen.dispatchPopulateAccessibilityEvent(event);
|
||
|
View view = inCallScreen.getInCallScreenFragment().getView();
|
||
|
view.getParent().requestSendAccessibilityEvent(view, event);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private void maybeSendAccessibilityEvent(
|
||
|
InCallState oldState, final InCallState newState, boolean primaryChanged) {
|
||
|
shouldSendAccessibilityEvent = false;
|
||
|
if (context == null) {
|
||
|
return;
|
||
|
}
|
||
|
final AccessibilityManager am =
|
||
|
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||
|
if (!am.isEnabled()) {
|
||
|
return;
|
||
|
}
|
||
|
// Announce the current call if it's new incoming/outgoing call or primary call is changed
|
||
|
// due to switching calls between two ongoing calls (one is on hold).
|
||
|
if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
|
||
|
|| (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
|
||
|
|| primaryChanged) {
|
||
|
LogUtil.i(
|
||
|
"CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
|
||
|
shouldSendAccessibilityEvent = true;
|
||
|
handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determines whether the call subject should be visible on the UI. For the call subject to be
|
||
|
* visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
|
||
|
*
|
||
|
* @param call The call.
|
||
|
* @return {@code true} if the subject should be shown, {@code false} otherwise.
|
||
|
*/
|
||
|
private boolean shouldShowCallSubject(DialerCall call) {
|
||
|
if (call == null) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
boolean isIncomingOrWaiting =
|
||
|
primary.getState() == DialerCallState.INCOMING
|
||
|
|| primary.getState() == DialerCallState.CALL_WAITING;
|
||
|
return isIncomingOrWaiting
|
||
|
&& !TextUtils.isEmpty(call.getCallSubject())
|
||
|
&& call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
|
||
|
&& call.isCallSubjectSupported();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
|
||
|
* call with a subject.
|
||
|
*
|
||
|
* @param call The call
|
||
|
* @return {@code true} if the toast should be shown, {@code false} otherwise.
|
||
|
*/
|
||
|
private boolean shouldShowNoteSentToast(DialerCall call) {
|
||
|
return call != null
|
||
|
&& hasCallSubject(call)
|
||
|
&& (call.getState() == DialerCallState.DIALING
|
||
|
|| call.getState() == DialerCallState.CONNECTING);
|
||
|
}
|
||
|
|
||
|
private InCallScreen getUi() {
|
||
|
return inCallScreen;
|
||
|
}
|
||
|
|
||
|
/** Callback for contact lookup. */
|
||
|
public static class ContactLookupCallback implements ContactInfoCacheCallback {
|
||
|
|
||
|
private final WeakReference<CallCardPresenter> callCardPresenter;
|
||
|
private final boolean isPrimary;
|
||
|
|
||
|
public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
|
||
|
this.callCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
|
||
|
this.isPrimary = isPrimary;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
|
||
|
CallCardPresenter presenter = callCardPresenter.get();
|
||
|
if (presenter != null) {
|
||
|
presenter.onContactInfoComplete(callId, entry, isPrimary);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
|
||
|
CallCardPresenter presenter = callCardPresenter.get();
|
||
|
if (presenter != null) {
|
||
|
presenter.onImageLoadComplete(callId, entry);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|