Merge "Remove hidden Telephony API usages from Telecom"
diff --git a/Android.bp b/Android.bp
index 629c95c..a4cb2ca 100644
--- a/Android.bp
+++ b/Android.bp
@@ -33,6 +33,7 @@
"androidx.legacy_legacy-support-core-utils",
"androidx.core_core",
"androidx.fragment_fragment",
+ "androidx.test.ext.junit"
],
srcs: [
"tests/src/**/*.java",
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3112abe..2c6a84a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -335,6 +335,11 @@
<string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt">Call Blocking has been disabled to allow emergency responders to contact you.</string>
<!-- Window title used for the Telecom Developer Menu -->
<string name="developer_title">Telecom Developer Menu</string>
+ <!-- Some carriers allow an "external" call that has been placed on another device (another
+ tablet, phone, etc...) to be transferred to this device and taken as a call. In
+ the situation that we are already in an emergency call, notify the user that the call can not
+ be taken at this time.-->
+ <string name="toast_emergency_can_not_pull_call">Calls can not be taken while in an emergency call.</string>
<!-- Label for a switch in the Telecom Developer Menu which is used to enable the enhanced call
blocking functionality (for test purposes).
DO NOT TRANSLATE -->
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 6e48f56..e48cc84 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -58,6 +58,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.IVideoProvider;
import com.android.internal.util.Preconditions;
+import com.android.server.telecom.ui.ToastFactory;
import java.io.IOException;
import java.text.SimpleDateFormat;
@@ -435,6 +436,7 @@
private final Context mContext;
private final CallsManager mCallsManager;
private final ClockProxy mClockProxy;
+ private final ToastFactory mToastFactory;
private final TelecomSystem.SyncRoot mLock;
private final String mId;
private String mConnectionId;
@@ -560,6 +562,17 @@
private boolean mIsUsingCallFiltering = false;
/**
+ * Indicates whether or not this call has been active before. This is helpful in detecting
+ * situations where we have moved into {@link CallState#SIMULATED_RINGING} or
+ * {@link CallState#AUDIO_PROCESSING} again after being active. If a call has moved into one
+ * of these states again after being active and the user dials an emergency call, we want to
+ * log these calls normally instead of considering them MISSED. If the emergency call was
+ * dialed during initial screening however, we want to treat those calls as MISSED (because the
+ * user never got the chance to explicitly reject).
+ */
+ private boolean mHasGoneActiveBefore = false;
+
+ /**
* Persists the specified parameters and initializes the new instance.
* @param context The context.
* @param repository The connection service repository.
@@ -589,7 +602,8 @@
int callDirection,
boolean shouldAttachToExistingConnection,
boolean isConference,
- ClockProxy clockProxy) {
+ ClockProxy clockProxy,
+ ToastFactory toastFactory) {
mId = callId;
mConnectionId = callId;
mState = isConference ? CallState.ACTIVE : CallState.NEW;
@@ -610,6 +624,7 @@
|| callDirection == CALL_DIRECTION_INCOMING;
maybeLoadCannedSmsResponses();
mClockProxy = clockProxy;
+ mToastFactory = toastFactory;
mCreationTimeMillis = mClockProxy.currentTimeMillis();
}
@@ -647,11 +662,12 @@
boolean isConference,
long connectTimeMillis,
long connectElapsedTimeMillis,
- ClockProxy clockProxy) {
+ ClockProxy clockProxy,
+ ToastFactory toastFactory) {
this(callId, context, callsManager, lock, repository,
phoneNumberUtilsAdapter, handle, gatewayInfo,
connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
- shouldAttachToExistingConnection, isConference, clockProxy);
+ shouldAttachToExistingConnection, isConference, clockProxy, toastFactory);
mConnectTimeMillis = connectTimeMillis;
mConnectElapsedTimeMillis = connectElapsedTimeMillis;
@@ -943,6 +959,7 @@
// We're clearly not disconnected, so reset the disconnected time.
mDisconnectTimeMillis = 0;
mDisconnectElapsedTimeMillis = 0;
+ mHasGoneActiveBefore = true;
} else if (mState == CallState.DISCONNECTED) {
mDisconnectTimeMillis = mClockProxy.currentTimeMillis();
mDisconnectElapsedTimeMillis = mClockProxy.elapsedRealtime();
@@ -1944,10 +1961,11 @@
Log.v(this, "Aborting call %s", this);
abort(disconnectionTimeout);
} else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
- if (mState == CallState.AUDIO_PROCESSING) {
+ if (mState == CallState.AUDIO_PROCESSING && !hasGoneActiveBefore()) {
mOverrideDisconnectCauseCode = DisconnectCause.REJECTED;
} else if (mState == CallState.SIMULATED_RINGING) {
// This is the case where the dialer calls disconnect() because the call timed out
+ // or an emergency call was dialed while in this state.
// Override the disconnect cause to MISSED
mOverrideDisconnectCauseCode = DisconnectCause.MISSED;
}
@@ -2389,6 +2407,7 @@
* device, so will issue a request to pull the call to the second device.
* <p>
* Requests to pull a call which is not external, or a call which is not pullable are ignored.
+ * If there is an ongoing emergency call, pull requests are also ignored.
*/
public void pullExternalCall() {
if (mConnectionService == null) {
@@ -2404,6 +2423,15 @@
Log.w(this, "pullExternalCall - call %s is external but cannot be pulled.", mId);
return;
}
+
+ if (mCallsManager.isInEmergencyCall()) {
+ Log.w(this, "pullExternalCall = pullExternalCall - call %s is external but can not be"
+ + " pulled while an emergency call is in progress.", mId);
+ mToastFactory.makeText(mContext, R.string.toast_emergency_can_not_pull_call,
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+
Log.addEvent(this, LogUtils.Events.REQUEST_PULL);
mConnectionService.pullExternalCall(this);
}
@@ -2438,8 +2466,9 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- Toast.makeText(mContext, "WARNING: Event-based handover APIs are deprecated "
- + "and will no longer function in Android Q.",
+ mToastFactory.makeText(mContext,
+ "WARNING: Event-based handover APIs are deprecated and will no"
+ + " longer function in Android Q.",
Toast.LENGTH_LONG).show();
}
});
@@ -3317,6 +3346,18 @@
}
/**
+ * In some cases, we need to know if this call has ever gone active (for example, the case
+ * when the call was put into the {@link CallState#AUDIO_PROCESSING} state after being active)
+ * for call logging purposes.
+ *
+ * @return {@code true} if this call has gone active before (even if it isn't now), false if it
+ * has never gone active.
+ */
+ public boolean hasGoneActiveBefore() {
+ return mHasGoneActiveBefore;
+ }
+
+ /**
* When upgrading a call to video via
* {@link VideoProviderProxy#onSendSessionModifyRequest(VideoProfile, VideoProfile)}, if the
* upgrade is from audio to video, potentially auto-engage the speakerphone.
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 87ca26c..3a84407 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -363,7 +363,7 @@
@VisibleForTesting
public void toggleMute() {
// Don't mute if there are any emergency calls.
- if (mCallsManager.hasEmergencyCall()) {
+ if (mCallsManager.isInEmergencyCall()) {
Log.v(this, "ignoring toggleMute for emergency call");
return;
}
@@ -382,7 +382,7 @@
Log.v(this, "mute, shouldMute: %b", shouldMute);
// Don't mute if there are any emergency calls.
- if (mCallsManager.hasEmergencyCall()) {
+ if (mCallsManager.isInEmergencyCall()) {
shouldMute = false;
Log.v(this, "ignoring mute for emergency call");
}
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 0d070c8..3a5d113 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -1278,7 +1278,7 @@
Log.startSession("CARSM.mCR");
try {
if (AudioManager.ACTION_MICROPHONE_MUTE_CHANGED.equals(intent.getAction())) {
- if (mCallsManager.hasEmergencyCall()) {
+ if (mCallsManager.isInEmergencyCall()) {
Log.i(this, "Mute was externally changed when there's an emergency call. " +
"Forcing mute back off.");
sendInternalMessage(MUTE_OFF);
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 9e8cf22..935790c 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -96,6 +96,7 @@
import com.android.server.telecom.ui.CallRedirectionTimeoutDialogActivity;
import com.android.server.telecom.ui.ConfirmCallDialogActivity;
import com.android.server.telecom.ui.IncomingCallNotifier;
+import com.android.server.telecom.ui.ToastFactory;
import java.util.Arrays;
import java.util.Collection;
@@ -205,7 +206,7 @@
*/
private static final int[] LIVE_CALL_STATES =
{CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
- CallState.PULLING, CallState.ACTIVE};
+ CallState.PULLING, CallState.ACTIVE, CallState.AUDIO_PROCESSING};
/**
* These states determine which calls will cause {@link TelecomManager#isInCall()} or
@@ -216,13 +217,15 @@
*/
public static final int[] ONGOING_CALL_STATES =
{CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING, CallState.PULLING, CallState.ACTIVE,
- CallState.ON_HOLD, CallState.RINGING, CallState.ANSWERED};
+ CallState.ON_HOLD, CallState.RINGING, CallState.SIMULATED_RINGING,
+ CallState.ANSWERED, CallState.AUDIO_PROCESSING};
private static final int[] ANY_CALL_STATE =
{CallState.NEW, CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
- CallState.RINGING, CallState.ACTIVE, CallState.ON_HOLD, CallState.DISCONNECTED,
- CallState.ABORTED, CallState.DISCONNECTING, CallState.PULLING,
- CallState.ANSWERED};
+ CallState.RINGING, CallState.SIMULATED_RINGING, CallState.ACTIVE,
+ CallState.ON_HOLD, CallState.DISCONNECTED, CallState.ABORTED,
+ CallState.DISCONNECTING, CallState.PULLING, CallState.ANSWERED,
+ CallState.AUDIO_PROCESSING};
public static final String TELECOM_CALL_ID_PREFIX = "TC@";
@@ -333,6 +336,7 @@
private final Timeouts.Adapter mTimeoutsAdapter;
private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
private final ClockProxy mClockProxy;
+ private final ToastFactory mToastFactory;
private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
private final Set<Call> mPendingCallsToDisconnect = new HashSet<>();
private final ConnectionServiceFocusManager mConnectionSvrFocusMgr;
@@ -456,7 +460,8 @@
CallAudioModeStateMachine.Factory callAudioModeStateMachineFactory,
InCallControllerFactory inCallControllerFactory,
RoleManagerAdapter roleManagerAdapter,
- IncomingCallFilter.Factory incomingCallFilterFactory) {
+ IncomingCallFilter.Factory incomingCallFilterFactory,
+ ToastFactory toastFactory) {
mContext = context;
mLock = lock;
mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
@@ -531,6 +536,7 @@
new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
mClockProxy = clockProxy;
+ mToastFactory = toastFactory;
mRoleManagerAdapter = roleManagerAdapter;
mListeners.add(mInCallWakeLockController);
@@ -1056,16 +1062,6 @@
return mPhoneAccountListener;
}
- @VisibleForTesting
- public boolean hasEmergencyCall() {
- for (Call call : mCalls) {
- if (call.isEmergencyCall()) {
- return true;
- }
- }
- return false;
- }
-
public boolean hasEmergencyRttCall() {
for (Call call : mCalls) {
if (call.isEmergencyCall() && call.isRttCall()) {
@@ -1149,7 +1145,8 @@
Call.CALL_DIRECTION_INCOMING /* callDirection */,
false /* forceAttachToExistingConnection */,
false, /* isConference */
- mClockProxy);
+ mClockProxy,
+ mToastFactory);
// Ensure new calls related to self-managed calls/connections are set as such. This will
// be overridden when the actual connection is returned in startCreateConnection, however
@@ -1262,6 +1259,13 @@
if (!isHandoverAllowed || (call.isSelfManaged() && !isIncomingCallPermitted(call,
call.getTargetPhoneAccount()))) {
notifyCreateConnectionFailed(phoneAccountHandle, call);
+ } else if (isInEmergencyCall()) {
+ // The incoming call is implicitly being rejected so the user does not get any incoming
+ // call UI during an emergency call. In this case, log the call as missed instead of
+ // rejected since the user did not explicitly reject.
+ mCallLogManager.logCall(call, Calls.MISSED_TYPE,
+ true /*showNotificationForMissedCall*/, null /*CallFilteringResult*/);
+ notifyCreateConnectionFailed(phoneAccountHandle, call);
} else {
call.startCreateConnection(mPhoneAccountRegistrar);
}
@@ -1286,7 +1290,8 @@
// to the existing connection instead of trying to create a new one.
true /* forceAttachToExistingConnection */,
false, /* isConference */
- mClockProxy);
+ mClockProxy,
+ mToastFactory);
call.initAnalytics();
setIntentExtrasAndStartTime(call, extras);
@@ -1370,7 +1375,8 @@
Call.CALL_DIRECTION_OUTGOING /* callDirection */,
false /* forceAttachToExistingConnection */,
false, /* isConference */
- mClockProxy);
+ mClockProxy,
+ mToastFactory);
call.initAnalytics(callingPackage);
// Ensure new calls related to self-managed calls/connections are set as such. This
@@ -1762,7 +1768,8 @@
new CallerInfoLookupHelper.OnQueryCompleteListener() {
@Override
public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
- if (info.preferredPhoneAccountComponent != null &&
+ if (info != null &&
+ info.preferredPhoneAccountComponent != null &&
info.preferredPhoneAccountId != null &&
!info.preferredPhoneAccountId.isEmpty()) {
PhoneAccountHandle contactDefaultHandle = new PhoneAccountHandle(
@@ -2490,7 +2497,7 @@
/** Called by the in-call UI to change the mute state. */
void mute(boolean shouldMute) {
- if (hasEmergencyCall() && shouldMute) {
+ if (isInEmergencyCall() && shouldMute) {
Log.i(this, "Refusing to turn on mute because we're in an emergency call");
shouldMute = false;
}
@@ -3054,7 +3061,8 @@
true /* isConference */,
connectTime,
connectElapsedTime,
- mClockProxy);
+ mClockProxy,
+ mToastFactory);
setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()),
"new conference call");
@@ -3601,7 +3609,8 @@
&& incomingCall.getHandoverSourceCall() == null;
}
- private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
+ @VisibleForTesting
+ public boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
if (hasMaximumLiveCalls(call)) {
// NOTE: If the amount of live calls changes beyond 1, this logic will probably
// have to change.
@@ -3638,6 +3647,13 @@
return false;
}
+ if (liveCall.getState() == CallState.AUDIO_PROCESSING && isEmergency) {
+ call.getAnalytics().setCallIsAdditional(true);
+ liveCall.getAnalytics().setCallIsInterrupted(true);
+ liveCall.disconnect(0, "disconnecting audio processing call for emergency");
+ return true;
+ }
+
// If we have the max number of held managed calls and we're placing an emergency call,
// we'll disconnect the ongoing call if it cannot be held.
if (hasMaximumManagedHoldingCalls(call) && isEmergency && !canHold(liveCall)) {
@@ -3694,6 +3710,30 @@
// The live call cannot be held so we're out of luck here. There's no room.
return false;
+ } else if (hasRingingOrSimulatedRingingCall() && isEmergency) {
+ Call ringingCall = getRingingOrSimulatedRingingCall();
+ ringingCall.getAnalytics().setCallIsAdditional(true);
+ ringingCall.getAnalytics().setCallIsInterrupted(true);
+ if (ringingCall.getState() == CallState.SIMULATED_RINGING) {
+ if (!ringingCall.hasGoneActiveBefore()) {
+ // If this is an incoming call that is currently in SIMULATED_RINGING only
+ // after a call screen, disconnect to make room and mark as missed, since
+ // the user didn't get a chance to accept/reject.
+ ringingCall.disconnect("emergency call dialed during simulated ringing "
+ + "after screen.");
+ } else {
+ // If this is a simulated ringing call after being active and put in
+ // AUDIO_PROCESSING state again, disconnect normally.
+ ringingCall.reject(false, null, "emergency call dialed during simulated "
+ + "ringing.");
+ }
+ } else { // normal incoming ringing call.
+ // Hang up the ringing call to make room for the emergency call and mark as missed,
+ // since the user did not reject.
+ ringingCall.setOverrideDisconnectCauseCode(DisconnectCause.MISSED);
+ ringingCall.reject(false, null, "emergency call dialed during ringing.");
+ }
+ return true;
}
return true;
}
@@ -3777,7 +3817,8 @@
isDowngradedConference /* isConference */,
connection.getConnectTimeMillis() /* connectTimeMillis */,
connection.getConnectElapsedTimeMillis(), /* connectElapsedTimeMillis */
- mClockProxy);
+ mClockProxy,
+ mToastFactory);
call.initAnalytics();
call.getAnalytics().setCreatedFromExistingConnection(true);
@@ -3927,13 +3968,13 @@
if (phoneAccount == null) {
return false;
}
+ if (isInEmergencyCall()) return false;
if (!phoneAccount.isSelfManaged()) {
return !hasMaximumManagedRingingCalls(excludeCall) &&
!hasMaximumManagedHoldingCalls(excludeCall);
} else {
- return !hasEmergencyCall() &&
- !hasMaximumSelfManagedRingingCalls(excludeCall, phoneAccountHandle) &&
+ return !hasMaximumSelfManagedRingingCalls(excludeCall, phoneAccountHandle) &&
!hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle);
}
}
@@ -3964,7 +4005,7 @@
// 2. The outgoing call is an handover call or it not hit the self-managed call limit
// and the current active call can be held.
Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
- return !hasEmergencyCall() &&
+ return !isInEmergencyCall() &&
((excludeCall != null && excludeCall.getHandoverSourceCall() != null) ||
(!hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) &&
(activeCall == null || canHold(activeCall))));
@@ -4285,110 +4326,6 @@
Log.addEvent(handoverFromCall, LogUtils.Events.HANDOVER_REQUEST, "legacy request denied");
}
- public Call createHandoverCall(Uri handle, PhoneAccountHandle handoverToHandle,
- Bundle extras, UserHandle initiatingUser) {
- boolean isReusedCall = true;
- Call call = reuseOutgoingCall(handle);
-
- PhoneAccount account =
- mPhoneAccountRegistrar.getPhoneAccount(handoverToHandle, initiatingUser);
- boolean isSelfManaged = account != null && account.isSelfManaged();
-
- // Create a call with original handle. The handle may be changed when the call is attached
- // to a connection service, but in most cases will remain the same.
- if (call == null) {
- call = new Call(getNextCallId(), mContext,
- this,
- mLock,
- mConnectionServiceRepository,
- mPhoneNumberUtilsAdapter,
- handle,
- null /* gatewayInfo */,
- null /* connectionManagerPhoneAccount */,
- null /* handoverToHandle */,
- Call.CALL_DIRECTION_OUTGOING /* callDirection */,
- false /* forceAttachToExistingConnection */,
- false, /* isConference */
- mClockProxy);
- call.initAnalytics(null);
-
- // Ensure new calls related to self-managed calls/connections are set as such. This
- // will be overridden when the actual connection is returned in startCreateConnection,
- // however doing this now ensures the logs and any other logic will treat this call as
- // self-managed from the moment it is created.
- call.setIsSelfManaged(isSelfManaged);
- if (isSelfManaged) {
- // Self-managed calls will ALWAYS use voip audio mode.
- call.setIsVoipAudioMode(true);
- }
- call.setInitiatingUser(initiatingUser);
- isReusedCall = false;
- }
-
- int videoState = VideoProfile.STATE_AUDIO_ONLY;
- if (extras != null) {
- // Set the video state on the call early so that when it is added to the InCall UI the
- // UI knows to configure itself as a video call immediately.
- videoState = extras.getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
- VideoProfile.STATE_AUDIO_ONLY);
-
- // If this is an emergency video call, we need to check if the phone account supports
- // emergency video calling.
- // Also, ensure we don't try to place an outgoing call with video if video is not
- // supported.
- if (VideoProfile.isVideo(videoState)) {
- if (call.isEmergencyCall() && account != null &&
- !account.hasCapabilities(PhoneAccount.CAPABILITY_EMERGENCY_VIDEO_CALLING)) {
- // Phone account doesn't support emergency video calling, so fallback to
- // audio-only now to prevent the InCall UI from setting up video surfaces
- // needlessly.
- Log.i(this, "startOutgoingCall - emergency video calls not supported; " +
- "falling back to audio-only");
- videoState = VideoProfile.STATE_AUDIO_ONLY;
- } else if (account != null &&
- !account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)) {
- // Phone account doesn't support video calling, so fallback to audio-only.
- Log.i(this, "startOutgoingCall - video calls not supported; fallback to " +
- "audio-only.");
- videoState = VideoProfile.STATE_AUDIO_ONLY;
- }
- }
-
- call.setVideoState(videoState);
- }
-
- call.setTargetPhoneAccount(handoverToHandle);
-
- // If there's no more room for a handover, just fail.
- if ((!isReusedCall && !makeRoomForOutgoingCall(call, call.isEmergencyCall()))) {
- return null;
- }
-
- PhoneAccount accountToUse =
- mPhoneAccountRegistrar.getPhoneAccount(handoverToHandle, initiatingUser);
- if (accountToUse != null && accountToUse.getExtras() != null) {
- if (accountToUse.getExtras()
- .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
- Log.d(this, "startOutgoingCall: defaulting to voip mode for call %s",
- call.getId());
- call.setIsVoipAudioMode(true);
- }
- }
-
- call.setState(
- CallState.CONNECTING,
- handoverToHandle == null ? "no-handle" : handoverToHandle.toString());
-
- setIntentExtrasAndStartTime(call, extras);
-
- if (!mCalls.contains(call)) {
- // We check if mCalls already contains the call because we could potentially be reusing
- // a call which was previously added (See {@link #reuseOutgoingCall}).
- addCall(call);
- }
-
- return call;
- }
/**
* Called in response to a {@link Call} receiving a {@link Call#handoverTo(PhoneAccountHandle,
* int, Bundle)} indicating the {@link android.telecom.InCallService} has requested a
@@ -4406,7 +4343,7 @@
int videoState, Bundle extras) {
// Send an error back if there are any ongoing emergency calls.
- if (hasEmergencyCall()) {
+ if (isInEmergencyCall()) {
handoverFromCall.onHandoverFailed(
android.telecom.Call.Callback.HANDOVER_FAILURE_ONGOING_EMERGENCY_CALL);
return;
@@ -4435,7 +4372,7 @@
handoverFromCall.getHandle(), null,
null, null,
Call.CALL_DIRECTION_OUTGOING, false,
- false, mClockProxy);
+ false, mClockProxy, mToastFactory);
call.initAnalytics();
// Set self-managed and voipAudioMode if destination is self-managed CS
@@ -4639,12 +4576,13 @@
Call.CALL_DIRECTION_INCOMING /* callDirection */,
false /* forceAttachToExistingConnection */,
false, /* isConference */
- mClockProxy);
+ mClockProxy,
+ mToastFactory);
if (fromCall == null || isHandoverInProgress() ||
!isHandoverFromPhoneAccountSupported(fromCall.getTargetPhoneAccount()) ||
!isHandoverToPhoneAccountSupported(destAcct) ||
- hasEmergencyCall()) {
+ isInEmergencyCall()) {
Log.w(this, "acceptHandover: Handover not supported");
notifyHandoverFailed(call,
android.telecom.Call.Callback.HANDOVER_FAILURE_NOT_SUPPORTED);
@@ -4814,8 +4752,8 @@
* @return {@code true} if there is an ongoing emergency call, {@code false} otherwise.
*/
public boolean isInEmergencyCall() {
- return mCalls.stream().filter(c -> c.isEmergencyCall()
- || c.isNetworkIdentifiedEmergencyCall()).count() > 0;
+ return mCalls.stream().filter(c -> (c.isEmergencyCall()
+ || c.isNetworkIdentifiedEmergencyCall()) && !c.isDisconnected()).count() > 0;
}
/**
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index cf7abad..d608852 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -1527,7 +1527,8 @@
}
}
- void pullExternalCall(Call call) {
+ @VisibleForTesting
+ public void pullExternalCall(Call call) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("pullExternalCall")) {
try {
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index c4df1d3..b0ac9d7 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -24,7 +24,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -1107,7 +1106,7 @@
mDefaultDialerCache.getSystemDialerComponent(), IN_CALL_SERVICE_TYPE_SYSTEM_UI);
EmergencyInCallServiceConnection systemInCall =
new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
- systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());
+ systemInCall.setHasEmergency(mCallsManager.isInEmergencyCall());
InCallServiceConnection carModeInCall = null;
InCallServiceInfo carModeComponentInfo = getCarModeComponent();
@@ -1334,7 +1333,7 @@
private void adjustServiceBindingsForEmergency() {
// The connected UI is not the system UI, so lets check if we should switch them
// if there exists an emergency number.
- if (mCallsManager.hasEmergencyCall()) {
+ if (mCallsManager.isInEmergencyCall()) {
mInCallServiceConnection.setHasEmergency(true);
}
}
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index f3b7b25..7700852 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -29,6 +29,7 @@
import com.android.server.telecom.BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory;
import com.android.server.telecom.CallAudioManager.AudioServiceFactory;
import com.android.server.telecom.DefaultDialerCache.DefaultDialerManagerAdapter;
+import com.android.server.telecom.ui.ToastFactory;
import android.Manifest;
import android.content.BroadcastReceiver;
@@ -42,6 +43,7 @@
import android.os.UserManager;
import android.telecom.Log;
import android.telecom.PhoneAccountHandle;
+import android.widget.Toast;
import java.io.FileNotFoundException;
import java.io.InputStream;
@@ -271,6 +273,19 @@
AudioProcessingNotification audioProcessingNotification =
new AudioProcessingNotification(mContext);
+ ToastFactory toastFactory = new ToastFactory() {
+ @Override
+ public Toast makeText(Context context, int resId, int duration) {
+ return Toast.makeText(context, context.getMainLooper(), context.getString(resId),
+ duration);
+ }
+
+ @Override
+ public Toast makeText(Context context, CharSequence text, int duration) {
+ return Toast.makeText(context, context.getMainLooper(), text, duration);
+ }
+ };
+
mCallsManager = new CallsManager(
mContext,
mLock,
@@ -298,7 +313,8 @@
callAudioModeStateMachineFactory,
inCallControllerFactory,
roleManagerAdapter,
- incomingCallFilterFactory);
+ incomingCallFilterFactory,
+ toastFactory);
mIncomingCallNotifier = incomingCallNotifier;
incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
diff --git a/src/com/android/server/telecom/ui/ToastFactory.java b/src/com/android/server/telecom/ui/ToastFactory.java
new file mode 100644
index 0000000..75b69da
--- /dev/null
+++ b/src/com/android/server/telecom/ui/ToastFactory.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 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.server.telecom.ui;
+
+import android.annotation.StringRes;
+import android.content.Context;
+import android.widget.Toast;
+
+public interface ToastFactory {
+ Toast makeText(Context context, @StringRes int resId, int duration);
+ Toast makeText(Context context, CharSequence text, int duration);
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
index 90509f6..f6fa116 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
@@ -182,7 +182,7 @@
setConnectionProperties(properties);
if (isIncoming) {
- Bundle newExtras = getExtras();
+ Bundle newExtras = (getExtras() == null) ? new Bundle() : getExtras();
newExtras.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
putExtras(newExtras);
}
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 322a3c2..01927f5 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -358,6 +358,7 @@
mInCallServiceFixtureY.getCall(ids.mCallId).getState());
}
+ @FlakyTest
@LargeTest
@Test
public void testIncomingCallFromContactWithSendToVoicemailIsRejected() throws Exception {
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
new file mode 100644
index 0000000..0566999
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2019 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.server.telecom.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.Toast;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ClockProxy;
+import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.ui.ToastFactory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+@RunWith(AndroidJUnit4.class)
+public class CallTest extends TelecomTestCase {
+ private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
+ private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
+ ComponentName.unflattenFromString("com.foo/.Blah"), "Sim1");
+ private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1")
+ .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
+ | PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .setIsEnabled(true)
+ .build();
+
+ @Mock private CallsManager mMockCallsManager;
+ @Mock private CallerInfoLookupHelper mMockCallerInfoLookupHelper;
+ @Mock private PhoneAccountRegistrar mMockPhoneAccountRegistrar;
+ @Mock private ClockProxy mMockClockProxy;
+ @Mock private ToastFactory mMockToastProxy;
+ @Mock private Toast mMockToast;
+ @Mock private PhoneNumberUtilsAdapter mMockPhoneNumberUtilsAdapter;
+ @Mock private ConnectionServiceWrapper mMockConnectionService;
+
+ private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ doReturn(mMockCallerInfoLookupHelper).when(mMockCallsManager).getCallerInfoLookupHelper();
+ doReturn(mMockPhoneAccountRegistrar).when(mMockCallsManager).getPhoneAccountRegistrar();
+ doReturn(SIM_1_ACCOUNT).when(mMockPhoneAccountRegistrar).getPhoneAccountUnchecked(
+ eq(SIM_1_HANDLE));
+ doReturn(new ComponentName(mContext, CallTest.class))
+ .when(mMockConnectionService).getComponentName();
+ doReturn(mMockToast).when(mMockToastProxy).makeText(any(), anyInt(), anyInt());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ @SmallTest
+ public void testSetHasGoneActive() {
+ Call call = new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_INCOMING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+
+ assertFalse(call.hasGoneActiveBefore());
+ call.setState(CallState.ACTIVE, "");
+ assertTrue(call.hasGoneActiveBefore());
+ call.setState(CallState.AUDIO_PROCESSING, "");
+ assertTrue(call.hasGoneActiveBefore());
+ }
+
+ @Test
+ @SmallTest
+ public void testDisconnectCauseWhenAudioProcessing() {
+ Call call = new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_INCOMING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+ call.setState(CallState.AUDIO_PROCESSING, "");
+ call.disconnect();
+ call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
+ assertEquals(DisconnectCause.REJECTED, call.getDisconnectCause().getCode());
+ }
+
+ @Test
+ @SmallTest
+ public void testDisconnectCauseWhenAudioProcessingAfterActive() {
+ Call call = new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_INCOMING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+ call.setState(CallState.AUDIO_PROCESSING, "");
+ call.setState(CallState.ACTIVE, "");
+ call.setState(CallState.AUDIO_PROCESSING, "");
+ call.disconnect();
+ call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
+ assertEquals(DisconnectCause.LOCAL, call.getDisconnectCause().getCode());
+ }
+
+ @Test
+ @SmallTest
+ public void testDisconnectCauseWhenSimulatedRingingAndDisconnect() {
+ Call call = new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_INCOMING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+ call.setState(CallState.SIMULATED_RINGING, "");
+ call.disconnect();
+ call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
+ assertEquals(DisconnectCause.MISSED, call.getDisconnectCause().getCode());
+ }
+
+ @Test
+ @SmallTest
+ public void testDisconnectCauseWhenSimulatedRingingAndReject() {
+ Call call = new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_INCOMING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+ call.setState(CallState.SIMULATED_RINGING, "");
+ call.reject(false, "");
+ call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
+ assertEquals(DisconnectCause.REJECTED, call.getDisconnectCause().getCode());
+ }
+
+ @Test
+ @SmallTest
+ public void testCanNotPullCallDuringEmergencyCall() {
+ Call call = new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_INCOMING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+ call.setConnectionService(mMockConnectionService);
+ call.setConnectionProperties(Connection.PROPERTY_IS_EXTERNAL_CALL);
+ call.setConnectionCapabilities(Connection.CAPABILITY_CAN_PULL_CALL);
+ call.setState(CallState.ACTIVE, "");
+ // Emergency call in progress, this should show a toast and never call pullExternalCall
+ // on the ConnectionService.
+ doReturn(true).when(mMockCallsManager).isInEmergencyCall();
+ call.pullExternalCall();
+ verify(mMockConnectionService, never()).pullExternalCall(any());
+ verify(mMockToast).show();
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 71ad721..110b58e 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -25,6 +25,8 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyChar;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doAnswer;
@@ -35,10 +37,12 @@
import static org.mockito.Mockito.when;
import android.content.ComponentName;
+import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.Process;
import android.os.SystemClock;
+import android.telecom.CallerInfo;
import android.telecom.Connection;
import android.telecom.Log;
import android.telecom.PhoneAccount;
@@ -48,8 +52,8 @@
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.Toast;
-import android.telecom.CallerInfo;
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallAudioManager;
@@ -84,6 +88,7 @@
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
import com.android.server.telecom.callfiltering.IncomingCallFilter;
import com.android.server.telecom.ui.AudioProcessingNotification;
+import com.android.server.telecom.ui.ToastFactory;
import org.junit.After;
import org.junit.Before;
@@ -176,6 +181,8 @@
@Mock private RoleManagerAdapter mRoleManagerAdapter;
@Mock private IncomingCallFilter.Factory mIncomingCallFilterFactory;
@Mock private IncomingCallFilter mIncomingCallFilter;
+ @Mock private ToastFactory mToastFactory;
+ @Mock private Toast mToast;
private CallsManager mCallsManager;
@@ -229,7 +236,8 @@
mCallAudioModeStateMachineFactory,
mInCallControllerFactory,
mRoleManagerAdapter,
- mIncomingCallFilterFactory);
+ mIncomingCallFilterFactory,
+ mToastFactory);
when(mPhoneAccountRegistrar.getPhoneAccount(
eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);
@@ -237,6 +245,8 @@
eq(SIM_1_HANDLE), any())).thenReturn(SIM_1_ACCOUNT);
when(mPhoneAccountRegistrar.getPhoneAccount(
eq(SIM_2_HANDLE), any())).thenReturn(SIM_2_ACCOUNT);
+ when(mToastFactory.makeText(any(), anyInt(), anyInt())).thenReturn(mToast);
+ when(mToastFactory.makeText(any(), any(), anyInt())).thenReturn(mToast);
}
@Override
@@ -276,7 +286,8 @@
Call.CALL_DIRECTION_INCOMING,
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
- mClockProxy);
+ mClockProxy,
+ mToastFactory);
ongoingCall.setState(CallState.ACTIVE, "just cuz");
mCallsManager.addCall(ongoingCall);
@@ -1041,6 +1052,102 @@
assertTrue(mCallsManager.isInEmergencyCall());
}
+ @SmallTest
+ @Test
+ public void testIsInEmergencyCallLocalDisconnected() {
+ // Setup a call which is considered emergency based on its phone number.
+ Call ongoingCall = addSpyCall();
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(true);
+ ongoingCall.setHandle(Uri.fromParts("tel", "5551212", null),
+ TelecomManager.PRESENTATION_ALLOWED);
+
+ // and then set it as disconnected.
+ ongoingCall.setState(CallState.DISCONNECTED, "");
+ assertTrue(ongoingCall.isEmergencyCall());
+ assertFalse(ongoingCall.isNetworkIdentifiedEmergencyCall());
+ assertFalse(mCallsManager.isInEmergencyCall());
+ }
+
+ @SmallTest
+ @Test
+ public void testHasEmergencyCallIncomingCallPermitted() {
+ // Setup a call which is considered emergency based on its phone number.
+ Call ongoingCall = addSpyCall();
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(true);
+ ongoingCall.setHandle(Uri.fromParts("tel", "5551212", null),
+ TelecomManager.PRESENTATION_ALLOWED);
+ when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SELF_MANAGED_HANDLE))
+ .thenReturn(SELF_MANAGED_ACCOUNT);
+ when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_1_HANDLE))
+ .thenReturn(SIM_1_ACCOUNT);
+
+ assertFalse(mCallsManager.isIncomingCallPermitted(null, SELF_MANAGED_HANDLE));
+ assertFalse(mCallsManager.isIncomingCallPermitted(null, SIM_1_HANDLE));
+ }
+
+ @SmallTest
+ @Test
+ public void testMakeRoomForOutgoingCallAudioProcessingInProgress() {
+ Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.AUDIO_PROCESSING);
+
+ Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(true);
+ newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null),
+ TelecomManager.PRESENTATION_ALLOWED);
+
+ assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/));
+ verify(ongoingCall).disconnect(anyLong(), anyString());
+ }
+
+ @SmallTest
+ @Test
+ public void testMakeRoomForEmergencyDuringIncomingCall() {
+ Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.RINGING);
+
+ Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(true);
+ newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null),
+ TelecomManager.PRESENTATION_ALLOWED);
+
+ assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/));
+ verify(ongoingCall).reject(anyBoolean(), any(), any());
+ }
+
+ @SmallTest
+ @Test
+ public void testMakeRoomForEmergencyCallSimulatedRingingInProgress() {
+ Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.SIMULATED_RINGING);
+
+ Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(true);
+ newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null),
+ TelecomManager.PRESENTATION_ALLOWED);
+
+ assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/));
+ verify(ongoingCall).disconnect(anyString());
+ }
+
+ @SmallTest
+ @Test
+ public void testMakeRoomForEmergencyCallSimulatedRingingInProgressHasBeenActive() {
+ Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
+ ongoingCall.setState(CallState.SIMULATED_RINGING, "");
+
+ Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
+ when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+ .thenReturn(true);
+ newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null),
+ TelecomManager.PRESENTATION_ALLOWED);
+
+ assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/));
+ verify(ongoingCall).reject(anyBoolean(), any(), any());
+ }
+
/**
* Verifies that changes to a {@link PhoneAccount}'s
* {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} capability will be reflected on a call.
@@ -1112,6 +1219,21 @@
}
private Call addSpyCall(PhoneAccountHandle targetPhoneAccount, int initialState) {
+ Call ongoingCall = createCall(targetPhoneAccount, initialState);
+ Call callSpy = Mockito.spy(ongoingCall);
+
+ // Mocks some methods to not call the real method.
+ doNothing().when(callSpy).unhold();
+ doNothing().when(callSpy).hold();
+ doNothing().when(callSpy).disconnect();
+ doNothing().when(callSpy).answer(Matchers.anyInt());
+ doNothing().when(callSpy).setStartWithSpeakerphoneOn(Matchers.anyBoolean());
+
+ mCallsManager.addCall(callSpy);
+ return callSpy;
+ }
+
+ private Call createCall(PhoneAccountHandle targetPhoneAccount, int initialState) {
Call ongoingCall = new Call(String.format("TC@%d", sCallId++), /* callId */
mContext,
mCallsManager,
@@ -1125,19 +1247,10 @@
Call.CALL_DIRECTION_INCOMING,
false /* shouldAttachToExistingConnection*/,
false /* isConference */,
- mClockProxy);
+ mClockProxy,
+ mToastFactory);
ongoingCall.setState(initialState, "just cuz");
- Call callSpy = Mockito.spy(ongoingCall);
-
- // Mocks some methods to not call the real method.
- doNothing().when(callSpy).unhold();
- doNothing().when(callSpy).hold();
- doNothing().when(callSpy).disconnect();
- doNothing().when(callSpy).answer(Matchers.anyInt());
- doNothing().when(callSpy).setStartWithSpeakerphoneOn(Matchers.anyBoolean());
-
- mCallsManager.addCall(callSpy);
- return callSpy;
+ return ongoingCall;
}
private void verifyFocusRequestAndExecuteCallback(Call call) {
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 2577631..6a97c27 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -93,17 +93,10 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.matches;
-import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
@RunWith(JUnit4.class)
@@ -179,7 +172,7 @@
public void testBindToService_NoServicesFound_IncomingCall() throws Exception {
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(true);
when(mMockCall.isExternalCall()).thenReturn(false);
when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
@@ -211,7 +204,7 @@
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockCall.getIntentExtras()).thenReturn(callExtras);
@@ -249,7 +242,7 @@
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockCall.getIntentExtras()).thenReturn(callExtras);
@@ -301,7 +294,7 @@
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(true);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
when(mMockCall.isEmergencyCall()).thenReturn(true);
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
@@ -371,7 +364,7 @@
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
when(mMockCallsManager.getAudioState()).thenReturn(null);
when(mMockCallsManager.canAddCall()).thenReturn(false);
@@ -497,7 +490,7 @@
public void testUnbindDueToCallDisconnect() throws Exception {
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(true);
when(mMockCall.isExternalCall()).thenReturn(false);
when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
@@ -699,7 +692,7 @@
public void testBindingFuture() throws Exception {
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.isExternalCall()).thenReturn(false);
when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
@@ -745,7 +738,7 @@
private void setupMocks(boolean isExternalCall) {
when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
- when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+ when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
diff --git a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
index 7104e3a..30b870e 100644
--- a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
+++ b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
@@ -31,6 +31,7 @@
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.ui.ToastFactory;
import org.junit.After;
import org.junit.Before;
@@ -45,6 +46,7 @@
private SyncRoot mLock = new SyncRoot() {};
@Mock private ClockProxy mClockProxy;
+ @Mock private ToastFactory mToastProxy;
@Mock private CallsManager mCallsManager;
@Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
@Mock private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
@@ -77,7 +79,8 @@
Call.CALL_DIRECTION_INCOMING,
false /* shouldAttachToExistingConnection */,
false /* isConference */,
- mClockProxy /* ClockProxy */);
+ mClockProxy /* ClockProxy */,
+ mToastProxy);
}
@Override
diff --git a/tests/src/com/android/server/telecom/tests/SessionTest.java b/tests/src/com/android/server/telecom/tests/SessionTest.java
index 337041b..6a14a64 100644
--- a/tests/src/com/android/server/telecom/tests/SessionTest.java
+++ b/tests/src/com/android/server/telecom/tests/SessionTest.java
@@ -18,6 +18,7 @@
import static junit.framework.Assert.fail;
+import android.telecom.Log;
import android.telecom.Logging.Session;
import android.test.suitebuilder.annotation.SmallTest;
@@ -48,30 +49,189 @@
/**
* Ensure creating two sessions that are parent/child of each other does not lead to a crash
- * or infinite recursion.
+ * or infinite recursion when using Session#printFullSessionTree.
+ */
+ @SmallTest
+ @Test
+ public void testRecursion_printFullSessionTree() {
+ Log.startSession("testParent");
+ // Running in the same thread, so mark as invisible subsession
+ Session childSession = Log.getSessionManager()
+ .createSubsession(true /*isStartedFromActiveSession*/);
+ Log.continueSession(childSession, "child");
+ Session parentSession = childSession.getParentSession();
+ // Create a circular dependency and ensure we do not crash
+ parentSession.setParentSession(childSession);
+ childSession.addChild(parentSession);
+
+ // Make sure calling these methods does not result in a crash
+ try {
+ parentSession.printFullSessionTree();
+ childSession.printFullSessionTree();
+ } catch (Exception e) {
+ fail("Exception: " + e.getMessage());
+ } finally {
+ // End child
+ Log.endSession();
+ // End parent
+ Log.endSession();
+ }
+ }
+
+ /**
+ * Ensure creating two sessions that are parent/child of each other does not lead to a crash
+ * or infinite recursion when using Session#getFullMethodPath.
+ */
+ @SmallTest
+ @Test
+ public void testRecursion_getFullMethodPath() {
+ Log.startSession("testParent");
+ // Running in the same thread, so mark as invisible subsession
+ Session childSession = Log.getSessionManager()
+ .createSubsession(true /*isStartedFromActiveSession*/);
+ Log.continueSession(childSession, "child");
+ Session parentSession = childSession.getParentSession();
+ // Create a circular dependency and ensure we do not crash
+ parentSession.setParentSession(childSession);
+ childSession.addChild(parentSession);
+
+ // Make sure calling these methods does not result in a crash
+ try {
+ parentSession.getFullMethodPath(false /*truncatePath*/);
+ childSession.getFullMethodPath(false /*truncatePath*/);
+ } catch (Exception e) {
+ fail("Exception: " + e.getMessage());
+ } finally {
+ // End child
+ Log.endSession();
+ // End parent
+ Log.endSession();
+ }
+ }
+
+ /**
+ * Ensure creating two sessions that are parent/child of each other does not lead to a crash
+ * or infinite recursion when using Session#getFullMethodPath.
+ */
+ @SmallTest
+ @Test
+ public void testRecursion_getFullMethodPathTruncated() {
+ Log.startSession("testParent");
+ // Running in the same thread, so mark as invisible subsession
+ Session childSession = Log.getSessionManager()
+ .createSubsession(true /*isStartedFromActiveSession*/);
+ Log.continueSession(childSession, "child");
+ Session parentSession = childSession.getParentSession();
+ // Create a circular dependency and ensure we do not crash
+ parentSession.setParentSession(childSession);
+ childSession.addChild(parentSession);
+
+ // Make sure calling these methods does not result in a crash
+ try {
+ parentSession.getFullMethodPath(true /*truncatePath*/);
+ childSession.getFullMethodPath(true /*truncatePath*/);
+ } catch (Exception e) {
+ fail("Exception: " + e.getMessage());
+ } finally {
+ // End child
+ Log.endSession();
+ // End parent
+ Log.endSession();
+ }
+ }
+
+ /**
+ * Ensure creating two sessions that are parent/child of each other does not lead to a crash
+ * or infinite recursion when using Session#toString.
+ */
+ @SmallTest
+ @Test
+ public void testRecursion_toString() {
+ Log.startSession("testParent");
+ // Running in the same thread, so mark as invisible subsession
+ Session childSession = Log.getSessionManager()
+ .createSubsession(true /*isStartedFromActiveSession*/);
+ Log.continueSession(childSession, "child");
+ Session parentSession = childSession.getParentSession();
+ // Create a circular dependency and ensure we do not crash
+ parentSession.setParentSession(childSession);
+ childSession.addChild(parentSession);
+
+ // Make sure calling these methods does not result in a crash
+ try {
+
+ parentSession.toString();
+ childSession.toString();
+ } catch (Exception e) {
+ fail("Exception: " + e.getMessage());
+ } finally {
+ // End child
+ Log.endSession();
+ // End parent
+ Log.endSession();
+ }
+ }
+
+ /**
+ * Ensure creating two sessions that are parent/child of each other does not lead to a crash
+ * or infinite recursion when using Session#getInfo.
+ */
+ @SmallTest
+ @Test
+ public void testRecursion_getInfo() {
+ Log.startSession("testParent");
+ // Running in the same thread, so mark as invisible subsession
+ Session childSession = Log.getSessionManager()
+ .createSubsession(true /*isStartedFromActiveSession*/);
+ Log.continueSession(childSession, "child");
+ Session parentSession = childSession.getParentSession();
+ // Create a circular dependency and ensure we do not crash
+ parentSession.setParentSession(childSession);
+ childSession.addChild(parentSession);
+
+ // Make sure calling these methods does not result in a crash
+ try {
+ Session.Info.getInfo(parentSession);
+ Session.Info.getInfo(childSession);
+ } catch (Exception e) {
+ fail("Exception: " + e.getMessage());
+ } finally {
+ // End child
+ Log.endSession();
+ // End parent
+ Log.endSession();
+ }
+ }
+
+ /**
+ * Ensure creating two sessions that are parent/child of each other does not lead to a crash
+ * or infinite recursion in the general case.
*/
@SmallTest
@Test
public void testRecursion() {
Session parentSession = createTestSession("parent", "p");
Session childSession = createTestSession("child", "c");
+ // Create a circular dependency
parentSession.addChild(childSession);
- parentSession.setParentSession(childSession);
childSession.addChild(parentSession);
+ parentSession.setParentSession(childSession);
childSession.setParentSession(parentSession);
// Make sure calling these methods does not result in a crash
try {
parentSession.printFullSessionTree();
childSession.printFullSessionTree();
- parentSession.getFullMethodPath(false);
- childSession.getFullMethodPath(false);
+ parentSession.getFullMethodPath(false /*truncatePath*/);
+ childSession.getFullMethodPath(false /*truncatePath*/);
+ parentSession.getFullMethodPath(true /*truncatePath*/);
+ childSession.getFullMethodPath(true /*truncatePath*/);
parentSession.toString();
childSession.toString();
Session.Info.getInfo(parentSession);
Session.Info.getInfo(childSession);
} catch (Exception e) {
- fail();
+ fail("Exception: " + e.getMessage());
}
}