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