Merge "Add Call Redirection app into RoleManagerAdapter in Telecom"
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 1d5a24b..6784d2b 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -531,7 +531,7 @@
/**
* Persists the specified parameters and initializes the new instance.
- * @param context The context.
+ * @param context The context.
* @param repository The connection service repository.
* @param handle The handle to dial.
* @param gatewayInfo Gateway information to use for the call.
@@ -551,8 +551,6 @@
CallsManager callsManager,
TelecomSystem.SyncRoot lock,
ConnectionServiceRepository repository,
- ContactsAsyncHelper contactsAsyncHelper,
- CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
Uri handle,
GatewayInfo gatewayInfo,
@@ -587,7 +585,7 @@
/**
* Persists the specified parameters and initializes the new instance.
- * @param context The context.
+ * @param context The context.
* @param repository The connection service repository.
* @param handle The handle to dial.
* @param gatewayInfo Gateway information to use for the call.
@@ -609,8 +607,6 @@
CallsManager callsManager,
TelecomSystem.SyncRoot lock,
ConnectionServiceRepository repository,
- ContactsAsyncHelper contactsAsyncHelper,
- CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
Uri handle,
GatewayInfo gatewayInfo,
@@ -622,8 +618,8 @@
long connectTimeMillis,
long connectElapsedTimeMillis,
ClockProxy clockProxy) {
- this(callId, context, callsManager, lock, repository, contactsAsyncHelper,
- callerInfoAsyncQueryFactory, phoneNumberUtilsAdapter, handle, gatewayInfo,
+ this(callId, context, callsManager, lock, repository,
+ phoneNumberUtilsAdapter, handle, gatewayInfo,
connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
shouldAttachToExistingConnection, isConference, clockProxy);
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index a8f2bc8..b68a851 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -727,9 +727,11 @@
Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
- mPlayerFactory.createPlayer(toneToPlay).startTone();
- mCallsManager.onDisconnectedTonePlaying(true);
- mIsDisconnectedTonePlaying = true;
+ boolean didToneStart = mPlayerFactory.createPlayer(toneToPlay).startTone();
+ if (didToneStart) {
+ mCallsManager.onDisconnectedTonePlaying(true);
+ mIsDisconnectedTonePlaying = true;
+ }
}
}
}
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index 5213364..cea18dc 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -11,6 +11,7 @@
import android.os.UserManager;
import android.telecom.DefaultDialerManager;
import android.telecom.Log;
+import android.telecom.Logging.Session;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
@@ -19,6 +20,8 @@
import android.telephony.PhoneNumberUtils;
import android.widget.Toast;
+import java.util.concurrent.CompletableFuture;
+
/**
* Single point of entry for all outgoing and incoming calls.
* {@link com.android.server.telecom.components.UserCallIntentProcessor} serves as a trampoline that
@@ -146,13 +149,21 @@
UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
// Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
- Call call = callsManager
+ CompletableFuture<Call> callFuture = callsManager
.startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
intent, callingPackage);
- if (call != null) {
- sendNewOutgoingCallIntent(context, call, callsManager, intent);
- }
+ final Session logSubsession = Log.createSubsession();
+ callFuture.thenAccept((call) -> {
+ if (call != null) {
+ Log.continueSession(logSubsession, "CIP.sNOCI");
+ try {
+ sendNewOutgoingCallIntent(context, call, callsManager, intent);
+ } finally {
+ Log.endSession();
+ }
+ }
+ });
}
static void sendNewOutgoingCallIntent(Context context, Call call, CallsManager callsManager,
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 993c40e..6130bb5 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -16,6 +16,7 @@
package com.android.server.telecom;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.content.BroadcastReceiver;
@@ -54,6 +55,7 @@
import android.telecom.ParcelableConnection;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
+import android.telecom.PhoneAccountSuggestion;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.CarrierConfigManager;
@@ -64,6 +66,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.AsyncEmergencyContactNotifier;
+import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.util.IndentingPrintWriter;
@@ -93,8 +96,10 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -141,6 +146,26 @@
void performAction();
}
+ private class LoggedHandlerExecutor implements Executor {
+ private Handler mHandler;
+ private String mSessionName;
+
+ public LoggedHandlerExecutor(Handler handler, String sessionName) {
+ mHandler = handler;
+ mSessionName = sessionName;
+ }
+
+ @Override
+ public void execute(java.lang.Runnable command) {
+ mHandler.post(new Runnable(mSessionName, mLock) {
+ @Override
+ public void loggedRun() {
+ command.run();
+ }
+ }.prepare());
+ }
+ }
+
private static final String TAG = "CallsManager";
/**
@@ -232,9 +257,15 @@
/**
* A pending call is one which requires user-intervention in order to be placed.
- * Used by {@link #startCallConfirmation(Call)}.
+ * Used by {@link #startCallConfirmation}.
*/
private Call mPendingCall;
+ private CompletableFuture<Call> mPendingCallConfirm;
+ private CompletableFuture<Pair<Call, PhoneAccountHandle>> mPendingAccountSelection;
+
+ // Instance variables for testing -- we keep the latest copy of the outgoing call futures
+ // here so that we can wait on them in tests
+ private CompletableFuture<Call> mLatestPostSelectionProcessingFuture;
/**
* The current telecom call ID. Used when creating new instances of {@link Call}. Should
@@ -271,8 +302,6 @@
private final CallLogManager mCallLogManager;
private final Context mContext;
private final TelecomSystem.SyncRoot mLock;
- private final ContactsAsyncHelper mContactsAsyncHelper;
- private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final MissedCallNotifier mMissedCallNotifier;
private IncomingCallNotifier mIncomingCallNotifier;
@@ -370,8 +399,7 @@
public CallsManager(
Context context,
TelecomSystem.SyncRoot lock,
- ContactsAsyncHelper contactsAsyncHelper,
- CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
+ CallerInfoLookupHelper callerInfoLookupHelper,
MissedCallNotifier missedCallNotifier,
PhoneAccountRegistrar phoneAccountRegistrar,
HeadsetMediaButtonFactory headsetMediaButtonFactory,
@@ -398,8 +426,6 @@
mContext = context;
mLock = lock;
mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
- mContactsAsyncHelper = contactsAsyncHelper;
- mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
mPhoneAccountRegistrar = phoneAccountRegistrar;
mPhoneAccountRegistrar.addListener(mPhoneAccountListener);
mMissedCallNotifier = missedCallNotifier;
@@ -410,8 +436,7 @@
mDockManager = new DockManager(context);
mTimeoutsAdapter = timeoutsAdapter;
mEmergencyCallHelper = emergencyCallHelper;
- mCallerInfoLookupHelper = new CallerInfoLookupHelper(context, mCallerInfoAsyncQueryFactory,
- mContactsAsyncHelper, mLock);
+ mCallerInfoLookupHelper = callerInfoLookupHelper;
mDtmfLocalTonePlayer =
new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
@@ -973,8 +998,6 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
handle,
null /* gatewayInfo */,
@@ -1108,8 +1131,6 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
handle,
null /* gatewayInfo */,
@@ -1169,7 +1190,7 @@
* For self-managed connections, we don't expect the Incall UI to launch, but this is still a
* first step in getting the self-managed ConnectionService to create the connection.
* @param handle Handle to connect the call with.
- * @param phoneAccountHandle The phone account which contains the component name of the
+ * @param requestedAccountHandle The phone account which contains the component name of the
* connection service to use for this call.
* @param extras The optional extras Bundle passed with the intent used for the incoming call.
* @param initiatingUser {@link UserHandle} of user that place the outgoing call.
@@ -1177,13 +1198,16 @@
* @param callingPackage the package name of the app which initiated the outgoing call.
*/
@VisibleForTesting
- public Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras,
- UserHandle initiatingUser, Intent originalIntent, String callingPackage) {
- boolean isReusedCall = true;
+ public @NonNull
+ CompletableFuture<Call> startOutgoingCall(Uri handle,
+ PhoneAccountHandle requestedAccountHandle,
+ Bundle extras, UserHandle initiatingUser, Intent originalIntent,
+ String callingPackage) {
+ boolean isReusedCall;
Call call = reuseOutgoingCall(handle);
PhoneAccount account =
- mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, initiatingUser);
+ mPhoneAccountRegistrar.getPhoneAccount(requestedAccountHandle, initiatingUser);
boolean isSelfManaged = account != null && account.isSelfManaged();
// Create a call with original handle. The handle may be changed when the call is attached
@@ -1193,13 +1217,11 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
handle,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
- null /* phoneAccountHandle */,
+ null /* requestedAccountHandle */,
Call.CALL_DIRECTION_OUTGOING /* callDirection */,
false /* forceAttachToExistingConnection */,
false, /* isConference */
@@ -1217,6 +1239,8 @@
}
call.setInitiatingUser(initiatingUser);
isReusedCall = false;
+ } else {
+ isReusedCall = true;
}
int videoState = VideoProfile.STATE_AUDIO_ONLY;
@@ -1251,96 +1275,209 @@
call.setVideoState(videoState);
}
- List<PhoneAccountHandle> potentialPhoneAccounts = findOutgoingCallPhoneAccount(
- phoneAccountHandle, handle, VideoProfile.isVideo(videoState), initiatingUser);
- if (potentialPhoneAccounts.size() == 1) {
- phoneAccountHandle = potentialPhoneAccounts.get(0);
- } else {
- phoneAccountHandle = null;
- }
- call.setTargetPhoneAccount(phoneAccountHandle);
+ final int finalVideoState = videoState;
+ final Call finalCall = call;
+ Handler outgoingCallHandler = new Handler(Looper.getMainLooper());
+ // Create a empty CompletableFuture and compose it with findOutgoingPhoneAccount to get
+ // a first guess at the list of suitable outgoing PhoneAccounts.
+ // findOutgoingPhoneAccount returns a CompletableFuture which is either already complete
+ // (in the case where we don't need to do the per-contact lookup) or a CompletableFuture
+ // that completes once the contact lookup via CallerInfoLookupHelper is complete.
+ CompletableFuture<List<PhoneAccountHandle>> accountsForCall =
+ CompletableFuture.completedFuture((Void) null).thenComposeAsync((x) ->
+ findOutgoingCallPhoneAccount(requestedAccountHandle, handle,
+ VideoProfile.isVideo(finalVideoState), initiatingUser),
+ new LoggedHandlerExecutor(outgoingCallHandler, "CM.fOCP"));
- boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle) && !isSelfManaged;
+ // This is a block of code that executes after the list of potential phone accts has been
+ // retrieved.
+ CompletableFuture<List<PhoneAccountHandle>> setAccountHandle =
+ accountsForCall.whenCompleteAsync((potentialPhoneAccounts, exception) -> {
+ Log.i(CallsManager.this, "set outgoing call phone acct stage");
+ PhoneAccountHandle phoneAccountHandle;
+ if (potentialPhoneAccounts.size() == 1) {
+ phoneAccountHandle = potentialPhoneAccounts.get(0);
+ } else {
+ phoneAccountHandle = null;
+ }
+ finalCall.setTargetPhoneAccount(phoneAccountHandle);
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.sOCPA"));
- // Do not support any more live calls. Our options are to move a call to hold, disconnect
- // a call, or cancel this call altogether. If a call is being reused, then it has already
- // passed the makeRoomForOutgoingCall check once and will fail the second time due to the
- // call transitioning into the CONNECTING state.
- if (!isPotentialInCallMMICode && (!isReusedCall
- && !makeRoomForOutgoingCall(call, call.isEmergencyCall()))) {
- Call foregroundCall = getForegroundCall();
- Log.d(this, "No more room for outgoing call %s ", call);
- if (foregroundCall.isSelfManaged()) {
- // If the ongoing call is a self-managed call, then prompt the user to ask if they'd
- // like to disconnect their ongoing call and place the outgoing call.
- call.setOriginalCallIntent(originalIntent);
- startCallConfirmation(call);
- } else {
- // If the ongoing call is a managed call, we will prevent the outgoing call from
- // dialing.
- notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
- }
- return null;
- }
- // The outgoing call can be placed, go forward.
+ // This composes the future containing the potential phone accounts with code that queries
+ // the suggestion service if necessary (i.e. if the list is longer than 1).
+ // If the suggestion service is queried, the inner lambda will return a future that
+ // completes when the suggestion service calls the callback.
+ CompletableFuture<List<PhoneAccountSuggestion>> suggestionFuture = accountsForCall.
+ thenComposeAsync(potentialPhoneAccounts -> {
+ Log.i(CallsManager.this, "call outgoing call suggestion service stage");
+ if (potentialPhoneAccounts.size() == 1) {
+ PhoneAccountSuggestion suggestion =
+ new PhoneAccountSuggestion(potentialPhoneAccounts.get(0),
+ PhoneAccountSuggestion.REASON_NONE, true);
+ return CompletableFuture.completedFuture(
+ Collections.singletonList(suggestion));
+ }
+ // todo: call onsuggestphoneaccount and bring back the list of suggestions
+ // from there. For now just map all the accounts to suggest_none
+ List<PhoneAccountSuggestion> suggestions =
+ potentialPhoneAccounts.stream().map(phoneAccountHandle ->
+ new PhoneAccountSuggestion(phoneAccountHandle,
+ PhoneAccountSuggestion.REASON_NONE, false)
+ ).collect(Collectors.toList());
- boolean needsAccountSelection =
- phoneAccountHandle == null && potentialPhoneAccounts.size() > 1
- && !call.isEmergencyCall() && !isSelfManaged;
- if (needsAccountSelection) {
- // This is the state where the user is expected to select an account
- call.setState(CallState.SELECT_PHONE_ACCOUNT, "needs account selection");
- // Create our own instance to modify (since extras may be Bundle.EMPTY)
- extras = new Bundle(extras);
- extras.putParcelableList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS,
- potentialPhoneAccounts);
- } else {
- PhoneAccount accountToUse =
- mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, 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);
- }
- }
+ return CompletableFuture.completedFuture(suggestions);
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.cOCSS"));
- call.setState(
- CallState.CONNECTING,
- phoneAccountHandle == null ? "no-handle" : phoneAccountHandle.toString());
- boolean isVoicemail = (call.getHandle() != null)
- && (PhoneAccount.SCHEME_VOICEMAIL.equals(call.getHandle().getScheme())
- || (accountToUse != null && mPhoneAccountRegistrar.isVoiceMailNumber(
- accountToUse.getAccountHandle(), call.getHandle().getSchemeSpecificPart())));
+ // This future checks the status of existing calls and attempts to make room for the
+ // outgoing call. The future returned by the inner method will usually be pre-completed --
+ // we only pause here if user interaction is required to disconnect a self-managed call.
+ // It runs after the account handle is set, independently of the phone account suggestion
+ // future.
+ CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync(
+ potentialPhoneAccounts -> {
+ Log.i(CallsManager.this, "make room for outgoing call stage");
+ boolean isPotentialInCallMMICode =
+ isPotentialInCallMMICode(handle) && !isSelfManaged;
+ // Do not support any more live calls. Our options are to move a call to hold,
+ // disconnect a call, or cancel this call altogether. If a call is being reused,
+ // then it has already passed the makeRoomForOutgoingCall check once and will
+ // fail the second time due to the call transitioning into the CONNECTING state.
+ if (!isPotentialInCallMMICode && (!isReusedCall
+ && !makeRoomForOutgoingCall(finalCall, finalCall.isEmergencyCall()))) {
+ Call foregroundCall = getForegroundCall();
+ Log.d(CallsManager.this, "No more room for outgoing call %s ", finalCall);
+ if (foregroundCall.isSelfManaged()) {
+ // If the ongoing call is a self-managed call, then prompt the user to
+ // ask if they'd like to disconnect their ongoing call and place the
+ // outgoing call.
+ Log.i(CallsManager.this, "Prompting user to disconnect "
+ + "self-managed call");
+ finalCall.setOriginalCallIntent(originalIntent);
+ CompletableFuture<Call> completionFuture = new CompletableFuture<>();
+ startCallConfirmation(finalCall, completionFuture);
+ return completionFuture;
+ } else {
+ // If the ongoing call is a managed call, we will prevent the outgoing
+ // call from dialing.
+ notifyCreateConnectionFailed(
+ finalCall.getTargetPhoneAccount(), finalCall);
+ }
+ Log.i(CallsManager.this, "Aborting call since there's no room");
+ return CompletableFuture.completedFuture(null);
+ }
+ return CompletableFuture.completedFuture(finalCall);
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.dSMCP"));
- if (!isVoicemail && (isRttSettingOn() || (extras != null
- && extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false)))) {
- Log.d(this, "Outgoing call requesting RTT, rtt setting is %b", isRttSettingOn());
- if (accountToUse != null
- && accountToUse.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
- call.createRttStreams();
- }
- // Even if the phone account doesn't support RTT yet, the connection manager might
- // change that. Set this to check it later.
- call.setRequestedToStartWithRtt();
- }
- }
- setIntentExtrasAndStartTime(call, extras);
- setCallSourceToAnalytics(call, originalIntent);
+ // The outgoing call can be placed, go forward. This future glues together the results of
+ // the account suggestion stage and the make room for call stage.
+ CompletableFuture<Pair<Call, List<PhoneAccountSuggestion>>> preSelectStage =
+ makeRoomForCall.thenCombine(suggestionFuture, Pair::create);
- if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) {
- // Do not add the call if it is a potential MMI code.
- call.addListener(this);
- } else 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);
- }
+ // This future takes the list of suggested accounts and the call and determines if more
+ // user interaction in the form of a phone account selection screen is needed. If so, it
+ // will set the call to SELECT_PHONE_ACCOUNT, add it to our internal list/send it to dialer,
+ // and then execution will pause pending the dialer calling phoneAccountSelected.
+ CompletableFuture<Pair<Call, PhoneAccountHandle>> dialerSelectPhoneAccountFuture =
+ preSelectStage.thenComposeAsync(
+ (args) -> {
+ Log.i(CallsManager.this, "dialer phone acct select stage");
+ Call callToPlace = args.first;
+ List<PhoneAccountSuggestion> accountSuggestions = args.second;
+ if (callToPlace == null) {
+ return CompletableFuture.completedFuture(null);
+ }
+ if (accountSuggestions == null || accountSuggestions.isEmpty()) {
+ Log.i(CallsManager.this, "Aborting call since there are no"
+ + " available accounts.");
+ return CompletableFuture.completedFuture(null);
+ }
+ boolean needsAccountSelection = accountSuggestions.size() > 1
+ && !callToPlace.isEmergencyCall() && !isSelfManaged;
+ if (!needsAccountSelection) {
+ return CompletableFuture.completedFuture(Pair.create(callToPlace,
+ accountSuggestions.get(0).getPhoneAccountHandle()));
+ }
+ // This is the state where the user is expected to select an account
+ callToPlace.setState(CallState.SELECT_PHONE_ACCOUNT,
+ "needs account selection");
+ // Create our own instance to modify (since extras may be Bundle.EMPTY)
+ Bundle newExtras = new Bundle(extras);
+ List<PhoneAccountHandle> accountsFromSuggestions = accountSuggestions
+ .stream()
+ .map(PhoneAccountSuggestion::getPhoneAccountHandle)
+ .collect(Collectors.toList());
+ newExtras.putParcelableList(
+ android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS,
+ accountsFromSuggestions);
+ // Set a future in place so that we can proceed once the dialer replies.
+ mPendingAccountSelection = new CompletableFuture<>();
+ callToPlace.setIntentExtras(newExtras);
- return call;
+ addCall(callToPlace);
+ return mPendingAccountSelection;
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.dSPA"));
+
+ // Finally, after all user interaction is complete, we execute this code to finish setting
+ // up the outgoing call. The inner method always returns a completed future containing the
+ // call that we've finished setting up.
+ mLatestPostSelectionProcessingFuture = dialerSelectPhoneAccountFuture
+ .thenComposeAsync(args -> {
+ if (args == null) {
+ return CompletableFuture.completedFuture(null);
+ }
+ Log.i(CallsManager.this, "post acct selection stage");
+ Call callToUse = args.first;
+ PhoneAccountHandle phoneAccountHandle = args.second;
+ PhoneAccount accountToUse = mPhoneAccountRegistrar
+ .getPhoneAccount(phoneAccountHandle, initiatingUser);
+ callToUse.setTargetPhoneAccount(phoneAccountHandle);
+ 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",
+ callToUse.getId());
+ callToUse.setIsVoipAudioMode(true);
+ }
+ }
+
+ callToUse.setState(
+ CallState.CONNECTING,
+ phoneAccountHandle == null ? "no-handle"
+ : phoneAccountHandle.toString());
+
+ boolean isVoicemail = isVoicemail(callToUse.getHandle(), accountToUse);
+
+ if (!isVoicemail && (isRttSettingOn() || (extras != null
+ && extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT,
+ false)))) {
+ Log.d(this, "Outgoing call requesting RTT, rtt setting is %b",
+ isRttSettingOn());
+ if (accountToUse != null
+ && accountToUse.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
+ callToUse.createRttStreams();
+ }
+ // Even if the phone account doesn't support RTT yet,
+ // the connection manager might change that. Set this to check it later.
+ callToUse.setRequestedToStartWithRtt();
+ }
+
+ setIntentExtrasAndStartTime(callToUse, extras);
+ setCallSourceToAnalytics(callToUse, originalIntent);
+
+ if (isPotentialMMICode(handle) && !isSelfManaged) {
+ // Do not add the call if it is a potential MMI code.
+ callToUse.addListener(this);
+ } else if (!mCalls.contains(callToUse)) {
+ // We check if mCalls already contains the call because we could
+ // potentially be reusing
+ // a call which was previously added (See {@link #reuseOutgoingCall}).
+ addCall(callToUse);
+ }
+ return CompletableFuture.completedFuture(callToUse);
+ }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.pASP"));
+ return mLatestPostSelectionProcessingFuture;
}
/**
@@ -1364,55 +1501,71 @@
* @return
*/
@VisibleForTesting
- public List<PhoneAccountHandle> findOutgoingCallPhoneAccount(
+ public CompletableFuture<List<PhoneAccountHandle>> findOutgoingCallPhoneAccount(
PhoneAccountHandle targetPhoneAccountHandle, Uri handle, boolean isVideo,
UserHandle initiatingUser) {
- boolean isSelfManaged = isSelfManaged(targetPhoneAccountHandle, initiatingUser);
+ if (isSelfManaged(targetPhoneAccountHandle, initiatingUser)) {
+ return CompletableFuture.completedFuture(Arrays.asList(targetPhoneAccountHandle));
+ }
List<PhoneAccountHandle> accounts;
- if (!isSelfManaged) {
- // Try to find a potential phone account, taking into account whether this is a video
- // call.
- accounts = constructPossiblePhoneAccounts(handle, initiatingUser, isVideo);
- if (isVideo && accounts.size() == 0) {
- // Placing a video call but no video capable accounts were found, so consider any
- // call capable accounts (we can fallback to audio).
- accounts = constructPossiblePhoneAccounts(handle, initiatingUser,
- false /* isVideo */);
- }
- Log.v(this, "findOutgoingCallPhoneAccount: accounts = " + accounts);
-
- // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this
- // call as if a phoneAccount was not specified (does the default behavior instead).
- // Note: We will not attempt to dial with a requested phoneAccount if it is disabled.
- if (targetPhoneAccountHandle != null) {
- if (!accounts.contains(targetPhoneAccountHandle)) {
- targetPhoneAccountHandle = null;
- } else {
- // The target phone account is valid and was found.
- return Arrays.asList(targetPhoneAccountHandle);
- }
- }
-
- if (targetPhoneAccountHandle == null && accounts.size() > 0) {
- // No preset account, check if default exists that supports the URI scheme for the
- // handle and verify it can be used.
- if (accounts.size() > 1) {
- PhoneAccountHandle defaultPhoneAccountHandle =
- mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(
- handle.getScheme(), initiatingUser);
- if (defaultPhoneAccountHandle != null &&
- accounts.contains(defaultPhoneAccountHandle)) {
- accounts.clear();
- accounts.add(defaultPhoneAccountHandle);
- }
- }
- }
- } else {
- // Self-managed ConnectionServices can only have a single potential account.
- accounts = Arrays.asList(targetPhoneAccountHandle);
+ // Try to find a potential phone account, taking into account whether this is a video
+ // call.
+ accounts = constructPossiblePhoneAccounts(handle, initiatingUser, isVideo);
+ if (isVideo && accounts.size() == 0) {
+ // Placing a video call but no video capable accounts were found, so consider any
+ // call capable accounts (we can fallback to audio).
+ accounts = constructPossiblePhoneAccounts(handle, initiatingUser,
+ false /* isVideo */);
}
- return accounts;
+ Log.v(this, "findOutgoingCallPhoneAccount: accounts = " + accounts);
+
+ // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this
+ // call as if a phoneAccount was not specified (does the default behavior instead).
+ // Note: We will not attempt to dial with a requested phoneAccount if it is disabled.
+ if (targetPhoneAccountHandle != null) {
+ if (accounts.contains(targetPhoneAccountHandle)) {
+ // The target phone account is valid and was found.
+ return CompletableFuture.completedFuture(Arrays.asList(targetPhoneAccountHandle));
+ }
+ }
+
+ // Do the query for whether there's a preferred contact
+ final CompletableFuture<PhoneAccountHandle> userPreferredAccountForContact =
+ new CompletableFuture<>();
+ final List<PhoneAccountHandle> possibleAccounts = accounts;
+ mCallerInfoLookupHelper.startLookup(handle,
+ new CallerInfoLookupHelper.OnQueryCompleteListener() {
+ @Override
+ public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
+ // TODO: construct the acct handle from caller info
+ userPreferredAccountForContact.complete(null);
+ }
+
+ @Override
+ public void onContactPhotoQueryComplete(Uri handle, CallerInfo info) {
+ // ignore this
+ }
+ });
+
+ return userPreferredAccountForContact.thenApply(phoneAccountHandle -> {
+ if (phoneAccountHandle != null) {
+ return Collections.singletonList(phoneAccountHandle);
+ }
+ if (possibleAccounts.isEmpty() || possibleAccounts.size() == 1) {
+ return possibleAccounts;
+ }
+ // No preset account, check if default exists that supports the URI scheme for the
+ // handle and verify it can be used.
+ PhoneAccountHandle defaultPhoneAccountHandle =
+ mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(
+ handle.getScheme(), initiatingUser);
+ if (defaultPhoneAccountHandle != null &&
+ possibleAccounts.contains(defaultPhoneAccountHandle)) {
+ return Collections.singletonList(defaultPhoneAccountHandle);
+ }
+ return possibleAccounts;
+ });
}
/**
@@ -1671,6 +1824,15 @@
} else {
mLocallyDisconnectingCalls.add(call);
call.disconnect();
+ // Cancel any of the outgoing call futures if they're still around.
+ if (mPendingCallConfirm != null && !mPendingCallConfirm.isDone()) {
+ mPendingCallConfirm.complete(null);
+ mPendingCallConfirm = null;
+ }
+ if (mPendingAccountSelection != null && !mPendingAccountSelection.isDone()) {
+ mPendingAccountSelection.complete(null);
+ mPendingAccountSelection = null;
+ }
}
}
@@ -1892,51 +2054,15 @@
if (!mCalls.contains(call)) {
Log.i(this, "Attempted to add account to unknown call %s", call);
} else {
- call.setTargetPhoneAccount(account);
- PhoneAccount realPhoneAccount =
- mPhoneAccountRegistrar.getPhoneAccountUnchecked(account);
- if (realPhoneAccount != null && realPhoneAccount.getExtras() != null
- && realPhoneAccount.getExtras()
- .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
- Log.d("phoneAccountSelected: default to voip mode for call %s", call.getId());
- call.setIsVoipAudioMode(true);
- }
-
- boolean isVoicemail = (call.getHandle() != null)
- && (PhoneAccount.SCHEME_VOICEMAIL.equals(call.getHandle().getScheme())
- || (realPhoneAccount != null && mPhoneAccountRegistrar.isVoiceMailNumber(
- realPhoneAccount.getAccountHandle(),
- call.getHandle().getSchemeSpecificPart())));
-
- if (!isVoicemail && (isRttSettingOn() || call.getIntentExtras()
- .getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false))) {
- Log.d(this, "Outgoing call after account selection requesting RTT," +
- " rtt setting is %b", isRttSettingOn());
- if (realPhoneAccount != null
- && realPhoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
- call.createRttStreams();
- }
- // Even if the phone account doesn't support RTT yet, the connection manager might
- // change that. Set this to check it later.
- call.setRequestedToStartWithRtt();
- }
-
- if (!call.isNewOutgoingCallIntentBroadcastDone()) {
- return;
- }
-
- // Note: emergency calls never go through account selection dialog so they never
- // arrive here.
- if (makeRoomForOutgoingCall(call, false /* isEmergencyCall */)) {
- call.startCreateConnection(mPhoneAccountRegistrar);
- } else {
- call.disconnect("no room");
- }
-
if (setDefault) {
mPhoneAccountRegistrar
.setUserSelectedOutgoingPhoneAccount(account, call.getInitiatingUser());
}
+
+ if (mPendingAccountSelection != null) {
+ mPendingAccountSelection.complete(Pair.create(call, account));
+ mPendingAccountSelection = null;
+ }
}
}
@@ -2101,7 +2227,7 @@
* indicate to the user that the call cannot me placed due to an ongoing call in another app.
*
* Used when there are ongoing self-managed calls and the user tries to make an outgoing managed
- * call. Called by {@link #startCallConfirmation(Call)} when the user is already confirming an
+ * call. Called by {@link #startCallConfirmation} when the user is already confirming an
* outgoing call. Realistically this should almost never be called since in practice the user
* won't make multiple outgoing calls at the same time.
*
@@ -2324,6 +2450,11 @@
return mPhoneNumberUtilsAdapter;
}
+ @VisibleForTesting
+ public CompletableFuture<Call> getLatestPostSelectionProcessingFuture() {
+ return mLatestPostSelectionProcessingFuture;
+ }
+
/**
* Returns the first call that it finds with the given states. The states are treated as having
* priority order so that any call with the first state will be returned before any call with
@@ -2383,8 +2514,6 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
null /* handle */,
null /* gatewayInfo */,
@@ -3099,8 +3228,6 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
connection.getHandle() /* handle */,
null /* gatewayInfo */,
@@ -3345,16 +3472,13 @@
Log.i(this, "confirmPendingCall: callId=%s", callId);
if (mPendingCall != null && mPendingCall.getId().equals(callId)) {
Log.addEvent(mPendingCall, LogUtils.Events.USER_CONFIRMED);
- addCall(mPendingCall);
// We are going to place the new outgoing call, so disconnect any ongoing self-managed
// calls which are ongoing at this time.
disconnectSelfManagedCalls("outgoing call " + callId);
- // Kick of the new outgoing call intent from where it left off prior to confirming the
- // call.
- CallIntentProcessor.sendNewOutgoingCallIntent(mContext, mPendingCall, this,
- mPendingCall.getOriginalCallIntent());
+ mPendingCallConfirm.complete(mPendingCall);
+ mPendingCallConfirm = null;
mPendingCall = null;
}
}
@@ -3373,6 +3497,8 @@
markCallAsDisconnected(mPendingCall, new DisconnectCause(DisconnectCause.CANCELED));
markCallAsRemoved(mPendingCall);
mPendingCall = null;
+ mPendingCallConfirm.complete(null);
+ mPendingCallConfirm = null;
}
}
@@ -3383,15 +3509,17 @@
* outgoing call or not.
* @param call The call to confirm.
*/
- private void startCallConfirmation(Call call) {
+ private void startCallConfirmation(Call call, CompletableFuture<Call> confirmationFuture) {
if (mPendingCall != null) {
Log.i(this, "startCallConfirmation: call %s is already pending; disconnecting %s",
mPendingCall.getId(), call.getId());
markCallDisconnectedDueToSelfManagedCall(call);
+ confirmationFuture.complete(null);
return;
}
Log.addEvent(call, LogUtils.Events.USER_CONFIRMATION);
mPendingCall = call;
+ mPendingCallConfirm = confirmationFuture;
// Figure out the name of the app in charge of the self-managed call(s).
Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
@@ -3540,6 +3668,18 @@
call.getAnalytics().setCallSource(callSource);
}
+ private boolean isVoicemail(Uri callHandle, PhoneAccount phoneAccount) {
+ if (callHandle == null) {
+ return false;
+ }
+ if (PhoneAccount.SCHEME_VOICEMAIL.equals(callHandle.getScheme())) {
+ return true;
+ }
+ return phoneAccount != null && mPhoneAccountRegistrar.isVoiceMailNumber(
+ phoneAccount.getAccountHandle(),
+ callHandle.getSchemeSpecificPart());
+ }
+
/**
* Notifies the {@link android.telecom.ConnectionService} associated with a
* {@link PhoneAccountHandle} that the attempt to create a new connection has failed.
@@ -3615,9 +3755,8 @@
}
extras.putParcelable(TelecomManager.EXTRA_CALL_AUDIO_STATE,
mCallAudioManager.getCallAudioState());
- Call handoverToCall = startOutgoingCall(handoverFromCall.getHandle(), handoverToHandle,
- extras, getCurrentUserHandle(), null /* originalIntent */,
- null /* callingPackage */);
+ Call handoverToCall = createHandoverCall(handoverFromCall.getHandle(), handoverToHandle,
+ extras, getCurrentUserHandle());
if (handoverToCall == null) {
handoverFromCall.sendCallEvent(android.telecom.Call.EVENT_HANDOVER_FAILED, null);
return;
@@ -3634,6 +3773,110 @@
videoState);
}
+ 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
@@ -3676,7 +3919,7 @@
Call call = new Call(getNextCallId(), mContext,
this, mLock, mConnectionServiceRepository,
- mContactsAsyncHelper, mCallerInfoAsyncQueryFactory, mPhoneNumberUtilsAdapter,
+ mPhoneNumberUtilsAdapter,
handoverFromCall.getHandle(), null,
null, null,
Call.CALL_DIRECTION_OUTGOING, false,
@@ -3876,8 +4119,6 @@
this,
mLock,
mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
srcAddr,
null /* gatewayInfo */,
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 0cc7ad2..98d5bba 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -427,11 +427,11 @@
}
@VisibleForTesting
- public void startTone() {
+ public boolean startTone() {
// Skip playing the end call tone if the volume is silenced.
if (mToneId == TONE_CALL_ENDED && !mAudioManagerAdapter.isVolumeOverZero()) {
Log.i(this, "startTone: skip end-call tone as device is silenced.");
- return;
+ return false;
}
sTonesPlaying++;
@@ -447,6 +447,7 @@
}
super.start();
+ return true;
}
@Override
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 9153414..ffa6035 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -243,6 +243,10 @@
mMissedCallNotifier = missedCallNotifierImplFactory
.makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar, defaultDialerCache);
+ CallerInfoLookupHelper callerInfoLookupHelper =
+ new CallerInfoLookupHelper(context, callerInfoAsyncQueryFactory,
+ mContactsAsyncHelper, mLock);
+
EmergencyCallHelper emergencyCallHelper = new EmergencyCallHelper(mContext,
mContext.getResources().getString(R.string.ui_default_package), timeoutsAdapter);
@@ -262,8 +266,7 @@
mCallsManager = new CallsManager(
mContext,
mLock,
- mContactsAsyncHelper,
- callerInfoAsyncQueryFactory,
+ callerInfoLookupHelper,
mMissedCallNotifier,
mPhoneAccountRegistrar,
headsetMediaButtonFactory,
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index e863d27..51bfed7 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -338,6 +338,9 @@
mInCallServiceFixtureY.getCall(callId).getState());
mInCallServiceFixtureX.mInCallAdapter.phoneAccountSelected(callId,
mPhoneAccountA0.getAccountHandle(), false);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ verifyAndProcessOutgoingCallBroadcast(mPhoneAccountA0.getAccountHandle());
IdPair ids = outgoingCallPhoneAccountSelected(mPhoneAccountA0.getAccountHandle(),
startingNumConnections, startingNumCalls, mConnectionServiceFixtureA);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
index e4b22ec..01add22 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
@@ -77,7 +77,7 @@
InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
doAnswer((invocation2) -> {
mCallAudioManager.setIsTonePlaying(true);
- return null;
+ return true;
}).when(mockInCallTonePlayer).startTone();
return mockInCallTonePlayer;
}).when(mPlayerFactory).createPlayer(anyInt());
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index dff89c9..fe4213f 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -50,6 +51,7 @@
import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallerInfoAsyncQueryFactory;
+import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.ConnectionServiceFocusManager;
@@ -87,6 +89,8 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -120,8 +124,7 @@
private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
- @Mock private ContactsAsyncHelper mContactsAsyncHelper;
- @Mock private CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
+ @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
@Mock private MissedCallNotifier mMissedCallNotifier;
@Mock private PhoneAccountRegistrar mPhoneAccountRegistrar;
@Mock private HeadsetMediaButton mHeadsetMediaButton;
@@ -178,8 +181,7 @@
mCallsManager = new CallsManager(
mComponentContextFixture.getTestDouble().getApplicationContext(),
mLock,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
+ mCallerInfoLookupHelper,
mMissedCallNotifier,
mPhoneAccountRegistrar,
mHeadsetMediaButtonFactory,
@@ -234,8 +236,6 @@
mCallsManager,
mLock,
null /* ConnectionServiceRepository */,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
TEST_ADDRESS,
null /* GatewayInfo */,
@@ -268,6 +268,15 @@
assertEquals(2, phoneAccountHandles.size());
}
+ private void setupCallerInfoLookupHelper() {
+ doAnswer(invocation -> {
+ Uri handle = invocation.getArgument(0);
+ CallerInfoLookupHelper.OnQueryCompleteListener listener = invocation.getArgument(1);
+ listener.onCallerInfoQueryComplete(handle, null);
+ return null;
+ }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class),
+ any(CallerInfoLookupHelper.OnQueryCompleteListener.class));
+ }
/**
* Tests finding the outgoing call phone account where the call is being placed on a
* self-managed ConnectionService.
@@ -276,8 +285,10 @@
@MediumTest
@Test
public void testFindOutgoingCallPhoneAccountSelfManaged() throws Exception {
+ setupCallerInfoLookupHelper();
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- SELF_MANAGED_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */);
+ SELF_MANAGED_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */)
+ .get();
assertEquals(1, accounts.size());
assertEquals(SELF_MANAGED_HANDLE, accounts.get(0));
}
@@ -290,6 +301,7 @@
@MediumTest
@Test
public void testFindOutgoingCallAccountDefault() throws Exception {
+ setupCallerInfoLookupHelper();
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
SIM_1_HANDLE);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -297,7 +309,8 @@
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, null /* userHandle */);
+ null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, null /* userHandle */)
+ .get();
// Should have found just the default.
assertEquals(1, accounts.size());
@@ -312,6 +325,7 @@
@MediumTest
@Test
public void testFindOutgoingCallAccountNoDefault() throws Exception {
+ setupCallerInfoLookupHelper();
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -319,7 +333,8 @@
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, null /* userHandle */);
+ null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, null /* userHandle */)
+ .get();
assertEquals(2, accounts.size());
assertTrue(accounts.contains(SIM_1_HANDLE));
@@ -334,6 +349,7 @@
@MediumTest
@Test
public void testFindOutgoingCallAccountVideo() throws Exception {
+ setupCallerInfoLookupHelper();
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -341,7 +357,8 @@
new ArrayList<>(Arrays.asList(SIM_2_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, null /* userHandle */);
+ null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, null /* userHandle */)
+ .get();
assertEquals(1, accounts.size());
assertTrue(accounts.contains(SIM_2_HANDLE));
@@ -355,6 +372,7 @@
@MediumTest
@Test
public void testFindOutgoingCallAccountVideoNotAvailable() throws Exception {
+ setupCallerInfoLookupHelper();
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
// When querying for video capable accounts, return nothing.
@@ -366,7 +384,8 @@
any(), eq(0 /* none specified */))).thenReturn(
new ArrayList<>(Arrays.asList(SIM_1_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, null /* userHandle */);
+ null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, null /* userHandle */)
+ .get();
// Should have found one.
assertEquals(1, accounts.size());
@@ -380,6 +399,7 @@
@MediumTest
@Test
public void testUseSpecifiedAccount() throws Exception {
+ setupCallerInfoLookupHelper();
when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
null);
when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -387,7 +407,7 @@
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
- SIM_2_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */);
+ SIM_2_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */).get();
assertEquals(1, accounts.size());
assertTrue(accounts.contains(SIM_2_HANDLE));
@@ -912,8 +932,6 @@
mCallsManager,
mLock, /* ConnectionServiceRepository */
null,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
TEST_ADDRESS,
null /* GatewayInfo */,
diff --git a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
index 7718da8..eba494f 100644
--- a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
+++ b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
@@ -16,6 +16,8 @@
package com.android.server.telecom.tests;
+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;
@@ -114,7 +116,7 @@
public void testNoEndCallToneInSilence() {
when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(false);
InCallTonePlayer player = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
- player.startTone();
+ assertFalse(player.startTone());
// Verify we didn't play a tone.
verify(mCallAudioManager, never()).setIsTonePlaying(eq(true));
@@ -126,7 +128,7 @@
public void testEndCallToneWhenNotSilenced() {
when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
InCallTonePlayer player = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
- player.startTone();
+ assertTrue(player.startTone());
// Verify we did play a tone.
verify(mCallAudioManager).setIsTonePlaying(eq(true));
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index ab5ddef..02ee137 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -123,6 +123,7 @@
when(mockAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
NotificationManager notificationManager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ when(mockTonePlayer.startTone()).thenReturn(true);
when(notificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
mockRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 282a74e..c60a428 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -561,8 +561,11 @@
ConnectionServiceFixture connectionServiceFixture)
throws Exception {
- return startOutgoingPhoneCallPendingCreateConnection(number, null,
- connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY);
+ startOutgoingPhoneCallWaitForBroadcaster(number, null,
+ connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY,
+ false /*isEmergency*/);
+
+ return mInCallServiceFixtureX.mLatestCallId;
}
protected IdPair outgoingCallPhoneAccountSelected(PhoneAccountHandle phoneAccountHandle,
@@ -687,13 +690,17 @@
// Send the CallerInfo lookup reply.
mCallerInfoAsyncQueryFactoryFixture.mRequests.forEach(
CallerInfoAsyncQueryFactoryFixture.Request::reply);
+ if (phoneAccountHandle != null) {
+ mTelecomSystem.getCallsManager().getLatestPostSelectionProcessingFuture().join();
+ }
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
boolean isSelfManaged = phoneAccountHandle == mPhoneAccountSelfManaged.getAccountHandle();
if (!hasInCallAdapter && !isSelfManaged) {
- verify(mInCallServiceFixtureX.getTestDouble())
+ verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
.setInCallAdapter(
any(IInCallAdapter.class));
- verify(mInCallServiceFixtureY.getTestDouble())
+ verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
.setInCallAdapter(
any(IInCallAdapter.class));
}
@@ -705,7 +712,13 @@
int videoState) throws Exception {
startOutgoingPhoneCallWaitForBroadcaster(number,phoneAccountHandle,
connectionServiceFixture, initiatingUser, videoState, false /*isEmergency*/);
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ verifyAndProcessOutgoingCallBroadcast(phoneAccountHandle);
+ return mInCallServiceFixtureX.mLatestCallId;
+ }
+
+ protected void verifyAndProcessOutgoingCallBroadcast(PhoneAccountHandle phoneAccountHandle) {
ArgumentCaptor<Intent> newOutgoingCallIntent =
ArgumentCaptor.forClass(Intent.class);
ArgumentCaptor<BroadcastReceiver> newOutgoingCallReceiver =
@@ -734,7 +747,6 @@
newOutgoingCallIntent.getValue());
}
- return mInCallServiceFixtureX.mLatestCallId;
}
// When Telecom is redialing due to an error, we need to make sure the number of connections
@@ -852,20 +864,6 @@
//Wait for/Verify call blocking happened asynchronously
incomingCallAddedLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
- // Do the blocked number check only for non-self-managed calls
- PhoneAccount pa = mTelecomSystem.getPhoneAccountRegistrar()
- .getPhoneAccount(phoneAccountHandle, phoneAccountHandle.getUserHandle());
- if (!pa.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
- IContentProvider blockedNumberProvider =
- mSpyContext.getContentResolver().acquireProvider(
- BlockedNumberContract.AUTHORITY);
- verify(blockedNumberProvider, timeout(TEST_TIMEOUT)).call(
- anyString(),
- eq(BlockedNumberContract.SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER),
- eq(number),
- isNotNull(Bundle.class));
- }
-
// For the case of incoming calls, Telecom connecting the InCall services and adding the
// Call is triggered by the async completion of the CallerInfoAsyncQuery. Once the Call
// is added, future interactions as triggered by the ConnectionService, through the various