/src/com/android/server/telecom/CallsManager.java
Java | 1211 lines | 830 code | 134 blank | 247 comment | 197 complexity | bb6d4cbacd50734cb04d7a880325b5c7 MD5 | raw file
- /*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.android.server.telecom;
- import android.content.Context;
- import android.net.Uri;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Looper;
- import android.os.Message;
- import android.os.SystemProperties;
- import android.os.Trace;
- import android.provider.CallLog.Calls;
- import android.provider.Settings;
- import android.telecom.CallAudioState;
- import android.telecom.Conference;
- import android.telecom.Connection;
- import android.telecom.DisconnectCause;
- import android.telecom.GatewayInfo;
- import android.telecom.ParcelableConference;
- import android.telecom.ParcelableConnection;
- import android.telecom.PhoneAccount;
- import android.telecom.PhoneAccountHandle;
- import android.telecom.TelecomManager;
- import android.telecom.VideoProfile;
- import android.telephony.PhoneNumberUtils;
- import android.telephony.TelephonyManager;
- import android.text.TextUtils;
- import com.android.internal.annotations.VisibleForTesting;
- import com.android.internal.telephony.PhoneConstants;
- import com.android.internal.telephony.TelephonyProperties;
- import com.android.internal.util.IndentingPrintWriter;
- import com.android.server.telecom.ui.ViceNotificationImpl;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Objects;
- import java.util.Set;
- import java.util.concurrent.ConcurrentHashMap;
- /**
- * Singleton.
- *
- * NOTE: by design most APIs are package private, use the relevant adapter/s to allow
- * access from other packages specifically refraining from passing the CallsManager instance
- * beyond the com.android.server.telecom package boundary.
- */
- @VisibleForTesting
- public class CallsManager extends Call.ListenerBase implements VideoProviderProxy.Listener {
- // TODO: Consider renaming this CallsManagerPlugin.
- interface CallsManagerListener {
- void onCallAdded(Call call);
- void onCallRemoved(Call call);
- void onCallStateChanged(Call call, int oldState, int newState);
- void onConnectionServiceChanged(
- Call call,
- ConnectionServiceWrapper oldService,
- ConnectionServiceWrapper newService);
- void onIncomingCallAnswered(Call call);
- void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage);
- void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall);
- void onCallAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState);
- void onRingbackRequested(Call call, boolean ringback);
- void onIsConferencedChanged(Call call);
- void onIsVoipAudioModeChanged(Call call);
- void onVideoStateChanged(Call call);
- void onCanAddCallChanged(boolean canAddCall);
- void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
- void onMergeFailed(Call call);
- }
- private static final String TAG = "CallsManager";
- private static final int MAXIMUM_LIVE_CALLS = 1;
- private static final int MAXIMUM_HOLD_CALLS = 1;
- private static final int MAXIMUM_RINGING_CALLS = 1;
- private static final int MAXIMUM_DIALING_CALLS = 1;
- private static final int MAXIMUM_OUTGOING_CALLS = 1;
- private static final int MAXIMUM_TOP_LEVEL_CALLS = 2;
- private static final int MAXIMUM_DSDA_LIVE_CALLS = 2;
- private static final int MAXIMUM_DSDA_HOLD_CALLS = 2;
- private static final int MAXIMUM_DSDA_TOP_LEVEL_CALLS = 4;
- private static final int[] OUTGOING_CALL_STATES =
- {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING};
- private static final int[] LIVE_CALL_STATES =
- {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
- CallState.ACTIVE};
- /**
- * The main call repository. Keeps an instance of all live calls. New incoming and outgoing
- * calls are added to the map and removed when the calls move to the disconnected state.
- *
- * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
- * load factor before resizing, 1 means we only expect a single thread to
- * access the map so make only a single shard
- */
- private final Set<Call> mCalls = Collections.newSetFromMap(
- new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
- private final ConnectionServiceRepository mConnectionServiceRepository;
- private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
- private final InCallController mInCallController;
- private final CallAudioManager mCallAudioManager;
- private RespondViaSmsManager mRespondViaSmsManager;
- private final Ringer mRinger;
- private final InCallWakeLockController mInCallWakeLockController;
- // For this set initial table size to 16 because we add 13 listeners in
- // the CallsManager constructor.
- private final Set<CallsManagerListener> mListeners = Collections.newSetFromMap(
- new ConcurrentHashMap<CallsManagerListener, Boolean>(16, 0.9f, 1));
- private final HeadsetMediaButton mHeadsetMediaButton;
- private final WiredHeadsetManager mWiredHeadsetManager;
- private final DockManager mDockManager;
- private final TtyManager mTtyManager;
- private final ProximitySensorManager mProximitySensorManager;
- private final PhoneStateBroadcaster mPhoneStateBroadcaster;
- 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 final ViceNotificationImpl mViceNotificationImpl;
- private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
- private final Set<Call> mPendingCallsToDisconnect = new HashSet<>();
- /* Handler tied to thread in which CallManager was initialized. */
- private final Handler mHandler = new Handler(Looper.getMainLooper());
- private boolean mCanAddCall = true;
- /**
- * The call the user is currently interacting with. This is the call that should have audio
- * focus and be visible in the in-call UI.
- */
- private Call mForegroundCall;
- private static final int LCH_PLAY_DTMF = 100;
- private static final int LCH_STOP_DTMF = 101;
- private static final int PHONE_START_DSDA_INCALL_TONE = 102;
- private static final int LCH_DTMF_PERIODICITY = 3000;
- private static final int LCH_DTMF_PERIOD = 500;
- private static final String sSupervisoryCallHoldToneConfig =
- SystemProperties.get("persist.radio.sch_tone", "none");
- private static InCallTonePlayer.Factory mPlayerFactory;
- private String mLchSub = null;
- private InCallTonePlayer mLocalCallReminderTonePlayer = null;
- private String mSubInConversation = null;
- private Runnable mStopTone;
- /**
- * Initializes the required Telecom components.
- */
- CallsManager(
- Context context,
- TelecomSystem.SyncRoot lock,
- ContactsAsyncHelper contactsAsyncHelper,
- CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
- MissedCallNotifier missedCallNotifier,
- PhoneAccountRegistrar phoneAccountRegistrar,
- HeadsetMediaButtonFactory headsetMediaButtonFactory,
- ProximitySensorManagerFactory proximitySensorManagerFactory,
- InCallWakeLockControllerFactory inCallWakeLockControllerFactory,
- ViceNotifier viceNotifier) {
- mContext = context;
- mLock = lock;
- mContactsAsyncHelper = contactsAsyncHelper;
- mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
- mPhoneAccountRegistrar = phoneAccountRegistrar;
- mMissedCallNotifier = missedCallNotifier;
- StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
- mWiredHeadsetManager = new WiredHeadsetManager(context);
- mDockManager = new DockManager(context);
- mCallAudioManager = new CallAudioManager(
- context, mLock, statusBarNotifier, mWiredHeadsetManager, mDockManager, this);
- InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager, lock);
- mPlayerFactory = playerFactory;
- mRinger = new Ringer(mCallAudioManager, this, playerFactory, context);
- mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
- mTtyManager = new TtyManager(context, mWiredHeadsetManager);
- mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
- mPhoneStateBroadcaster = new PhoneStateBroadcaster(this);
- mCallLogManager = new CallLogManager(context);
- mInCallController = new InCallController(context, mLock, this);
- mDtmfLocalTonePlayer = new DtmfLocalTonePlayer(context);
- mConnectionServiceRepository =
- new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
- mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
- mViceNotificationImpl = viceNotifier.create(mContext, this);
- mListeners.add(statusBarNotifier);
- mListeners.add(mCallLogManager);
- mListeners.add(mPhoneStateBroadcaster);
- mListeners.add(mInCallController);
- mListeners.add(mRinger);
- mListeners.add(new RingbackPlayer(this, playerFactory));
- mListeners.add(new InCallToneMonitor(playerFactory, this));
- mListeners.add(mCallAudioManager);
- mListeners.add(missedCallNotifier);
- mListeners.add(mDtmfLocalTonePlayer);
- mListeners.add(mHeadsetMediaButton);
- mListeners.add(mProximitySensorManager);
- mListeners.add(mViceNotificationImpl);
- mMissedCallNotifier.updateOnStartup(
- mLock, this, mContactsAsyncHelper, mCallerInfoAsyncQueryFactory);
- }
- ViceNotificationImpl getViceNotificationImpl() {
- return mViceNotificationImpl;
- }
- public void setRespondViaSmsManager(RespondViaSmsManager respondViaSmsManager) {
- if (mRespondViaSmsManager != null) {
- mListeners.remove(mRespondViaSmsManager);
- }
- mRespondViaSmsManager = respondViaSmsManager;
- mListeners.add(respondViaSmsManager);
- }
- public RespondViaSmsManager getRespondViaSmsManager() {
- return mRespondViaSmsManager;
- }
- @Override
- public void onSuccessfulOutgoingCall(Call call, int callState) {
- Log.v(this, "onSuccessfulOutgoingCall, %s", call);
- setCallState(call, callState, "successful outgoing call");
- if (!mCalls.contains(call)) {
- // Call was not added previously in startOutgoingCall due to it being a potential MMI
- // code, so add it now.
- addCall(call);
- }
- // The call's ConnectionService has been updated.
- for (CallsManagerListener listener : mListeners) {
- listener.onConnectionServiceChanged(call, null, call.getConnectionService());
- }
- markCallAsDialing(call);
- }
- @Override
- public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {
- Log.v(this, "onFailedOutgoingCall, call: %s", call);
- markCallAsRemoved(call);
- }
- @Override
- public void onSuccessfulIncomingCall(Call incomingCall) {
- Log.d(this, "onSuccessfulIncomingCall");
- setCallState(incomingCall, CallState.RINGING, "successful incoming call");
- if (hasMaximumRingingCalls(incomingCall.getTargetPhoneAccount().getId()) || hasMaximumDialingCalls() ) {
- incomingCall.reject(false, null);
- // since the call was not added to the list of calls, we have to call the missed
- // call notifier and the call logger manually.
- mMissedCallNotifier.showMissedCallNotification(incomingCall);
- mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE);
- } else {
- if (TelephonyManager.getDefault().getMultiSimConfiguration()
- == TelephonyManager.MultiSimVariants.DSDA) {
- incomingCall.mIsActiveSub = true;
- }
- addCall(incomingCall);
- setActiveSubscription(incomingCall.getTargetPhoneAccount().getId());
- }
- }
- @Override
- public void onFailedIncomingCall(Call call) {
- setCallState(call, CallState.DISCONNECTED, "failed incoming call");
- call.removeListener(this);
- }
- @Override
- public void onSuccessfulUnknownCall(Call call, int callState) {
- setCallState(call, callState, "successful unknown call");
- Log.i(this, "onSuccessfulUnknownCall for call %s", call);
- addCall(call);
- }
- @Override
- public void onFailedUnknownCall(Call call) {
- Log.i(this, "onFailedUnknownCall for call %s", call);
- setCallState(call, CallState.DISCONNECTED, "failed unknown call");
- call.removeListener(this);
- }
- @Override
- public void onRingbackRequested(Call call, boolean ringback) {
- for (CallsManagerListener listener : mListeners) {
- listener.onRingbackRequested(call, ringback);
- }
- }
- @Override
- public void onPostDialWait(Call call, String remaining) {
- mInCallController.onPostDialWait(call, remaining);
- }
- @Override
- public void onPostDialChar(final Call call, char nextChar) {
- if (PhoneNumberUtils.is12Key(nextChar)) {
- // Play tone if it is one of the dialpad digits, canceling out the previously queued
- // up stopTone runnable since playing a new tone automatically stops the previous tone.
- if (mStopTone != null) {
- mHandler.removeCallbacks(mStopTone);
- }
- mDtmfLocalTonePlayer.playTone(call, nextChar);
- // TODO: Create a LockedRunnable class that does the synchronization automatically.
- mStopTone = new Runnable() {
- @Override
- public void run() {
- synchronized (mLock) {
- // Set a timeout to stop the tone in case there isn't another tone to follow.
- mDtmfLocalTonePlayer.stopTone(call);
- }
- }
- };
- mHandler.postDelayed(
- mStopTone,
- Timeouts.getDelayBetweenDtmfTonesMillis(mContext.getContentResolver()));
- } else if (nextChar == 0 || nextChar == TelecomManager.DTMF_CHARACTER_WAIT ||
- nextChar == TelecomManager.DTMF_CHARACTER_PAUSE) {
- // Stop the tone if a tone is playing, removing any other stopTone callbacks since
- // the previous tone is being stopped anyway.
- if (mStopTone != null) {
- mHandler.removeCallbacks(mStopTone);
- }
- mDtmfLocalTonePlayer.stopTone(call);
- } else {
- Log.w(this, "onPostDialChar: invalid value %d", nextChar);
- }
- }
- @Override
- public void onParentChanged(Call call) {
- // parent-child relationship affects which call should be foreground, so do an update.
- updateCallsManagerState();
- for (CallsManagerListener listener : mListeners) {
- listener.onIsConferencedChanged(call);
- }
- }
- @Override
- public void onChildrenChanged(Call call) {
- // parent-child relationship affects which call should be foreground, so do an update.
- updateCallsManagerState();
- for (CallsManagerListener listener : mListeners) {
- listener.onIsConferencedChanged(call);
- }
- }
- @Override
- public void onIsVoipAudioModeChanged(Call call) {
- for (CallsManagerListener listener : mListeners) {
- listener.onIsVoipAudioModeChanged(call);
- }
- }
- @Override
- public void onVideoStateChanged(Call call) {
- for (CallsManagerListener listener : mListeners) {
- listener.onVideoStateChanged(call);
- }
- }
- @Override
- public boolean onCanceledViaNewOutgoingCallBroadcast(final Call call) {
- mPendingCallsToDisconnect.add(call);
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- synchronized (mLock) {
- if (mPendingCallsToDisconnect.remove(call)) {
- Log.i(this, "Delayed disconnection of call: %s", call);
- call.disconnect();
- }
- }
- }
- }, Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver()));
- return true;
- }
- /**
- * Handles changes to the {@link Connection.VideoProvider} for a call. Adds the
- * {@link CallsManager} as a listener for the {@link VideoProviderProxy} which is created
- * in {@link Call#setVideoProvider(IVideoProvider)}. This allows the {@link CallsManager} to
- * respond to callbacks from the {@link VideoProviderProxy}.
- *
- * @param call The call.
- */
- @Override
- public void onVideoCallProviderChanged(Call call) {
- VideoProviderProxy videoProviderProxy = call.getVideoProviderProxy();
- if (videoProviderProxy == null) {
- return;
- }
- videoProviderProxy.addListener(this);
- }
- /**
- * Handles session modification requests received via the {@link TelecomVideoCallCallback} for
- * a call. Notifies listeners of the {@link CallsManager.CallsManagerListener} of the session
- * modification request.
- *
- * @param call The call.
- * @param videoProfile The {@link VideoProfile}.
- */
- @Override
- public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
- int videoState = videoProfile != null ? videoProfile.getVideoState() :
- VideoProfile.STATE_AUDIO_ONLY;
- Log.v(TAG, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile
- .videoStateToString(videoState));
- for (CallsManagerListener listener : mListeners) {
- listener.onSessionModifyRequestReceived(call, videoProfile);
- }
- }
- Collection<Call> getCalls() {
- return Collections.unmodifiableCollection(mCalls);
- }
- Call getForegroundCall() {
- return mForegroundCall;
- }
- Ringer getRinger() {
- return mRinger;
- }
- InCallController getInCallController() {
- return mInCallController;
- }
- boolean hasEmergencyCall() {
- for (Call call : mCalls) {
- if (call.isEmergencyCall()) {
- return true;
- }
- }
- return false;
- }
- boolean hasOnlyDisconnectedCalls() {
- for (Call call : mCalls) {
- if (!call.isDisconnected()) {
- return false;
- }
- }
- return true;
- }
- boolean hasVideoCall() {
- for (Call call : mCalls) {
- if (VideoProfile.isVideo(call.getVideoState())) {
- return true;
- }
- }
- return false;
- }
- CallAudioState getAudioState() {
- return mCallAudioManager.getCallAudioState();
- }
- boolean isTtySupported() {
- return mTtyManager.isTtySupported();
- }
- int getCurrentTtyMode() {
- return mTtyManager.getCurrentTtyMode();
- }
- void addListener(CallsManagerListener listener) {
- mListeners.add(listener);
- }
- void removeListener(CallsManagerListener listener) {
- mListeners.remove(listener);
- }
- /**
- * Starts the process to attach the call to a connection service.
- *
- * @param phoneAccountHandle 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.
- */
- void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
- Log.d(this, "processIncomingCallIntent");
- Uri handle = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS);
- if (handle == null) {
- // Required for backwards compatibility
- handle = extras.getParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER);
- }
- Call call = new Call(
- mContext,
- this,
- mLock,
- mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
- handle,
- null /* gatewayInfo */,
- null /* connectionManagerPhoneAccount */,
- phoneAccountHandle,
- true /* isIncoming */,
- false /* isConference */);
- call.setIntentExtras(extras);
- // TODO: Move this to be a part of addCall()
- call.addListener(this);
- call.startCreateConnection(mPhoneAccountRegistrar);
- }
- void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
- Uri handle = extras.getParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE);
- Log.i(this, "addNewUnknownCall with handle: %s", Log.pii(handle));
- Call call = new Call(
- mContext,
- this,
- mLock,
- mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
- handle,
- null /* gatewayInfo */,
- null /* connectionManagerPhoneAccount */,
- phoneAccountHandle,
- // Use onCreateIncomingConnection in TelephonyConnectionService, so that we attach
- // to the existing connection instead of trying to create a new one.
- true /* isIncoming */,
- false /* isConference */);
- call.setIsUnknown(true);
- call.setIntentExtras(extras);
- call.addListener(this);
- call.startCreateConnection(mPhoneAccountRegistrar);
- }
- private boolean areHandlesEqual(Uri handle1, Uri handle2) {
- if (handle1 == null || handle2 == null) {
- return handle1 == handle2;
- }
- if (!TextUtils.equals(handle1.getScheme(), handle2.getScheme())) {
- return false;
- }
- final String number1 = PhoneNumberUtils.normalizeNumber(handle1.getSchemeSpecificPart());
- final String number2 = PhoneNumberUtils.normalizeNumber(handle2.getSchemeSpecificPart());
- return TextUtils.equals(number1, number2);
- }
- private Call getNewOutgoingCall(Uri handle) {
- // First check to see if we can reuse any of the calls that are waiting to disconnect.
- // See {@link Call#abort} and {@link #onCanceledViaNewOutgoingCall} for more information.
- Call reusedCall = null;
- for (Call pendingCall : mPendingCallsToDisconnect) {
- if (reusedCall == null && areHandlesEqual(pendingCall.getHandle(), handle)) {
- mPendingCallsToDisconnect.remove(pendingCall);
- Log.i(this, "Reusing disconnected call %s", pendingCall);
- reusedCall = pendingCall;
- } else {
- Log.i(this, "Not reusing disconnected call %s", pendingCall);
- pendingCall.disconnect();
- }
- }
- if (reusedCall != null) {
- return reusedCall;
- }
- // 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.
- return new Call(
- mContext,
- this,
- mLock,
- mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
- handle,
- null /* gatewayInfo */,
- null /* connectionManagerPhoneAccount */,
- null /* phoneAccountHandle */,
- false /* isIncoming */,
- false /* isConference */);
- }
- /**
- * Kicks off the first steps to creating an outgoing call so that InCallUI can launch.
- *
- * @param handle Handle to connect the call with.
- * @param phoneAccountHandle 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.
- */
- Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) {
- Call call = getNewOutgoingCall(handle);
- if (extras!=null) {
- call.setVideoState(extras.getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
- VideoProfile.STATE_AUDIO_ONLY));
- }
- boolean isAddParticipant = ((extras != null) && (extras.getBoolean(
- TelephonyProperties.ADD_PARTICIPANT_KEY, false)));
- boolean isSkipSchemaOrConfUri = ((extras != null) && (extras.getBoolean(
- TelephonyProperties.EXTRA_SKIP_SCHEMA_PARSING, false) ||
- extras.getBoolean(TelephonyProperties.EXTRA_DIAL_CONFERENCE_URI, false)));
- if (isAddParticipant) {
- String number = handle.getSchemeSpecificPart();
- if (!isSkipSchemaOrConfUri) {
- number = PhoneNumberUtils.stripSeparators(number);
- }
- addParticipant(number);
- mInCallController.bringToForeground(false);
- return null;
- }
- // Force tel scheme for ims conf uri/skip schema calls to avoid selection of sip accounts
- String scheme = (isSkipSchemaOrConfUri? PhoneAccount.SCHEME_TEL: handle.getScheme());
- Log.d(this, "startOutgoingCall :: isAddParticipant=" + isAddParticipant
- + " isSkipSchemaOrConfUri=" + isSkipSchemaOrConfUri + " scheme=" + scheme);
- List<PhoneAccountHandle> accounts =
- mPhoneAccountRegistrar.getCallCapablePhoneAccounts(scheme, false);
- Log.v(this, "startOutgoingCall found accounts = " + accounts);
- if (mForegroundCall != null && TelephonyManager.getDefault().getMultiSimConfiguration()
- != TelephonyManager.MultiSimVariants.DSDA) {
- Call ongoingCall = mForegroundCall;
- // If there is an ongoing call, use the same phone account to place this new call.
- // If the ongoing call is a conference call, we fetch the phone account from the
- // child calls because we don't have targetPhoneAccount set on Conference calls.
- // TODO: Set targetPhoneAccount for all conference calls (b/23035408).
- if (ongoingCall.getTargetPhoneAccount() == null &&
- !ongoingCall.getChildCalls().isEmpty()) {
- ongoingCall = ongoingCall.getChildCalls().get(0);
- }
- if (ongoingCall.getTargetPhoneAccount() != null) {
- phoneAccountHandle = ongoingCall.getTargetPhoneAccount();
- }
- }
- // 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 (phoneAccountHandle != null) {
- if (!accounts.contains(phoneAccountHandle)) {
- phoneAccountHandle = null;
- }
- }
- if (phoneAccountHandle == null) {
- // No preset account, check if default exists that supports the URI scheme for the
- // handle.
- phoneAccountHandle =
- mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(scheme);
- }
- call.setTargetPhoneAccount(phoneAccountHandle);
- boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle);
- // 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 (!isPotentialInCallMMICode && !makeRoomForOutgoingCall(call, call.isEmergencyCall())) {
- // just cancel at this point.
- Log.i(this, "No remaining room for outgoing call: %s", call);
- if (mCalls.contains(call)) {
- // This call can already exist if it is a reused call,
- // See {@link #getNewOutgoingCall}.
- call.disconnect();
- }
- return null;
- }
- boolean needsAccountSelection = phoneAccountHandle == null && accounts.size() > 1 &&
- !call.isEmergencyCall();
- 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, accounts);
- } else {
- call.setState(
- CallState.CONNECTING,
- phoneAccountHandle == null ? "no-handle" : phoneAccountHandle.toString());
- }
- call.setIntentExtras(extras);
- // Do not add the call if it is a potential MMI code.
- if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) {
- 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 #getNewOutgoingCall}).
- addCall(call);
- }
- return call;
- }
- /**
- * Attempts to issue/connect the specified call.
- *
- * @param handle Handle to connect the call with.
- * @param gatewayInfo Optional gateway information that can be used to route the call to the
- * actual dialed handle via a gateway provider. May be null.
- * @param speakerphoneOn Whether or not to turn the speakerphone on once the call connects.
- * @param videoState The desired video state for the outgoing call.
- */
- void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn,
- int videoState) {
- if (call == null) {
- // don't do anything if the call no longer exists
- Log.i(this, "Canceling unknown call.");
- return;
- }
- final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayAddress();
- if (gatewayInfo == null) {
- Log.i(this, "Creating a new outgoing call with handle: %s", Log.piiHandle(uriHandle));
- } else {
- Log.i(this, "Creating a new outgoing call with gateway handle: %s, original handle: %s",
- Log.pii(uriHandle), Log.pii(handle));
- }
- call.setHandle(uriHandle);
- call.setGatewayInfo(gatewayInfo);
- // Auto-enable speakerphone if the originating intent specified to do so, or if the call
- // is a video call or if the phone is docked.
- call.setStartWithSpeakerphoneOn(speakerphoneOn || isSpeakerphoneAutoEnabled(videoState)
- || mDockManager.isDocked());
- call.setVideoState(videoState);
- if (call.isEmergencyCall()) {
- // Emergency -- CreateConnectionProcessor will choose accounts automatically
- call.setTargetPhoneAccount(null);
- }
- if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) {
- if (!call.isEmergencyCall()) {
- updateLchStatus(call.getTargetPhoneAccount().getId());
- }
- // If the account has been set, proceed to place the outgoing call.
- // Otherwise the connection will be initiated when the account is set by the user.
- call.startCreateConnection(mPhoneAccountRegistrar);
- }
- }
- /**
- * Attempts to add participant in a call.
- *
- * @param number number to connect the call with.
- */
- private void addParticipant(String number) {
- Log.i(this, "addParticipant number ="+number);
- if (getForegroundCall() == null) {
- // don't do anything if the call no longer exists
- Log.i(this, "Canceling unknown call.");
- return;
- } else {
- getForegroundCall().addParticipantWithConference(number);
- }
- }
- /**
- * Attempts to start a conference call for the specified call.
- *
- * @param call The call to conference.
- * @param otherCall The other call to conference with.
- */
- void conference(Call call, Call otherCall) {
- call.conferenceWith(otherCall);
- }
- /**
- * Instructs Telecom to answer the specified call. Intended to be invoked by the in-call
- * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by
- * the user opting to answer said call.
- *
- * @param call The call to answer.
- * @param videoState The video state in which to answer the call.
- */
- void answerCall(Call call, int videoState) {
- if (!mCalls.contains(call)) {
- Log.i(this, "Request to answer a non-existent call %s", call);
- } else {
- Call activeCall = getFirstCallWithState(call.getTargetPhoneAccount()
- .getId(), CallState.ACTIVE, CallState.DIALING);
- // If the foreground call is not the ringing call and it is currently isActive() or
- // STATE_DIALING, put it on hold before answering the call.
- if (activeCall != null && activeCall != call &&
- (activeCall.isActive() ||
- activeCall.getState() == CallState.DIALING)) {
- if (0 == (mForegroundCall.getConnectionCapabilities()
- & Connection.CAPABILITY_HOLD)) {
- // This call does not support hold. If it is from a different connection
- // service, then disconnect it, otherwise allow the connection service to
- // figure out the right states.
- if (activeCall.getConnectionService() != call.getConnectionService()) {
- activeCall.disconnect();
- }
- } else {
- Call heldCall = getHeldCall();
- if (heldCall != null) {
- Log.v(this, "Disconnecting held call %s before holding active call.",
- heldCall);
- heldCall.disconnect();
- }
- Log.v(this, "Holding active/dialing call %s before answering incoming call %s.",
- mForegroundCall, call);
- activeCall.hold();
- }
- // TODO: Wait until we get confirmation of the active call being
- // on-hold before answering the new call.
- // TODO: Import logic from CallManager.acceptCall()
- }
- for (CallsManagerListener listener : mListeners) {
- listener.onIncomingCallAnswered(call);
- }
- updateLchStatus(call.getTargetPhoneAccount().getId());
- // We do not update the UI until we get confirmation of the answer() through
- // {@link #markCallAsActive}.
- call.answer(videoState);
- if (isSpeakerphoneAutoEnabled(videoState)) {
- call.setStartWithSpeakerphoneOn(true);
- }
- }
- }
- /**
- * Determines if the speakerphone should be automatically enabled for the call. Speakerphone
- * should be enabled if the call is a video call and bluetooth or the wired headset are not in
- * use.
- *
- * @param videoState The video state of the call.
- * @return {@code true} if the speakerphone should be enabled.
- */
- private boolean isSpeakerphoneAutoEnabled(int videoState) {
- return VideoProfile.isVideo(videoState) &&
- !mWiredHeadsetManager.isPluggedIn() &&
- !mCallAudioManager.isBluetoothDeviceAvailable() &&
- isSpeakerEnabledForVideoCalls();
- }
- /**
- * Determines if the speakerphone should be automatically enabled for video calls.
- *
- * @return {@code true} if the speakerphone should automatically be enabled.
- */
- private static boolean isSpeakerEnabledForVideoCalls() {
- return (SystemProperties.getInt(TelephonyProperties.PROPERTY_VIDEOCALL_AUDIO_OUTPUT,
- PhoneConstants.AUDIO_OUTPUT_DEFAULT) ==
- PhoneConstants.AUDIO_OUTPUT_ENABLE_SPEAKER);
- }
- /**
- * Instructs Telecom to reject the specified call. Intended to be invoked by the in-call
- * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by
- * the user opting to reject said call.
- */
- void rejectCall(Call call, boolean rejectWithMessage, String textMessage) {
- if (!mCalls.contains(call)) {
- Log.i(this, "Request to reject a non-existent call %s", call);
- } else {
- for (CallsManagerListener listener : mListeners) {
- listener.onIncomingCallRejected(call, rejectWithMessage, textMessage);
- }
- setActiveSubscription(getConversationSub());
- call.reject(rejectWithMessage, textMessage);
- }
- }
- /**
- * Instructs Telecom to play the specified DTMF tone within the specified call.
- *
- * @param digit The DTMF digit to play.
- */
- void playDtmfTone(Call call, char digit) {
- if (!mCalls.contains(call)) {
- Log.i(this, "Request to play DTMF in a non-existent call %s", call);
- } else {
- call.playDtmfTone(digit);
- mDtmfLocalTonePlayer.playTone(call, digit);
- }
- }
- /**
- * Instructs Telecom to stop the currently playing DTMF tone, if any.
- */
- void stopDtmfTone(Call call) {
- if (!mCalls.contains(call)) {
- Log.i(this, "Request to stop DTMF in a non-existent call %s", call);
- } else {
- call.stopDtmfTone();
- mDtmfLocalTonePlayer.stopTone(call);
- }
- }
- /**
- * Instructs Telecom to continue (or not) the current post-dial DTMF string, if any.
- */
- void postDialContinue(Call call, boolean proceed) {
- if (!mCalls.contains(call)) {
- Log.i(this, "Request to continue post-dial string in a non-existent call %s", call);
- } else {
- call.postDialContinue(proceed);
- }
- }
- /**
- * Instructs Telecom to disconnect the specified call. Intended to be invoked by the
- * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
- * the user hitting the end-call button.
- */
- void disconnectCall(Call call) {
- Log.v(this, "disconnectCall %s", call);
- if (!mCalls.contains(call)) {
- Log.w(this, "Unknown call (%s) asked to disconnect", call);
- } else {
- mLocallyDisconnectingCalls.add(call);
- call.disconnect();
- }
- }
- /**
- * Instructs Telecom to disconnect all calls.
- */
- void disconnectAllCalls() {
- Log.v(this, "disconnectAllCalls");
- for (Call call : mCalls) {
- disconnectCall(call);
- }
- }
- /**
- * Instructs Telecom to put the specified call on hold. Intended to be invoked by the
- * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
- * the user hitting the hold button during an active call.
- */
- void holdCall(Call call) {
- if (!mCalls.contains(call)) {
- Log.w(this, "Unknown call (%s) asked to be put on hold", call);
- } else {
- Log.d(this, "Putting call on hold: (%s)", call);
- call.hold();
- }
- }
- /**
- * Instructs Telecom to release the specified call from hold. Intended to be invoked by
- * the in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered
- * by the user hitting the hold button during a held call.
- */
- void unholdCall(Call call) {
- if (!mCalls.contains(call)) {
- Log.w(this, "Unknown call (%s) asked to be removed from hold", call);
- } else {
- Log.d(this, "unholding call: (%s)", call);
- for (Call c : mCalls) {
- PhoneAccountHandle ph = call.getTargetPhoneAccount();
- PhoneAccountHandle ph1 = c.getTargetPhoneAccount();
- // Only attempt to hold parent calls and not the individual children.
- // if 'c' is not for same subscription as call, then don't disturb 'c'
- if (c != null && c.isAlive() && c != call && c.getParentCall() == null
- && (ph != null && ph1 != null &&
- isSamePhAccIdOrSipId(ph.getId(), ph1.getId()))) {
- c.hold();
- }
- }
- call.unhold();
- }
- }
- /**
- * Returns true if the ids are same or one of the ids is sip id.
- */
- private boolean isSamePhAccIdOrSipId(String id1, String id2) {
- boolean ret = ((id1 != null && id2 != null) &&
- (id1.equals(id2) || id1.contains("sip") || id2.contains("sip")));
- Log.d(this, "isSamePhAccIdOrSipId: id1 = " + id1 + " id2 = " + id2 + " ret = " + ret);
- return ret;
- }
- /** Called by the in-call UI to change the mute state. */
- void mute(boolean shouldMute) {
- mCallAudioManager.mute(shouldMute);
- }
- /**
- * Called by the in-call UI to change the audio route, for example to change from earpiece to
- * speaker phone.
- */
- void setAudioRoute(int route) {
- Log.d(this, "Audio routed by user");
- mCallAudioManager.mUserSetAudioRoute = true;
- mCallAudioManager.setAudioRoute(route);
- mCallAudioManager.mUserSetAudioRoute = false;
- }
- /** Called by the in-call UI to turn the proximity sensor on. */
- void turnOnProximitySensor() {
- mProximitySensorManager.turnOn();
- }
- /**
- * Called by the in-call UI to turn the proximity sensor off.
- * @param screenOnImmediately If true, the screen will be turned on immediately. Otherwise,
- * the screen will be kept off until the proximity sensor goes negative.
- */
- void turnOffProximitySensor(boolean screenOnImmediately) {
- mProximitySensorManager.turnOff(screenOnImmediately);
- }
- void phoneAccountSelected(Call call, PhoneAccountHandle account, boolean setDefault) {
- if (!mCalls.contains(call)) {
- Log.i(this, "Attempted to add account to unknown call %s", call);
- } else {
- // TODO: There is an odd race condition here. Since NewOutgoingCallIntentBroadcaster and
- // the SELECT_PHONE_ACCOUNT sequence run in parallel, if the user selects an account before the
- // NEW_OUTGOING_CALL sequence finishes, we'll start the call immediately without
- // respecting a rewritten number or a canceled number. This is unlikely since
- // NEW_OUTGOING_CALL sequence, in practice, runs a lot faster than the user selecting
- // a phone account from the in-call UI.
- Log.i(this, "phoneAccountSelected , id = %s", account.getId());
- updateLchStatus(account.getId());
- call.setTargetPhoneAccount(account);
- // 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();
- }
- if (setDefault) {
- mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(account);
- }
- }
- }
- /** Called when the audio state changes. */
- void onCallAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) {
- Log.v(this, "onAudioStateChanged, audioState: %s -> %s", oldAudioState, newAudioState);
- for (CallsManagerListener listener : mListeners) {
- listener.onCallAudioStateChanged(oldAudioState, newAudioState);
- }
- }
- void markCallAsRinging(Call call) {
- setCallState(call, CallState.RINGING, "ringing set explicitly");
- }
- void markCallAsDialing(Call call) {
- setCallState(call, CallState.DIALING, "dialing set explicitly");
- maybeMoveToEarpiece(call);
- maybeMoveToSpeakerPhone(call);
- setActiveSubscription(call.getTargetPhoneAccount().getId());
- }
- void markCallAsActive(Call call) {
- setCallState(call, CallState.ACTIVE, "active set explicitly");
- maybeMoveToSpeakerPhone(call);
- }
- void markCallAsOnHold(Call call) {
- setCallState(call, CallState.ON_HOLD, "on-hold set explicitly");
- }
- /**
- * Marks the specified call as STATE_DISCONNECTED and notifies the in-call app. If this was the
- * last live call, then also disconnect from the in-call controller.
- *
- * @param disconnectCause The disconnect cause, see {@link android.telecom.DisconnectCause}.
- */
- void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
- // Show MT call in call log as a missed call if immediately disconnected
- // before creating a connection.
- if (!mCalls.contains(call) && (DisconnectCause.MISSED == disconnectCause.getCode())) {
- addCall(call);
- mMissedCallNotifier.showMissedCallNotification(call);
- }
- call.setDisconnectCause(disconnectCause);
- int prevState = call.getState();
- setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly");
- String activeSub = getActiveSubscription();
- String conversationSub = getConversationSub();
- String lchSub = IsAnySubInLch();
- PhoneAccount phAcc =
- getPhoneAccountRegistrar().getPhoneAccount(call.getTargetPhoneAccount());
- if (activeSub != null && (call.getTargetPhoneAccount() != null &&
- call.getTargetPhoneAccount().getId().equals(activeSub)) &&
- (phAcc != null) && (phAcc.isSet(PhoneAccount.LCH)) &&
- (conversationSub != null) &&
- (!conversationSub.equals(activeSub))) {
- Log.d(this,"Set active sub to conversation sub");
- setActiveSubscription(conversationSub);
- } else if ((conversationSub == null) && (lchSub != null) &&
- ((prevState == CallState.CONNECTING) || (prevState ==
- CallState.SELECT_PHONE_ACCOUNT)) &&
- (call.getState() == CallState.DISCONNECTED)) {
- Log.d(this,"remove sub with call from LCH");
- updateLchStatus(lchSub);
- setActiveSubscription(lchSub);
- manageDsdaInCallTones(false);
- }
- if ((call.getTargetPhoneAccount() != null) && (phAcc != null) &&
- (phAcc.isSet(PhoneAccount.LCH))) {
- Call activecall = getFirstCallWithState(call.getTargetPhoneAccount().getId(),
- CallState.RINGING, CallState.DIALING, CallState.ACTIVE, CallState.ON_HOLD);
- Log.d(this,"activecall: " + activecall);
- if (activecall == null) {
- phAcc.unSetBit(PhoneAccount.LCH);
- manageDsdaInCallTones(false);
- }
- }
- }
- private String IsAnySubInLch() {
- for (PhoneAccountHandle ph : getPhoneAccountRegistrar().getSimPhoneAccounts()) {
- PhoneAccount phAcc = getPhoneAccountRegistrar().getPhoneAccount(ph);
- if ((phAcc != null) && (phAcc.isSet(PhoneAccount.LCH))) {
- Log.d(this, "Sub in LCH: " + ph.getId());
- return ph.getId();
- }
- }
- return null;
- }
- /**
- * Removes an existing disconnected call, and notifies the in-call app.
- */
- void markCallAsRemoved(Call call) {
- removeCall(call);
- if (!hasAnyCalls()) {
- updateLchStatus(null);
- setActiveSubscription(null);
- manageDsdaInCallTones(false);
- }
- if (mLocallyDisconnectingCalls.contains(call)) {
- mLocallyDisconnectingCalls.remove(call);
- if (mForegroundCall != null && mForegroundCall.getState() == CallState.ON_HOLD) {
- mForegroundCall.unhold();
- }
- }
- }
- /**
- * Cleans up any calls currently associated with the specified connection service when the
- * service binder disconnects unexpectedly.
- *
- * @param service The connection service that disconnected.
- */
- void handleConnectionServiceDeath(ConnectionServiceWrapper service) {
- if (service != null) {
- for (Call call : mCalls) {
- if (call.getConnectionService() == service) {
- if (call.getState() != CallState.DISCONNECTED) {
- markCallAsDisconnected(call, new DisconnectCause(D