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