CallsManager.java revision 5385513ae43b4d5896245bf076a83b27dbf32a25
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.telecom;
18
19import android.content.Context;
20import android.net.Uri;
21import android.os.Bundle;
22import android.os.Handler;
23import android.os.Looper;
24import android.os.SystemProperties;
25import android.os.SystemVibrator;
26import android.os.Trace;
27import android.provider.CallLog.Calls;
28import android.telecom.CallAudioState;
29import android.telecom.Conference;
30import android.telecom.Connection;
31import android.telecom.DisconnectCause;
32import android.telecom.GatewayInfo;
33import android.telecom.ParcelableConference;
34import android.telecom.ParcelableConnection;
35import android.telecom.PhoneAccount;
36import android.telecom.PhoneAccountHandle;
37import android.telecom.TelecomManager;
38import android.telecom.VideoProfile;
39import android.telephony.PhoneNumberUtils;
40import android.telephony.TelephonyManager;
41import android.text.TextUtils;
42
43import com.android.internal.annotations.VisibleForTesting;
44import com.android.internal.telephony.PhoneConstants;
45import com.android.internal.telephony.TelephonyProperties;
46import com.android.internal.util.IndentingPrintWriter;
47
48import java.util.Collection;
49import java.util.Collections;
50import java.util.HashSet;
51import java.util.List;
52import java.util.Objects;
53import java.util.Set;
54import java.util.concurrent.ConcurrentHashMap;
55
56/**
57 * Singleton.
58 *
59 * NOTE: by design most APIs are package private, use the relevant adapter/s to allow
60 * access from other packages specifically refraining from passing the CallsManager instance
61 * beyond the com.android.server.telecom package boundary.
62 */
63@VisibleForTesting
64public class CallsManager extends Call.ListenerBase implements VideoProviderProxy.Listener {
65
66    // TODO: Consider renaming this CallsManagerPlugin.
67    @VisibleForTesting
68    public interface CallsManagerListener {
69        void onCallAdded(Call call);
70        void onCallRemoved(Call call);
71        void onCallStateChanged(Call call, int oldState, int newState);
72        void onConnectionServiceChanged(
73                Call call,
74                ConnectionServiceWrapper oldService,
75                ConnectionServiceWrapper newService);
76        void onIncomingCallAnswered(Call call);
77        void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage);
78        void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall);
79        void onCallAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState);
80        void onRingbackRequested(Call call, boolean ringback);
81        void onIsConferencedChanged(Call call);
82        void onIsVoipAudioModeChanged(Call call);
83        void onVideoStateChanged(Call call);
84        void onCanAddCallChanged(boolean canAddCall);
85        void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
86    }
87
88    private static final String TAG = "CallsManager";
89
90    private static final int MAXIMUM_LIVE_CALLS = 1;
91    private static final int MAXIMUM_HOLD_CALLS = 1;
92    private static final int MAXIMUM_RINGING_CALLS = 1;
93    private static final int MAXIMUM_DIALING_CALLS = 1;
94    private static final int MAXIMUM_OUTGOING_CALLS = 1;
95    private static final int MAXIMUM_TOP_LEVEL_CALLS = 2;
96
97    private static final int[] OUTGOING_CALL_STATES =
98            {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING};
99
100    private static final int[] LIVE_CALL_STATES =
101            {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING, CallState.ACTIVE};
102    public static final String TELECOM_CALL_ID_PREFIX = "TC@";
103
104    /**
105     * The main call repository. Keeps an instance of all live calls. New incoming and outgoing
106     * calls are added to the map and removed when the calls move to the disconnected state.
107     *
108     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
109     * load factor before resizing, 1 means we only expect a single thread to
110     * access the map so make only a single shard
111     */
112    private final Set<Call> mCalls = Collections.newSetFromMap(
113            new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
114
115    /**
116     * The current telecom call ID.  Used when creating new instances of {@link Call}.  Should
117     * only be accessed using the {@link #getNextCallId()} method which synchronizes on the
118     * {@link #mLock} sync root.
119     */
120    private int mCallId = 0;
121
122    private final ConnectionServiceRepository mConnectionServiceRepository;
123    private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
124    private final InCallController mInCallController;
125    private final CallAudioManager mCallAudioManager;
126    private RespondViaSmsManager mRespondViaSmsManager;
127    private final Ringer mRinger;
128    private final InCallWakeLockController mInCallWakeLockController;
129    // For this set initial table size to 16 because we add 13 listeners in
130    // the CallsManager constructor.
131    private final Set<CallsManagerListener> mListeners = Collections.newSetFromMap(
132            new ConcurrentHashMap<CallsManagerListener, Boolean>(16, 0.9f, 1));
133    private final HeadsetMediaButton mHeadsetMediaButton;
134    private final WiredHeadsetManager mWiredHeadsetManager;
135    private final DockManager mDockManager;
136    private final TtyManager mTtyManager;
137    private final ProximitySensorManager mProximitySensorManager;
138    private final PhoneStateBroadcaster mPhoneStateBroadcaster;
139    private final CallLogManager mCallLogManager;
140    private final Context mContext;
141    private final TelecomSystem.SyncRoot mLock;
142    private final ContactsAsyncHelper mContactsAsyncHelper;
143    private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
144    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
145    private final MissedCallNotifier mMissedCallNotifier;
146    private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
147    private final Set<Call> mPendingCallsToDisconnect = new HashSet<>();
148    /* Handler tied to thread in which CallManager was initialized. */
149    private final Handler mHandler = new Handler(Looper.getMainLooper());
150
151    private boolean mCanAddCall = true;
152
153    /**
154     * The call the user is currently interacting with. This is the call that should have audio
155     * focus and be visible in the in-call UI.
156     */
157    private Call mForegroundCall;
158
159    private Runnable mStopTone;
160
161    /**
162     * Initializes the required Telecom components.
163     */
164    CallsManager(
165            Context context,
166            TelecomSystem.SyncRoot lock,
167            ContactsAsyncHelper contactsAsyncHelper,
168            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
169            MissedCallNotifier missedCallNotifier,
170            PhoneAccountRegistrar phoneAccountRegistrar,
171            HeadsetMediaButtonFactory headsetMediaButtonFactory,
172            ProximitySensorManagerFactory proximitySensorManagerFactory,
173            InCallWakeLockControllerFactory inCallWakeLockControllerFactory,
174            CallAudioManager.AudioServiceFactory audioServiceFactory) {
175        mContext = context;
176        mLock = lock;
177        mContactsAsyncHelper = contactsAsyncHelper;
178        mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
179        mPhoneAccountRegistrar = phoneAccountRegistrar;
180        mMissedCallNotifier = missedCallNotifier;
181        StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
182        mWiredHeadsetManager = new WiredHeadsetManager(context);
183        mDockManager = new DockManager(context);
184        mCallAudioManager = new CallAudioManager(
185                context, mLock, statusBarNotifier,
186                mWiredHeadsetManager, mDockManager, this, audioServiceFactory);
187        InCallTonePlayer.Factory playerFactory =
188	    new InCallTonePlayer.Factory(mCallAudioManager, lock);
189        RingtoneFactory ringtoneFactory = new RingtoneFactory(context);
190        SystemVibrator systemVibrator = new SystemVibrator(context);
191        AsyncRingtonePlayer asyncRingtonePlayer = new AsyncRingtonePlayer();
192        SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
193        mRinger = new Ringer(
194                mCallAudioManager, this, playerFactory, context, systemSettingsUtil,
195                asyncRingtonePlayer, ringtoneFactory, systemVibrator);
196	mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
197        mTtyManager = new TtyManager(context, mWiredHeadsetManager);
198        mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
199        mPhoneStateBroadcaster = new PhoneStateBroadcaster(this);
200        mCallLogManager = new CallLogManager(context);
201        mInCallController = new InCallController(context, mLock, this);
202        mDtmfLocalTonePlayer = new DtmfLocalTonePlayer(context);
203        mConnectionServiceRepository =
204                new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
205        mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
206
207        mListeners.add(statusBarNotifier);
208        mListeners.add(mCallLogManager);
209        mListeners.add(mPhoneStateBroadcaster);
210        mListeners.add(mInCallController);
211        mListeners.add(mRinger);
212        mListeners.add(new RingbackPlayer(this, playerFactory));
213        mListeners.add(new InCallToneMonitor(playerFactory, this));
214        mListeners.add(mCallAudioManager);
215        mListeners.add(missedCallNotifier);
216        mListeners.add(mDtmfLocalTonePlayer);
217        mListeners.add(mHeadsetMediaButton);
218        mListeners.add(mProximitySensorManager);
219
220        mMissedCallNotifier.updateOnStartup(
221                mLock, this, mContactsAsyncHelper, mCallerInfoAsyncQueryFactory);
222    }
223
224    public void setRespondViaSmsManager(RespondViaSmsManager respondViaSmsManager) {
225        if (mRespondViaSmsManager != null) {
226            mListeners.remove(mRespondViaSmsManager);
227        }
228        mRespondViaSmsManager = respondViaSmsManager;
229        mListeners.add(respondViaSmsManager);
230    }
231
232    public RespondViaSmsManager getRespondViaSmsManager() {
233        return mRespondViaSmsManager;
234    }
235
236    @Override
237    public void onSuccessfulOutgoingCall(Call call, int callState) {
238        Log.v(this, "onSuccessfulOutgoingCall, %s", call);
239
240        setCallState(call, callState, "successful outgoing call");
241        if (!mCalls.contains(call)) {
242            // Call was not added previously in startOutgoingCall due to it being a potential MMI
243            // code, so add it now.
244            addCall(call);
245        }
246
247        // The call's ConnectionService has been updated.
248        for (CallsManagerListener listener : mListeners) {
249            listener.onConnectionServiceChanged(call, null, call.getConnectionService());
250        }
251
252        markCallAsDialing(call);
253    }
254
255    @Override
256    public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {
257        Log.v(this, "onFailedOutgoingCall, call: %s", call);
258
259        markCallAsRemoved(call);
260    }
261
262    @Override
263    public void onSuccessfulIncomingCall(Call incomingCall, boolean shouldSendToVoicemail) {
264        Log.d(this, "onSuccessfulIncomingCall");
265
266        // Only set the incoming call as ringing if it isn't already disconnected.  It is possible
267        // that the connection service disconnected the call before it was even added to Telecom, in
268        // which case it makes no sense to set it back to a ringing state.
269        if (incomingCall.getState() != CallState.DISCONNECTED &&
270                incomingCall.getState() != CallState.DISCONNECTING) {
271            setCallState(incomingCall, CallState.RINGING,
272                    shouldSendToVoicemail ? "directing to voicemail" : "successful incoming call");
273        } else {
274            Log.i(this, "onSuccessfulIncomingCall: call already disconnected.");
275        }
276
277        if (hasMaximumRingingCalls() || hasMaximumDialingCalls() || shouldSendToVoicemail) {
278            incomingCall.reject(false, null);
279            // Since the call was not added to the list of calls, we have to call the missed
280            // call notifier and the call logger manually.
281            // Do we need missed call notification for direct to Voicemail calls?
282            mMissedCallNotifier.showMissedCallNotification(incomingCall);
283            mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE);
284        } else {
285            addCall(incomingCall);
286        }
287    }
288
289    @Override
290    public void onFailedIncomingCall(Call call) {
291        setCallState(call, CallState.DISCONNECTED, "failed incoming call");
292        call.removeListener(this);
293    }
294
295    @Override
296    public void onSuccessfulUnknownCall(Call call, int callState) {
297        setCallState(call, callState, "successful unknown call");
298        Log.i(this, "onSuccessfulUnknownCall for call %s", call);
299        addCall(call);
300    }
301
302    @Override
303    public void onFailedUnknownCall(Call call) {
304        Log.i(this, "onFailedUnknownCall for call %s", call);
305        setCallState(call, CallState.DISCONNECTED, "failed unknown call");
306        call.removeListener(this);
307    }
308
309    @Override
310    public void onRingbackRequested(Call call, boolean ringback) {
311        for (CallsManagerListener listener : mListeners) {
312            listener.onRingbackRequested(call, ringback);
313        }
314    }
315
316    @Override
317    public void onPostDialWait(Call call, String remaining) {
318        mInCallController.onPostDialWait(call, remaining);
319    }
320
321    @Override
322    public void onPostDialChar(final Call call, char nextChar) {
323        if (PhoneNumberUtils.is12Key(nextChar)) {
324            // Play tone if it is one of the dialpad digits, canceling out the previously queued
325            // up stopTone runnable since playing a new tone automatically stops the previous tone.
326            if (mStopTone != null) {
327                mHandler.removeCallbacks(mStopTone);
328            }
329
330            mDtmfLocalTonePlayer.playTone(call, nextChar);
331
332            // TODO: Create a LockedRunnable class that does the synchronization automatically.
333            mStopTone = new Runnable() {
334                @Override
335                public void run() {
336                    synchronized (mLock) {
337                        // Set a timeout to stop the tone in case there isn't another tone to
338                        // follow.
339                        mDtmfLocalTonePlayer.stopTone(call);
340                    }
341                }
342            };
343            mHandler.postDelayed(
344                    mStopTone,
345                    Timeouts.getDelayBetweenDtmfTonesMillis(mContext.getContentResolver()));
346        } else if (nextChar == 0 || nextChar == TelecomManager.DTMF_CHARACTER_WAIT ||
347                nextChar == TelecomManager.DTMF_CHARACTER_PAUSE) {
348            // Stop the tone if a tone is playing, removing any other stopTone callbacks since
349            // the previous tone is being stopped anyway.
350            if (mStopTone != null) {
351                mHandler.removeCallbacks(mStopTone);
352            }
353            mDtmfLocalTonePlayer.stopTone(call);
354        } else {
355            Log.w(this, "onPostDialChar: invalid value %d", nextChar);
356        }
357    }
358
359    @Override
360    public void onParentChanged(Call call) {
361        // parent-child relationship affects which call should be foreground, so do an update.
362        updateCallsManagerState();
363        for (CallsManagerListener listener : mListeners) {
364            listener.onIsConferencedChanged(call);
365        }
366    }
367
368    @Override
369    public void onChildrenChanged(Call call) {
370        // parent-child relationship affects which call should be foreground, so do an update.
371        updateCallsManagerState();
372        for (CallsManagerListener listener : mListeners) {
373            listener.onIsConferencedChanged(call);
374        }
375    }
376
377    @Override
378    public void onIsVoipAudioModeChanged(Call call) {
379        for (CallsManagerListener listener : mListeners) {
380            listener.onIsVoipAudioModeChanged(call);
381        }
382    }
383
384    @Override
385    public void onVideoStateChanged(Call call) {
386        for (CallsManagerListener listener : mListeners) {
387            listener.onVideoStateChanged(call);
388        }
389    }
390
391    @Override
392    public boolean onCanceledViaNewOutgoingCallBroadcast(final Call call) {
393        mPendingCallsToDisconnect.add(call);
394        mHandler.postDelayed(new Runnable() {
395            @Override
396            public void run() {
397                synchronized (mLock) {
398                    if (mPendingCallsToDisconnect.remove(call)) {
399                        Log.i(this, "Delayed disconnection of call: %s", call);
400                        call.disconnect();
401                    }
402                }
403            }
404        }, Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver()));
405
406        return true;
407    }
408
409    /**
410     * Handles changes to the {@link Connection.VideoProvider} for a call.  Adds the
411     * {@link CallsManager} as a listener for the {@link VideoProviderProxy} which is created
412     * in {@link Call#setVideoProvider(IVideoProvider)}.  This allows the {@link CallsManager} to
413     * respond to callbacks from the {@link VideoProviderProxy}.
414     *
415     * @param call The call.
416     */
417    @Override
418    public void onVideoCallProviderChanged(Call call) {
419        VideoProviderProxy videoProviderProxy = call.getVideoProviderProxy();
420
421        if (videoProviderProxy == null) {
422            return;
423        }
424
425        videoProviderProxy.addListener(this);
426    }
427
428    /**
429     * Handles session modification requests received via the {@link TelecomVideoCallCallback} for
430     * a call.  Notifies listeners of the {@link CallsManager.CallsManagerListener} of the session
431     * modification request.
432     *
433     * @param call The call.
434     * @param videoProfile The {@link VideoProfile}.
435     */
436    @Override
437    public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
438        int videoState = videoProfile != null ? videoProfile.getVideoState() :
439                VideoProfile.STATE_AUDIO_ONLY;
440        Log.v(TAG, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile
441                .videoStateToString(videoState));
442
443        for (CallsManagerListener listener : mListeners) {
444            listener.onSessionModifyRequestReceived(call, videoProfile);
445        }
446    }
447
448    @VisibleForTesting
449    public Collection<Call> getCalls() {
450        return Collections.unmodifiableCollection(mCalls);
451    }
452
453    @VisibleForTesting
454    public Call getForegroundCall() {
455        return mForegroundCall;
456    }
457
458    Ringer getRinger() {
459        return mRinger;
460    }
461
462    InCallController getInCallController() {
463        return mInCallController;
464    }
465
466    boolean hasEmergencyCall() {
467        for (Call call : mCalls) {
468            if (call.isEmergencyCall()) {
469                return true;
470            }
471        }
472        return false;
473    }
474
475    boolean hasOnlyDisconnectedCalls() {
476        for (Call call : mCalls) {
477            if (!call.isDisconnected()) {
478                return false;
479            }
480        }
481        return true;
482    }
483
484    boolean hasVideoCall() {
485        for (Call call : mCalls) {
486            if (VideoProfile.isVideo(call.getVideoState())) {
487                return true;
488            }
489        }
490        return false;
491    }
492
493    CallAudioState getAudioState() {
494        return mCallAudioManager.getCallAudioState();
495    }
496
497    boolean isTtySupported() {
498        return mTtyManager.isTtySupported();
499    }
500
501    int getCurrentTtyMode() {
502        return mTtyManager.getCurrentTtyMode();
503    }
504
505    @VisibleForTesting
506    public void addListener(CallsManagerListener listener) {
507        mListeners.add(listener);
508    }
509
510    void removeListener(CallsManagerListener listener) {
511        mListeners.remove(listener);
512    }
513
514    /**
515     * Starts the process to attach the call to a connection service.
516     *
517     * @param phoneAccountHandle The phone account which contains the component name of the
518     *        connection service to use for this call.
519     * @param extras The optional extras Bundle passed with the intent used for the incoming call.
520     */
521    void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
522        Log.d(this, "processIncomingCallIntent");
523        Uri handle = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS);
524        if (handle == null) {
525            // Required for backwards compatibility
526            handle = extras.getParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER);
527        }
528        Call call = new Call(
529                getNextCallId(),
530                mContext,
531                this,
532                mLock,
533                mConnectionServiceRepository,
534                mContactsAsyncHelper,
535                mCallerInfoAsyncQueryFactory,
536                handle,
537                null /* gatewayInfo */,
538                null /* connectionManagerPhoneAccount */,
539                phoneAccountHandle,
540                true /* isIncoming */,
541                false /* isConference */);
542
543        call.setIntentExtras(extras);
544        // TODO: Move this to be a part of addCall()
545        call.addListener(this);
546        call.startCreateConnection(mPhoneAccountRegistrar);
547    }
548
549    void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
550        Uri handle = extras.getParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE);
551        Log.i(this, "addNewUnknownCall with handle: %s", Log.pii(handle));
552        Call call = new Call(
553                getNextCallId(),
554                mContext,
555                this,
556                mLock,
557                mConnectionServiceRepository,
558                mContactsAsyncHelper,
559                mCallerInfoAsyncQueryFactory,
560                handle,
561                null /* gatewayInfo */,
562                null /* connectionManagerPhoneAccount */,
563                phoneAccountHandle,
564                // Use onCreateIncomingConnection in TelephonyConnectionService, so that we attach
565                // to the existing connection instead of trying to create a new one.
566                true /* isIncoming */,
567                false /* isConference */);
568        call.setIsUnknown(true);
569        call.setIntentExtras(extras);
570        call.addListener(this);
571        call.startCreateConnection(mPhoneAccountRegistrar);
572    }
573
574    private boolean areHandlesEqual(Uri handle1, Uri handle2) {
575        if (handle1 == null || handle2 == null) {
576            return handle1 == handle2;
577        }
578
579        if (!TextUtils.equals(handle1.getScheme(), handle2.getScheme())) {
580            return false;
581        }
582
583        final String number1 = PhoneNumberUtils.normalizeNumber(handle1.getSchemeSpecificPart());
584        final String number2 = PhoneNumberUtils.normalizeNumber(handle2.getSchemeSpecificPart());
585        return TextUtils.equals(number1, number2);
586    }
587
588    private Call reuseOutgoingCall(Uri handle) {
589        // Check to see if we can reuse any of the calls that are waiting to disconnect.
590        // See {@link Call#abort} and {@link #onCanceledViaNewOutgoingCall} for more information.
591        Call reusedCall = null;
592        for (Call pendingCall : mPendingCallsToDisconnect) {
593            if (reusedCall == null && areHandlesEqual(pendingCall.getHandle(), handle)) {
594                mPendingCallsToDisconnect.remove(pendingCall);
595                Log.i(this, "Reusing disconnected call %s", pendingCall);
596                reusedCall = pendingCall;
597            } else {
598                Log.i(this, "Not reusing disconnected call %s", pendingCall);
599                pendingCall.disconnect();
600            }
601        }
602
603        return reusedCall;
604    }
605
606    /**
607     * Kicks off the first steps to creating an outgoing call so that InCallUI can launch.
608     *
609     * @param handle Handle to connect the call with.
610     * @param phoneAccountHandle The phone account which contains the component name of the
611     *        connection service to use for this call.
612     * @param extras The optional extras Bundle passed with the intent used for the incoming call.
613     */
614    Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) {
615        boolean isReusedCall = true;
616        Call call = reuseOutgoingCall(handle);
617
618        // Create a call with original handle. The handle may be changed when the call is attached
619        // to a connection service, but in most cases will remain the same.
620        if (call == null) {
621            call = new Call(getNextCallId(), mContext,
622                    this,
623                    mLock,
624                    mConnectionServiceRepository,
625                    mContactsAsyncHelper,
626                    mCallerInfoAsyncQueryFactory,
627                    handle,
628                    null /* gatewayInfo */,
629                    null /* connectionManagerPhoneAccount */,
630                    null /* phoneAccountHandle */,
631                    false /* isIncoming */,
632                    false /* isConference */);
633
634            isReusedCall = false;
635        }
636
637        List<PhoneAccountHandle> accounts =
638                mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme(), false);
639
640        Log.v(this, "startOutgoingCall found accounts = " + accounts);
641
642        if (mForegroundCall != null) {
643            Call ongoingCall = mForegroundCall;
644            // If there is an ongoing call, use the same phone account to place this new call.
645            // If the ongoing call is a conference call, we fetch the phone account from the
646            // child calls because we don't have targetPhoneAccount set on Conference calls.
647            // TODO: Set targetPhoneAccount for all conference calls (b/23035408).
648            if (ongoingCall.getTargetPhoneAccount() == null &&
649                    !ongoingCall.getChildCalls().isEmpty()) {
650                ongoingCall = ongoingCall.getChildCalls().get(0);
651            }
652            if (ongoingCall.getTargetPhoneAccount() != null) {
653                phoneAccountHandle = ongoingCall.getTargetPhoneAccount();
654            }
655        }
656
657        // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call
658        // as if a phoneAccount was not specified (does the default behavior instead).
659        // Note: We will not attempt to dial with a requested phoneAccount if it is disabled.
660        if (phoneAccountHandle != null) {
661            if (!accounts.contains(phoneAccountHandle)) {
662                phoneAccountHandle = null;
663            }
664        }
665
666        if (phoneAccountHandle == null) {
667            // No preset account, check if default exists that supports the URI scheme for the
668            // handle.
669            phoneAccountHandle =
670                    mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(handle.getScheme());
671        }
672
673        call.setTargetPhoneAccount(phoneAccountHandle);
674
675        boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle);
676
677        // Do not support any more live calls.  Our options are to move a call to hold, disconnect
678        // a call, or cancel this call altogether. If a call is being reused, then it has already
679        // passed the makeRoomForOutgoingCall check once and will fail the second time due to the
680        // call transitioning into the CONNECTING state.
681        if (!isPotentialInCallMMICode && (!isReusedCall &&
682                !makeRoomForOutgoingCall(call, call.isEmergencyCall()))) {
683            // just cancel at this point.
684            Log.i(this, "No remaining room for outgoing call: %s", call);
685            if (mCalls.contains(call)) {
686                // This call can already exist if it is a reused call,
687                // See {@link #reuseOutgoingCall}.
688                call.disconnect();
689            }
690            return null;
691        }
692
693        boolean needsAccountSelection = phoneAccountHandle == null && accounts.size() > 1 &&
694                !call.isEmergencyCall();
695
696        if (needsAccountSelection) {
697            // This is the state where the user is expected to select an account
698            call.setState(CallState.SELECT_PHONE_ACCOUNT, "needs account selection");
699            // Create our own instance to modify (since extras may be Bundle.EMPTY)
700            extras = new Bundle(extras);
701            extras.putParcelableList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS, accounts);
702        } else {
703            call.setState(
704                    CallState.CONNECTING,
705                    phoneAccountHandle == null ? "no-handle" : phoneAccountHandle.toString());
706        }
707
708        call.setIntentExtras(extras);
709
710        // Do not add the call if it is a potential MMI code.
711        if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) {
712            call.addListener(this);
713        } else if (!mCalls.contains(call)) {
714            // We check if mCalls already contains the call because we could potentially be reusing
715            // a call which was previously added (See {@link #reuseOutgoingCall}).
716            addCall(call);
717        }
718
719        return call;
720    }
721
722    /**
723     * Attempts to issue/connect the specified call.
724     *
725     * @param handle Handle to connect the call with.
726     * @param gatewayInfo Optional gateway information that can be used to route the call to the
727     *        actual dialed handle via a gateway provider. May be null.
728     * @param speakerphoneOn Whether or not to turn the speakerphone on once the call connects.
729     * @param videoState The desired video state for the outgoing call.
730     */
731    void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn,
732            int videoState) {
733        if (call == null) {
734            // don't do anything if the call no longer exists
735            Log.i(this, "Canceling unknown call.");
736            return;
737        }
738
739        final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayAddress();
740
741        if (gatewayInfo == null) {
742            Log.i(this, "Creating a new outgoing call with handle: %s", Log.piiHandle(uriHandle));
743        } else {
744            Log.i(this, "Creating a new outgoing call with gateway handle: %s, original handle: %s",
745                    Log.pii(uriHandle), Log.pii(handle));
746        }
747
748        call.setHandle(uriHandle);
749        call.setGatewayInfo(gatewayInfo);
750        call.setVideoState(videoState);
751
752        if (speakerphoneOn) {
753            Log.i(this, "%s Starting with speakerphone as requested", call);
754        } else {
755            Log.i(this, "%s Starting with speakerphone because car is docked.", call);
756        }
757
758        final boolean useSpeakerWhenDocked = mContext.getResources().getBoolean(
759                R.bool.use_speaker_when_docked);
760
761        call.setStartWithSpeakerphoneOn(speakerphoneOn
762                || (useSpeakerWhenDocked && mDockManager.isDocked()));
763
764        if (call.isEmergencyCall()) {
765            // Emergency -- CreateConnectionProcessor will choose accounts automatically
766            call.setTargetPhoneAccount(null);
767        }
768
769        final boolean requireCallCapableAccountByHandle = mContext.getResources().getBoolean(
770                com.android.internal.R.bool.config_requireCallCapableAccountForHandle);
771
772        if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) {
773            // If the account has been set, proceed to place the outgoing call.
774            // Otherwise the connection will be initiated when the account is set by the user.
775            call.startCreateConnection(mPhoneAccountRegistrar);
776        } else if (mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
777                requireCallCapableAccountByHandle ? call.getHandle().getScheme() : null, false)
778                .isEmpty()) {
779            // If there are no call capable accounts, disconnect the call.
780            markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.CANCELED,
781                    "No registered PhoneAccounts"));
782            markCallAsRemoved(call);
783        }
784    }
785
786    /**
787     * Attempts to start a conference call for the specified call.
788     *
789     * @param call The call to conference.
790     * @param otherCall The other call to conference with.
791     */
792    @VisibleForTesting
793    public void conference(Call call, Call otherCall) {
794        call.conferenceWith(otherCall);
795    }
796
797    /**
798     * Instructs Telecom to answer the specified call. Intended to be invoked by the in-call
799     * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by
800     * the user opting to answer said call.
801     *
802     * @param call The call to answer.
803     * @param videoState The video state in which to answer the call.
804     */
805    @VisibleForTesting
806    public void answerCall(Call call, int videoState) {
807        if (!mCalls.contains(call)) {
808            Log.i(this, "Request to answer a non-existent call %s", call);
809        } else {
810            // If the foreground call is not the ringing call and it is currently isActive() or
811            // STATE_DIALING, put it on hold before answering the call.
812            if (mForegroundCall != null && mForegroundCall != call &&
813                    (mForegroundCall.isActive() ||
814                     mForegroundCall.getState() == CallState.DIALING)) {
815                if (0 == (mForegroundCall.getConnectionCapabilities()
816                        & Connection.CAPABILITY_HOLD)) {
817                    // This call does not support hold.  If it is from a different connection
818                    // service, then disconnect it, otherwise allow the connection service to
819                    // figure out the right states.
820                    if (mForegroundCall.getConnectionService() != call.getConnectionService()) {
821                        mForegroundCall.disconnect();
822                    }
823                } else {
824                    Call heldCall = getHeldCall();
825                    if (heldCall != null) {
826                        Log.v(this, "Disconnecting held call %s before holding active call.",
827                                heldCall);
828                        heldCall.disconnect();
829                    }
830
831                    Log.v(this, "Holding active/dialing call %s before answering incoming call %s.",
832                            mForegroundCall, call);
833                    mForegroundCall.hold();
834                }
835                // TODO: Wait until we get confirmation of the active call being
836                // on-hold before answering the new call.
837                // TODO: Import logic from CallManager.acceptCall()
838            }
839
840            for (CallsManagerListener listener : mListeners) {
841                listener.onIncomingCallAnswered(call);
842            }
843
844            // We do not update the UI until we get confirmation of the answer() through
845            // {@link #markCallAsActive}.
846            call.answer(videoState);
847            if (VideoProfile.isVideo(videoState) &&
848                !mWiredHeadsetManager.isPluggedIn() &&
849                !mCallAudioManager.isBluetoothDeviceAvailable() &&
850                isSpeakerEnabledForVideoCalls()) {
851                call.setStartWithSpeakerphoneOn(true);
852            }
853        }
854    }
855
856    private static boolean isSpeakerEnabledForVideoCalls() {
857        return (SystemProperties.getInt(TelephonyProperties.PROPERTY_VIDEOCALL_AUDIO_OUTPUT,
858                PhoneConstants.AUDIO_OUTPUT_DEFAULT) ==
859                PhoneConstants.AUDIO_OUTPUT_ENABLE_SPEAKER);
860    }
861
862    /**
863     * Instructs Telecom to reject the specified call. Intended to be invoked by the in-call
864     * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by
865     * the user opting to reject said call.
866     */
867    @VisibleForTesting
868    public void rejectCall(Call call, boolean rejectWithMessage, String textMessage) {
869        if (!mCalls.contains(call)) {
870            Log.i(this, "Request to reject a non-existent call %s", call);
871        } else {
872            for (CallsManagerListener listener : mListeners) {
873                listener.onIncomingCallRejected(call, rejectWithMessage, textMessage);
874            }
875            call.reject(rejectWithMessage, textMessage);
876        }
877    }
878
879    /**
880     * Instructs Telecom to play the specified DTMF tone within the specified call.
881     *
882     * @param digit The DTMF digit to play.
883     */
884    @VisibleForTesting
885    public void playDtmfTone(Call call, char digit) {
886        if (!mCalls.contains(call)) {
887            Log.i(this, "Request to play DTMF in a non-existent call %s", call);
888        } else {
889            call.playDtmfTone(digit);
890            mDtmfLocalTonePlayer.playTone(call, digit);
891        }
892    }
893
894    /**
895     * Instructs Telecom to stop the currently playing DTMF tone, if any.
896     */
897    @VisibleForTesting
898    public void stopDtmfTone(Call call) {
899        if (!mCalls.contains(call)) {
900            Log.i(this, "Request to stop DTMF in a non-existent call %s", call);
901        } else {
902            call.stopDtmfTone();
903            mDtmfLocalTonePlayer.stopTone(call);
904        }
905    }
906
907    /**
908     * Instructs Telecom to continue (or not) the current post-dial DTMF string, if any.
909     */
910    void postDialContinue(Call call, boolean proceed) {
911        if (!mCalls.contains(call)) {
912            Log.i(this, "Request to continue post-dial string in a non-existent call %s", call);
913        } else {
914            call.postDialContinue(proceed);
915        }
916    }
917
918    /**
919     * Instructs Telecom to disconnect the specified call. Intended to be invoked by the
920     * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
921     * the user hitting the end-call button.
922     */
923    @VisibleForTesting
924    public void disconnectCall(Call call) {
925        Log.v(this, "disconnectCall %s", call);
926
927        if (!mCalls.contains(call)) {
928            Log.w(this, "Unknown call (%s) asked to disconnect", call);
929        } else {
930            mLocallyDisconnectingCalls.add(call);
931            call.disconnect();
932        }
933    }
934
935    /**
936     * Instructs Telecom to disconnect all calls.
937     */
938    void disconnectAllCalls() {
939        Log.v(this, "disconnectAllCalls");
940
941        for (Call call : mCalls) {
942            disconnectCall(call);
943        }
944    }
945
946
947    /**
948     * Instructs Telecom to put the specified call on hold. Intended to be invoked by the
949     * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
950     * the user hitting the hold button during an active call.
951     */
952    @VisibleForTesting
953    public void holdCall(Call call) {
954        if (!mCalls.contains(call)) {
955            Log.w(this, "Unknown call (%s) asked to be put on hold", call);
956        } else {
957            Log.d(this, "Putting call on hold: (%s)", call);
958            call.hold();
959        }
960    }
961
962    /**
963     * Instructs Telecom to release the specified call from hold. Intended to be invoked by
964     * the in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered
965     * by the user hitting the hold button during a held call.
966     */
967    @VisibleForTesting
968    public void unholdCall(Call call) {
969        if (!mCalls.contains(call)) {
970            Log.w(this, "Unknown call (%s) asked to be removed from hold", call);
971        } else {
972            Log.d(this, "unholding call: (%s)", call);
973            for (Call c : mCalls) {
974                // Only attempt to hold parent calls and not the individual children.
975                if (c != null && c.isAlive() && c != call && c.getParentCall() == null) {
976                    c.hold();
977                }
978            }
979            call.unhold();
980        }
981    }
982
983    /** Called by the in-call UI to change the mute state. */
984    void mute(boolean shouldMute) {
985        mCallAudioManager.mute(shouldMute);
986    }
987
988    /**
989      * Called by the in-call UI to change the audio route, for example to change from earpiece to
990      * speaker phone.
991      */
992    void setAudioRoute(int route) {
993        mCallAudioManager.setAudioRoute(route);
994    }
995
996    /** Called by the in-call UI to turn the proximity sensor on. */
997    void turnOnProximitySensor() {
998        mProximitySensorManager.turnOn();
999    }
1000
1001    /**
1002     * Called by the in-call UI to turn the proximity sensor off.
1003     * @param screenOnImmediately If true, the screen will be turned on immediately. Otherwise,
1004     *        the screen will be kept off until the proximity sensor goes negative.
1005     */
1006    void turnOffProximitySensor(boolean screenOnImmediately) {
1007        mProximitySensorManager.turnOff(screenOnImmediately);
1008    }
1009
1010    void phoneAccountSelected(Call call, PhoneAccountHandle account, boolean setDefault) {
1011        if (!mCalls.contains(call)) {
1012            Log.i(this, "Attempted to add account to unknown call %s", call);
1013        } else {
1014            // TODO: There is an odd race condition here. Since NewOutgoingCallIntentBroadcaster and
1015            // the SELECT_PHONE_ACCOUNT sequence run in parallel, if the user selects an account before the
1016            // NEW_OUTGOING_CALL sequence finishes, we'll start the call immediately without
1017            // respecting a rewritten number or a canceled number. This is unlikely since
1018            // NEW_OUTGOING_CALL sequence, in practice, runs a lot faster than the user selecting
1019            // a phone account from the in-call UI.
1020            call.setTargetPhoneAccount(account);
1021
1022            // Note: emergency calls never go through account selection dialog so they never
1023            // arrive here.
1024            if (makeRoomForOutgoingCall(call, false /* isEmergencyCall */)) {
1025                call.startCreateConnection(mPhoneAccountRegistrar);
1026            } else {
1027                call.disconnect();
1028            }
1029
1030            if (setDefault) {
1031                mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(account);
1032            }
1033        }
1034    }
1035
1036    /** Called when the audio state changes. */
1037    void onCallAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) {
1038        Log.v(this, "onAudioStateChanged, audioState: %s -> %s", oldAudioState, newAudioState);
1039        for (CallsManagerListener listener : mListeners) {
1040            listener.onCallAudioStateChanged(oldAudioState, newAudioState);
1041        }
1042    }
1043
1044    void markCallAsRinging(Call call) {
1045        setCallState(call, CallState.RINGING, "ringing set explicitly");
1046    }
1047
1048    void markCallAsDialing(Call call) {
1049        setCallState(call, CallState.DIALING, "dialing set explicitly");
1050        maybeMoveToSpeakerPhone(call);
1051    }
1052
1053    void markCallAsActive(Call call) {
1054        setCallState(call, CallState.ACTIVE, "active set explicitly");
1055        maybeMoveToSpeakerPhone(call);
1056    }
1057
1058    void markCallAsOnHold(Call call) {
1059        setCallState(call, CallState.ON_HOLD, "on-hold set explicitly");
1060    }
1061
1062    /**
1063     * Marks the specified call as STATE_DISCONNECTED and notifies the in-call app. If this was the
1064     * last live call, then also disconnect from the in-call controller.
1065     *
1066     * @param disconnectCause The disconnect cause, see {@link android.telecom.DisconnectCause}.
1067     */
1068    void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
1069        call.setDisconnectCause(disconnectCause);
1070        setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly");
1071    }
1072
1073    /**
1074     * Removes an existing disconnected call, and notifies the in-call app.
1075     */
1076    void markCallAsRemoved(Call call) {
1077        removeCall(call);
1078        if (mLocallyDisconnectingCalls.contains(call)) {
1079            mLocallyDisconnectingCalls.remove(call);
1080            if (mForegroundCall != null && mForegroundCall.getState() == CallState.ON_HOLD) {
1081                mForegroundCall.unhold();
1082            }
1083        }
1084    }
1085
1086    /**
1087     * Cleans up any calls currently associated with the specified connection service when the
1088     * service binder disconnects unexpectedly.
1089     *
1090     * @param service The connection service that disconnected.
1091     */
1092    void handleConnectionServiceDeath(ConnectionServiceWrapper service) {
1093        if (service != null) {
1094            for (Call call : mCalls) {
1095                if (call.getConnectionService() == service) {
1096                    if (call.getState() != CallState.DISCONNECTED) {
1097                        markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR));
1098                    }
1099                    markCallAsRemoved(call);
1100                }
1101            }
1102        }
1103    }
1104
1105    boolean hasAnyCalls() {
1106        return !mCalls.isEmpty();
1107    }
1108
1109    boolean hasActiveOrHoldingCall() {
1110        return getFirstCallWithState(CallState.ACTIVE, CallState.ON_HOLD) != null;
1111    }
1112
1113    boolean hasRingingCall() {
1114        return getFirstCallWithState(CallState.RINGING) != null;
1115    }
1116
1117    boolean onMediaButton(int type) {
1118        if (hasAnyCalls()) {
1119            if (HeadsetMediaButton.SHORT_PRESS == type) {
1120                Call ringingCall = getFirstCallWithState(CallState.RINGING);
1121                if (ringingCall == null) {
1122                    mCallAudioManager.toggleMute();
1123                    return true;
1124                } else {
1125                    ringingCall.answer(ringingCall.getVideoState());
1126                    return true;
1127                }
1128            } else if (HeadsetMediaButton.LONG_PRESS == type) {
1129                Log.d(this, "handleHeadsetHook: longpress -> hangup");
1130                Call callToHangup = getFirstCallWithState(
1131                        CallState.RINGING, CallState.DIALING, CallState.ACTIVE, CallState.ON_HOLD);
1132                if (callToHangup != null) {
1133                    callToHangup.disconnect();
1134                    return true;
1135                }
1136            }
1137        }
1138        return false;
1139    }
1140
1141    /**
1142     * Returns true if telecom supports adding another top-level call.
1143     */
1144    boolean canAddCall() {
1145        if (getFirstCallWithState(OUTGOING_CALL_STATES) != null) {
1146            return false;
1147        }
1148
1149        int count = 0;
1150        for (Call call : mCalls) {
1151            if (call.isEmergencyCall()) {
1152                // We never support add call if one of the calls is an emergency call.
1153                return false;
1154            } else  if (!call.getChildCalls().isEmpty() && !call.can(Connection.CAPABILITY_HOLD)) {
1155                // This is to deal with CDMA conference calls. CDMA conference calls do not
1156                // allow the addition of another call when it is already in a 3 way conference.
1157                // So, we detect that it is a CDMA conference call by checking if the call has
1158                // some children and it does not support the CAPABILILTY_HOLD
1159                // TODO: This maybe cleaner if the lower layers can explicitly signal to telecom
1160                // about this limitation (b/22880180).
1161                return false;
1162            } else if (call.getParentCall() == null) {
1163                count++;
1164            }
1165
1166            // We do not check states for canAddCall. We treat disconnected calls the same
1167            // and wait until they are removed instead. If we didn't count disconnected calls,
1168            // we could put InCallServices into a state where they are showing two calls but
1169            // also support add-call. Technically it's right, but overall looks better (UI-wise)
1170            // and acts better if we wait until the call is removed.
1171            if (count >= MAXIMUM_TOP_LEVEL_CALLS) {
1172                return false;
1173            }
1174        }
1175        return true;
1176    }
1177
1178    @VisibleForTesting
1179    public Call getRingingCall() {
1180        return getFirstCallWithState(CallState.RINGING);
1181    }
1182
1183    @VisibleForTesting
1184    public Call getActiveCall() {
1185        return getFirstCallWithState(CallState.ACTIVE);
1186    }
1187
1188    Call getDialingCall() {
1189        return getFirstCallWithState(CallState.DIALING);
1190    }
1191
1192    @VisibleForTesting
1193    public Call getHeldCall() {
1194        return getFirstCallWithState(CallState.ON_HOLD);
1195    }
1196
1197    @VisibleForTesting
1198    public int getNumHeldCalls() {
1199        int count = 0;
1200        for (Call call : mCalls) {
1201            if (call.getParentCall() == null && call.getState() == CallState.ON_HOLD) {
1202                count++;
1203            }
1204        }
1205        return count;
1206    }
1207
1208    @VisibleForTesting
1209    public Call getOutgoingCall() {
1210        return getFirstCallWithState(OUTGOING_CALL_STATES);
1211    }
1212
1213    Call getFirstCallWithState(int... states) {
1214        return getFirstCallWithState(null, states);
1215    }
1216
1217    /**
1218     * Returns the first call that it finds with the given states. The states are treated as having
1219     * priority order so that any call with the first state will be returned before any call with
1220     * states listed later in the parameter list.
1221     *
1222     * @param callToSkip Call that this method should skip while searching
1223     */
1224    Call getFirstCallWithState(Call callToSkip, int... states) {
1225        for (int currentState : states) {
1226            // check the foreground first
1227            if (mForegroundCall != null && mForegroundCall.getState() == currentState) {
1228                return mForegroundCall;
1229            }
1230
1231            for (Call call : mCalls) {
1232                if (Objects.equals(callToSkip, call)) {
1233                    continue;
1234                }
1235
1236                // Only operate on top-level calls
1237                if (call.getParentCall() != null) {
1238                    continue;
1239                }
1240
1241                if (currentState == call.getState()) {
1242                    return call;
1243                }
1244            }
1245        }
1246        return null;
1247    }
1248
1249    Call createConferenceCall(
1250            String callId,
1251            PhoneAccountHandle phoneAccount,
1252            ParcelableConference parcelableConference) {
1253
1254        // If the parceled conference specifies a connect time, use it; otherwise default to 0,
1255        // which is the default value for new Calls.
1256        long connectTime =
1257                parcelableConference.getConnectTimeMillis() ==
1258                        Conference.CONNECT_TIME_NOT_SPECIFIED ? 0 :
1259                        parcelableConference.getConnectTimeMillis();
1260
1261        Call call = new Call(
1262                callId,
1263                mContext,
1264                this,
1265                mLock,
1266                mConnectionServiceRepository,
1267                mContactsAsyncHelper,
1268                mCallerInfoAsyncQueryFactory,
1269                null /* handle */,
1270                null /* gatewayInfo */,
1271                null /* connectionManagerPhoneAccount */,
1272                phoneAccount,
1273                false /* isIncoming */,
1274                true /* isConference */,
1275                connectTime);
1276
1277        setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()),
1278                "new conference call");
1279        call.setConnectionCapabilities(parcelableConference.getConnectionCapabilities());
1280        call.setVideoState(parcelableConference.getVideoState());
1281        call.setVideoProvider(parcelableConference.getVideoProvider());
1282        call.setStatusHints(parcelableConference.getStatusHints());
1283        call.setExtras(parcelableConference.getExtras());
1284
1285        // TODO: Move this to be a part of addCall()
1286        call.addListener(this);
1287        addCall(call);
1288        return call;
1289    }
1290
1291    /**
1292     * @return the call state currently tracked by {@link PhoneStateBroadcaster}
1293     */
1294    int getCallState() {
1295        return mPhoneStateBroadcaster.getCallState();
1296    }
1297
1298    /**
1299     * Retrieves the {@link PhoneAccountRegistrar}.
1300     *
1301     * @return The {@link PhoneAccountRegistrar}.
1302     */
1303    PhoneAccountRegistrar getPhoneAccountRegistrar() {
1304        return mPhoneAccountRegistrar;
1305    }
1306
1307    /**
1308     * Retrieves the {@link MissedCallNotifier}
1309     * @return The {@link MissedCallNotifier}.
1310     */
1311    MissedCallNotifier getMissedCallNotifier() {
1312        return mMissedCallNotifier;
1313    }
1314
1315    /**
1316     * Adds the specified call to the main list of live calls.
1317     *
1318     * @param call The call to add.
1319     */
1320    private void addCall(Call call) {
1321        Trace.beginSection("addCall");
1322        Log.v(this, "addCall(%s)", call);
1323        call.addListener(this);
1324        mCalls.add(call);
1325
1326        updateCallsManagerState();
1327        // onCallAdded for calls which immediately take the foreground (like the first call).
1328        for (CallsManagerListener listener : mListeners) {
1329            if (Log.SYSTRACE_DEBUG) {
1330                Trace.beginSection(listener.getClass().toString() + " addCall");
1331            }
1332            listener.onCallAdded(call);
1333            if (Log.SYSTRACE_DEBUG) {
1334                Trace.endSection();
1335            }
1336        }
1337        Trace.endSection();
1338    }
1339
1340    private void removeCall(Call call) {
1341        Trace.beginSection("removeCall");
1342        Log.v(this, "removeCall(%s)", call);
1343
1344        call.setParentCall(null);  // need to clean up parent relationship before destroying.
1345        call.removeListener(this);
1346        call.clearConnectionService();
1347
1348        boolean shouldNotify = false;
1349        if (mCalls.contains(call)) {
1350            mCalls.remove(call);
1351            shouldNotify = true;
1352        }
1353
1354        call.destroy();
1355
1356        // Only broadcast changes for calls that are being tracked.
1357        if (shouldNotify) {
1358            updateCallsManagerState();
1359            for (CallsManagerListener listener : mListeners) {
1360                if (Log.SYSTRACE_DEBUG) {
1361                    Trace.beginSection(listener.getClass().toString() + " onCallRemoved");
1362                }
1363                listener.onCallRemoved(call);
1364                if (Log.SYSTRACE_DEBUG) {
1365                    Trace.endSection();
1366                }
1367            }
1368        }
1369        Trace.endSection();
1370    }
1371
1372    /**
1373     * Sets the specified state on the specified call.
1374     *
1375     * @param call The call.
1376     * @param newState The new state of the call.
1377     */
1378    private void setCallState(Call call, int newState, String tag) {
1379        if (call == null) {
1380            return;
1381        }
1382        int oldState = call.getState();
1383        Log.i(this, "setCallState %s -> %s, call: %s", CallState.toString(oldState),
1384                CallState.toString(newState), call);
1385        if (newState != oldState) {
1386            // Unfortunately, in the telephony world the radio is king. So if the call notifies
1387            // us that the call is in a particular state, we allow it even if it doesn't make
1388            // sense (e.g., STATE_ACTIVE -> STATE_RINGING).
1389            // TODO: Consider putting a stop to the above and turning CallState
1390            // into a well-defined state machine.
1391            // TODO: Define expected state transitions here, and log when an
1392            // unexpected transition occurs.
1393            call.setState(newState, tag);
1394
1395            Trace.beginSection("onCallStateChanged");
1396            // Only broadcast state change for calls that are being tracked.
1397            if (mCalls.contains(call)) {
1398                updateCallsManagerState();
1399                for (CallsManagerListener listener : mListeners) {
1400                    if (Log.SYSTRACE_DEBUG) {
1401                        Trace.beginSection(listener.getClass().toString() + " onCallStateChanged");
1402                    }
1403                    listener.onCallStateChanged(call, oldState, newState);
1404                    if (Log.SYSTRACE_DEBUG) {
1405                        Trace.endSection();
1406                    }
1407                }
1408            }
1409            Trace.endSection();
1410        }
1411    }
1412
1413    /**
1414     * Checks which call should be visible to the user and have audio focus.
1415     */
1416    private void updateForegroundCall() {
1417        Trace.beginSection("updateForegroundCall");
1418        Call newForegroundCall = null;
1419        for (Call call : mCalls) {
1420            // TODO: Foreground-ness needs to be explicitly set. No call, regardless
1421            // of its state will be foreground by default and instead the connection service should
1422            // be notified when its calls enter and exit foreground state. Foreground will mean that
1423            // the call should play audio and listen to microphone if it wants.
1424
1425            // Only top-level calls can be in foreground
1426            if (call.getParentCall() != null) {
1427                continue;
1428            }
1429
1430            // Active calls have priority.
1431            if (call.isActive()) {
1432                newForegroundCall = call;
1433                break;
1434            }
1435
1436            if (call.isAlive() || call.getState() == CallState.RINGING) {
1437                newForegroundCall = call;
1438                // Don't break in case there's an active call that has priority.
1439            }
1440        }
1441
1442        if (newForegroundCall != mForegroundCall) {
1443            Log.v(this, "Updating foreground call, %s -> %s.", mForegroundCall, newForegroundCall);
1444            Call oldForegroundCall = mForegroundCall;
1445            mForegroundCall = newForegroundCall;
1446
1447            for (CallsManagerListener listener : mListeners) {
1448                if (Log.SYSTRACE_DEBUG) {
1449                    Trace.beginSection(listener.getClass().toString() + " updateForegroundCall");
1450                }
1451                listener.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
1452                if (Log.SYSTRACE_DEBUG) {
1453                    Trace.endSection();
1454                }
1455            }
1456        }
1457        Trace.endSection();
1458    }
1459
1460    private void updateCanAddCall() {
1461        boolean newCanAddCall = canAddCall();
1462        if (newCanAddCall != mCanAddCall) {
1463            mCanAddCall = newCanAddCall;
1464            for (CallsManagerListener listener : mListeners) {
1465                if (Log.SYSTRACE_DEBUG) {
1466                    Trace.beginSection(listener.getClass().toString() + " updateCanAddCall");
1467                }
1468                listener.onCanAddCallChanged(mCanAddCall);
1469                if (Log.SYSTRACE_DEBUG) {
1470                    Trace.endSection();
1471                }
1472            }
1473        }
1474    }
1475
1476    private void updateCallsManagerState() {
1477        updateForegroundCall();
1478        updateCanAddCall();
1479    }
1480
1481    private boolean isPotentialMMICode(Uri handle) {
1482        return (handle != null && handle.getSchemeSpecificPart() != null
1483                && handle.getSchemeSpecificPart().contains("#"));
1484    }
1485
1486    /**
1487     * Determines if a dialed number is potentially an In-Call MMI code.  In-Call MMI codes are
1488     * MMI codes which can be dialed when one or more calls are in progress.
1489     * <P>
1490     * Checks for numbers formatted similar to the MMI codes defined in:
1491     * {@link com.android.internal.telephony.gsm.GSMPhone#handleInCallMmiCommands(String)}
1492     * and
1493     * {@link com.android.internal.telephony.imsphone.ImsPhone#handleInCallMmiCommands(String)}
1494     *
1495     * @param handle The URI to call.
1496     * @return {@code True} if the URI represents a number which could be an in-call MMI code.
1497     */
1498    private boolean isPotentialInCallMMICode(Uri handle) {
1499        if (handle != null && handle.getSchemeSpecificPart() != null &&
1500                handle.getScheme().equals(PhoneAccount.SCHEME_TEL)) {
1501
1502            String dialedNumber = handle.getSchemeSpecificPart();
1503            return (dialedNumber.equals("0") ||
1504                    (dialedNumber.startsWith("1") && dialedNumber.length() <= 2) ||
1505                    (dialedNumber.startsWith("2") && dialedNumber.length() <= 2) ||
1506                    dialedNumber.equals("3") ||
1507                    dialedNumber.equals("4") ||
1508                    dialedNumber.equals("5"));
1509        }
1510        return false;
1511    }
1512
1513    private int getNumCallsWithState(int... states) {
1514        int count = 0;
1515        for (int state : states) {
1516            for (Call call : mCalls) {
1517                if (call.getParentCall() == null && call.getState() == state) {
1518                    count++;
1519                }
1520            }
1521        }
1522        return count;
1523    }
1524
1525    private boolean hasMaximumLiveCalls() {
1526        return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(LIVE_CALL_STATES);
1527    }
1528
1529    private boolean hasMaximumHoldingCalls() {
1530        return MAXIMUM_HOLD_CALLS <= getNumCallsWithState(CallState.ON_HOLD);
1531    }
1532
1533    private boolean hasMaximumRingingCalls() {
1534        return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(CallState.RINGING);
1535    }
1536
1537    private boolean hasMaximumOutgoingCalls() {
1538        return MAXIMUM_OUTGOING_CALLS <= getNumCallsWithState(OUTGOING_CALL_STATES);
1539    }
1540
1541    private boolean hasMaximumDialingCalls() {
1542        return MAXIMUM_DIALING_CALLS <= getNumCallsWithState(CallState.DIALING);
1543    }
1544
1545    private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
1546        if (hasMaximumLiveCalls()) {
1547            // NOTE: If the amount of live calls changes beyond 1, this logic will probably
1548            // have to change.
1549            Call liveCall = getFirstCallWithState(call, LIVE_CALL_STATES);
1550            Log.i(this, "makeRoomForOutgoingCall call = " + call + " livecall = " +
1551                   liveCall);
1552
1553            if (call == liveCall) {
1554                // If the call is already the foreground call, then we are golden.
1555                // This can happen after the user selects an account in the SELECT_PHONE_ACCOUNT
1556                // state since the call was already populated into the list.
1557                return true;
1558            }
1559
1560            if (hasMaximumOutgoingCalls()) {
1561                Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES);
1562                if (isEmergency && !outgoingCall.isEmergencyCall()) {
1563                    // Disconnect the current outgoing call if it's not an emergency call. If the
1564                    // user tries to make two outgoing calls to different emergency call numbers,
1565                    // we will try to connect the first outgoing call.
1566                    outgoingCall.disconnect();
1567                    return true;
1568                }
1569                if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) {
1570                    // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT}
1571                    // state, just disconnect it since the user has explicitly started a new call.
1572                    outgoingCall.disconnect();
1573                    return true;
1574                }
1575                return false;
1576            }
1577
1578            if (hasMaximumHoldingCalls()) {
1579                // There is no more room for any more calls, unless it's an emergency.
1580                if (isEmergency) {
1581                    // Kill the current active call, this is easier then trying to disconnect a
1582                    // holding call and hold an active call.
1583                    liveCall.disconnect();
1584                    return true;
1585                }
1586                return false;  // No more room!
1587            }
1588
1589            // We have room for at least one more holding call at this point.
1590
1591            // TODO: Remove once b/23035408 has been corrected.
1592            // If the live call is a conference, it will not have a target phone account set.  This
1593            // means the check to see if the live call has the same target phone account as the new
1594            // call will not cause us to bail early.  As a result, we'll end up holding the
1595            // ongoing conference call.  However, the ConnectionService is already doing that.  This
1596            // has caused problems with some carriers.  As a workaround until b/23035408 is
1597            // corrected, we will try and get the target phone account for one of the conference's
1598            // children and use that instead.
1599            PhoneAccountHandle liveCallPhoneAccount = liveCall.getTargetPhoneAccount();
1600            if (liveCallPhoneAccount == null && liveCall.isConference() &&
1601                    !liveCall.getChildCalls().isEmpty()) {
1602                liveCallPhoneAccount = getFirstChildPhoneAccount(liveCall);
1603                Log.i(this, "makeRoomForOutgoingCall: using child call PhoneAccount = " +
1604                        liveCallPhoneAccount);
1605            }
1606
1607            // First thing, if we are trying to make a call with the same phone account as the live
1608            // call, then allow it so that the connection service can make its own decision about
1609            // how to handle the new call relative to the current one.
1610            if (Objects.equals(liveCallPhoneAccount, call.getTargetPhoneAccount())) {
1611                Log.i(this, "makeRoomForOutgoingCall: phoneAccount matches.");
1612                return true;
1613            } else if (call.getTargetPhoneAccount() == null) {
1614                // Without a phone account, we can't say reliably that the call will fail.
1615                // If the user chooses the same phone account as the live call, then it's
1616                // still possible that the call can be made (like with CDMA calls not supporting
1617                // hold but they still support adding a call by going immediately into conference
1618                // mode). Return true here and we'll run this code again after user chooses an
1619                // account.
1620                return true;
1621            }
1622
1623            // Try to hold the live call before attempting the new outgoing call.
1624            if (liveCall.can(Connection.CAPABILITY_HOLD)) {
1625                Log.i(this, "makeRoomForOutgoingCall: holding live call.");
1626                liveCall.hold();
1627                return true;
1628            }
1629
1630            // The live call cannot be held so we're out of luck here.  There's no room.
1631            return false;
1632        }
1633        return true;
1634    }
1635
1636    /**
1637     * Given a call, find the first non-null phone account handle of its children.
1638     *
1639     * @param parentCall The parent call.
1640     * @return The first non-null phone account handle of the children, or {@code null} if none.
1641     */
1642    private PhoneAccountHandle getFirstChildPhoneAccount(Call parentCall) {
1643        for (Call childCall : parentCall.getChildCalls()) {
1644            PhoneAccountHandle childPhoneAccount = childCall.getTargetPhoneAccount();
1645            if (childPhoneAccount != null) {
1646                return childPhoneAccount;
1647            }
1648        }
1649        return null;
1650    }
1651
1652    /**
1653     * Checks to see if the call should be on speakerphone and if so, set it.
1654     */
1655    private void maybeMoveToSpeakerPhone(Call call) {
1656        if (call.getStartWithSpeakerphoneOn()) {
1657            setAudioRoute(CallAudioState.ROUTE_SPEAKER);
1658            call.setStartWithSpeakerphoneOn(false);
1659        }
1660    }
1661
1662    /**
1663     * Creates a new call for an existing connection.
1664     *
1665     * @param callId The id of the new call.
1666     * @param connection The connection information.
1667     * @return The new call.
1668     */
1669    Call createCallForExistingConnection(String callId, ParcelableConnection connection) {
1670        Call call = new Call(
1671                getNextCallId(),
1672                mContext,
1673                this,
1674                mLock,
1675                mConnectionServiceRepository,
1676                mContactsAsyncHelper,
1677                mCallerInfoAsyncQueryFactory,
1678                connection.getHandle() /* handle */,
1679                null /* gatewayInfo */,
1680                null /* connectionManagerPhoneAccount */,
1681                connection.getPhoneAccount(), /* targetPhoneAccountHandle */
1682                false /* isIncoming */,
1683                false /* isConference */,
1684                connection.getConnectTimeMillis() /* connectTimeMillis */);
1685
1686        setCallState(call, Call.getStateFromConnectionState(connection.getState()),
1687                "existing connection");
1688        call.setConnectionCapabilities(connection.getConnectionCapabilities());
1689        call.setCallerDisplayName(connection.getCallerDisplayName(),
1690                connection.getCallerDisplayNamePresentation());
1691
1692        call.addListener(this);
1693        addCall(call);
1694
1695        return call;
1696    }
1697
1698    /**
1699     * @return A new unique telecom call Id.
1700     */
1701    private String getNextCallId() {
1702        synchronized(mLock) {
1703            return TELECOM_CALL_ID_PREFIX + (++mCallId);
1704        }
1705    }
1706
1707    /**
1708     * Dumps the state of the {@link CallsManager}.
1709     *
1710     * @param pw The {@code IndentingPrintWriter} to write the state to.
1711     */
1712    public void dump(IndentingPrintWriter pw) {
1713        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1714        if (mCalls != null) {
1715            pw.println("mCalls: ");
1716            pw.increaseIndent();
1717            for (Call call : mCalls) {
1718                pw.println(call);
1719            }
1720            pw.decreaseIndent();
1721        }
1722        pw.println("mForegroundCall: " + (mForegroundCall == null ? "none" : mForegroundCall));
1723
1724        if (mCallAudioManager != null) {
1725            pw.println("mCallAudioManager:");
1726            pw.increaseIndent();
1727            mCallAudioManager.dump(pw);
1728            pw.decreaseIndent();
1729        }
1730
1731        if (mTtyManager != null) {
1732            pw.println("mTtyManager:");
1733            pw.increaseIndent();
1734            mTtyManager.dump(pw);
1735            pw.decreaseIndent();
1736        }
1737
1738        if (mInCallController != null) {
1739            pw.println("mInCallController:");
1740            pw.increaseIndent();
1741            mInCallController.dump(pw);
1742            pw.decreaseIndent();
1743        }
1744
1745        if (mConnectionServiceRepository != null) {
1746            pw.println("mConnectionServiceRepository:");
1747            pw.increaseIndent();
1748            mConnectionServiceRepository.dump(pw);
1749            pw.decreaseIndent();
1750        }
1751    }
1752}
1753