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