CallsManager.java revision 68d1a6b0bd8840b74c61a94928610312c09b0486
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.provider.CallLog.Calls;
23import android.telecom.AudioState;
24import android.telecom.CallState;
25import android.telecom.DisconnectCause;
26import android.telecom.GatewayInfo;
27import android.telecom.ParcelableConference;
28import android.telecom.PhoneAccountHandle;
29import android.telecom.PhoneCapabilities;
30import android.telephony.TelephonyManager;
31
32import com.android.internal.util.IndentingPrintWriter;
33import com.google.common.collect.ImmutableCollection;
34import com.google.common.collect.ImmutableList;
35
36import java.util.Collections;
37import java.util.List;
38import java.util.Objects;
39import java.util.Set;
40import java.util.concurrent.ConcurrentHashMap;
41
42/**
43 * Singleton.
44 *
45 * NOTE: by design most APIs are package private, use the relevant adapter/s to allow
46 * access from other packages specifically refraining from passing the CallsManager instance
47 * beyond the com.android.server.telecom package boundary.
48 */
49public final class CallsManager extends Call.ListenerBase {
50
51    // TODO: Consider renaming this CallsManagerPlugin.
52    interface CallsManagerListener {
53        void onCallAdded(Call call);
54        void onCallRemoved(Call call);
55        void onCallStateChanged(Call call, int oldState, int newState);
56        void onConnectionServiceChanged(
57                Call call,
58                ConnectionServiceWrapper oldService,
59                ConnectionServiceWrapper newService);
60        void onIncomingCallAnswered(Call call);
61        void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage);
62        void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall);
63        void onAudioStateChanged(AudioState oldAudioState, AudioState newAudioState);
64        void onRingbackRequested(Call call, boolean ringback);
65        void onIsConferencedChanged(Call call);
66        void onIsVoipAudioModeChanged(Call call);
67        void onVideoStateChanged(Call call);
68    }
69
70    /**
71     * Singleton instance of the {@link CallsManager}, initialized from {@link TelecomService}.
72     */
73    private static CallsManager INSTANCE = null;
74
75    private static final String TAG = "CallsManager";
76
77    private static final int MAXIMUM_LIVE_CALLS = 1;
78    private static final int MAXIMUM_HOLD_CALLS = 1;
79    private static final int MAXIMUM_RINGING_CALLS = 1;
80    private static final int MAXIMUM_OUTGOING_CALLS = 1;
81
82    private static final int[] LIVE_CALL_STATES =
83            {CallState.CONNECTING, CallState.PRE_DIAL_WAIT, CallState.DIALING, CallState.ACTIVE};
84
85    private static final int[] OUTGOING_CALL_STATES =
86            {CallState.CONNECTING, CallState.PRE_DIAL_WAIT, CallState.DIALING};
87
88    /**
89     * The main call repository. Keeps an instance of all live calls. New incoming and outgoing
90     * calls are added to the map and removed when the calls move to the disconnected state.
91    *
92     * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
93     * load factor before resizing, 1 means we only expect a single thread to
94     * access the map so make only a single shard
95     */
96    private final Set<Call> mCalls = Collections.newSetFromMap(
97            new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
98
99    private final ConnectionServiceRepository mConnectionServiceRepository;
100    private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
101    private final InCallController mInCallController;
102    private final CallAudioManager mCallAudioManager;
103    private final Ringer mRinger;
104    // For this set initial table size to 16 because we add 13 listeners in
105    // the CallsManager constructor.
106    private final Set<CallsManagerListener> mListeners = Collections.newSetFromMap(
107            new ConcurrentHashMap<CallsManagerListener, Boolean>(16, 0.9f, 1));
108    private final HeadsetMediaButton mHeadsetMediaButton;
109    private final WiredHeadsetManager mWiredHeadsetManager;
110    private final TtyManager mTtyManager;
111    private final ProximitySensorManager mProximitySensorManager;
112    private final PhoneStateBroadcaster mPhoneStateBroadcaster;
113    private final CallLogManager mCallLogManager;
114    private final Context mContext;
115    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
116    private final MissedCallNotifier mMissedCallNotifier;
117
118    /**
119     * The call the user is currently interacting with. This is the call that should have audio
120     * focus and be visible in the in-call UI.
121     */
122    private Call mForegroundCall;
123
124    /** Singleton accessor. */
125    static CallsManager getInstance() {
126        return INSTANCE;
127    }
128
129    /**
130     * Sets the static singleton instance.
131     *
132     * @param instance The instance to set.
133     */
134    static void initialize(CallsManager instance) {
135        INSTANCE = instance;
136    }
137
138    /**
139     * Initializes the required Telecom components.
140     */
141     CallsManager(Context context, MissedCallNotifier missedCallNotifier,
142             PhoneAccountRegistrar phoneAccountRegistrar) {
143        mContext = context;
144        mPhoneAccountRegistrar = phoneAccountRegistrar;
145        mMissedCallNotifier = missedCallNotifier;
146        StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
147        mWiredHeadsetManager = new WiredHeadsetManager(context);
148        mCallAudioManager = new CallAudioManager(context, statusBarNotifier, mWiredHeadsetManager);
149        InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager);
150        mRinger = new Ringer(mCallAudioManager, this, playerFactory, context);
151        mHeadsetMediaButton = new HeadsetMediaButton(context, this);
152        mTtyManager = new TtyManager(context, mWiredHeadsetManager);
153        mProximitySensorManager = new ProximitySensorManager(context);
154        mPhoneStateBroadcaster = new PhoneStateBroadcaster();
155        mCallLogManager = new CallLogManager(context);
156        mInCallController = new InCallController(context);
157        mDtmfLocalTonePlayer = new DtmfLocalTonePlayer(context);
158        mConnectionServiceRepository = new ConnectionServiceRepository(mPhoneAccountRegistrar,
159                context);
160
161        mListeners.add(statusBarNotifier);
162        mListeners.add(mCallLogManager);
163        mListeners.add(mPhoneStateBroadcaster);
164        mListeners.add(mInCallController);
165        mListeners.add(mRinger);
166        mListeners.add(new RingbackPlayer(this, playerFactory));
167        mListeners.add(new InCallToneMonitor(playerFactory, this));
168        mListeners.add(mCallAudioManager);
169        mListeners.add(missedCallNotifier);
170        mListeners.add(mDtmfLocalTonePlayer);
171        mListeners.add(mHeadsetMediaButton);
172        mListeners.add(RespondViaSmsManager.getInstance());
173        mListeners.add(mProximitySensorManager);
174    }
175
176    @Override
177    public void onSuccessfulOutgoingCall(Call call, int callState) {
178        Log.v(this, "onSuccessfulOutgoingCall, %s", call);
179
180        setCallState(call, callState);
181        if (!mCalls.contains(call)) {
182            // Call was not added previously in startOutgoingCall due to it being a potential MMI
183            // code, so add it now.
184            addCall(call);
185        }
186
187        // The call's ConnectionService has been updated.
188        for (CallsManagerListener listener : mListeners) {
189            listener.onConnectionServiceChanged(call, null, call.getConnectionService());
190        }
191
192        markCallAsDialing(call);
193    }
194
195    @Override
196    public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {
197        Log.v(this, "onFailedOutgoingCall, call: %s", call);
198
199        // TODO: Replace disconnect cause with more specific disconnect causes.
200        markCallAsDisconnected(call, disconnectCause);
201    }
202
203    @Override
204    public void onSuccessfulIncomingCall(Call incomingCall) {
205        Log.d(this, "onSuccessfulIncomingCall");
206        setCallState(incomingCall, CallState.RINGING);
207
208        if (hasMaximumRingingCalls()) {
209            incomingCall.reject(false, null);
210            // since the call was not added to the list of calls, we have to call the missed
211            // call notifier and the call logger manually.
212            mMissedCallNotifier.showMissedCallNotification(incomingCall);
213            mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE);
214        } else {
215            addCall(incomingCall);
216        }
217    }
218
219    @Override
220    public void onFailedIncomingCall(Call call) {
221        setCallState(call, CallState.DISCONNECTED);
222        call.removeListener(this);
223    }
224
225    @Override
226    public void onRingbackRequested(Call call, boolean ringback) {
227        for (CallsManagerListener listener : mListeners) {
228            listener.onRingbackRequested(call, ringback);
229        }
230    }
231
232    @Override
233    public void onPostDialWait(Call call, String remaining) {
234        mInCallController.onPostDialWait(call, remaining);
235    }
236
237    @Override
238    public void onParentChanged(Call call) {
239        for (CallsManagerListener listener : mListeners) {
240            listener.onIsConferencedChanged(call);
241        }
242    }
243
244    @Override
245    public void onChildrenChanged(Call call) {
246        for (CallsManagerListener listener : mListeners) {
247            listener.onIsConferencedChanged(call);
248        }
249    }
250
251    @Override
252    public void onIsVoipAudioModeChanged(Call call) {
253        for (CallsManagerListener listener : mListeners) {
254            listener.onIsVoipAudioModeChanged(call);
255        }
256    }
257
258    @Override
259    public void onVideoStateChanged(Call call) {
260        for (CallsManagerListener listener : mListeners) {
261            listener.onVideoStateChanged(call);
262        }
263    }
264
265    ImmutableCollection<Call> getCalls() {
266        return ImmutableList.copyOf(mCalls);
267    }
268
269    Call getForegroundCall() {
270        return mForegroundCall;
271    }
272
273    Ringer getRinger() {
274        return mRinger;
275    }
276
277    InCallController getInCallController() {
278        return mInCallController;
279    }
280
281    boolean hasEmergencyCall() {
282        for (Call call : mCalls) {
283            if (call.isEmergencyCall()) {
284                return true;
285            }
286        }
287        return false;
288    }
289
290    AudioState getAudioState() {
291        return mCallAudioManager.getAudioState();
292    }
293
294    boolean isTtySupported() {
295        return mTtyManager.isTtySupported();
296    }
297
298    int getCurrentTtyMode() {
299        return mTtyManager.getCurrentTtyMode();
300    }
301
302    void addListener(CallsManagerListener listener) {
303        mListeners.add(listener);
304    }
305
306    void removeListener(CallsManagerListener listener) {
307        mListeners.remove(listener);
308    }
309
310    /**
311     * Starts the process to attach the call to a connection service.
312     *
313     * @param phoneAccountHandle The phone account which contains the component name of the
314     *        connection service to use for this call.
315     * @param extras The optional extras Bundle passed with the intent used for the incoming call.
316     */
317    void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
318        Log.d(this, "processIncomingCallIntent");
319        Uri handle = extras.getParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER);
320        Call call = new Call(
321                mContext,
322                mConnectionServiceRepository,
323                handle,
324                null /* gatewayInfo */,
325                null /* connectionManagerPhoneAccount */,
326                phoneAccountHandle,
327                true /* isIncoming */,
328                false /* isConference */);
329
330        call.setExtras(extras);
331        // TODO: Move this to be a part of addCall()
332        call.addListener(this);
333        call.startCreateConnection(mPhoneAccountRegistrar);
334    }
335
336    /**
337     * Kicks off the first steps to creating an outgoing call so that InCallUI can launch.
338     *
339     * @param handle Handle to connect the call with.
340     * @param phoneAccountHandle The phone account which contains the component name of the
341     *        connection service to use for this call.
342     * @param extras The optional extras Bundle passed with the intent used for the incoming call.
343     */
344    Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) {
345        // Create a call with original handle. The handle may be changed when the call is attached
346        // to a connection service, but in most cases will remain the same.
347        Call call = new Call(
348                mContext,
349                mConnectionServiceRepository,
350                handle,
351                null /* gatewayInfo */,
352                null /* connectionManagerPhoneAccount */,
353                null /* phoneAccountHandle */,
354                false /* isIncoming */,
355                false /* isConference */);
356
357        List<PhoneAccountHandle> accounts =
358                mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme());
359
360        // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call
361        // as if a phoneAccount was not specified (does the default behavior instead).
362        // Note: We will not attempt to dial with a requested phoneAccount if it is disabled.
363        if (phoneAccountHandle != null) {
364            if (!accounts.contains(phoneAccountHandle)) {
365                phoneAccountHandle = null;
366            }
367        }
368
369        if (phoneAccountHandle == null) {
370            // No preset account, check if default exists that supports the URI scheme for the
371            // handle.
372            PhoneAccountHandle defaultAccountHandle =
373                    mPhoneAccountRegistrar.getDefaultOutgoingPhoneAccount(
374                            handle.getScheme());
375            if (defaultAccountHandle != null) {
376                phoneAccountHandle = defaultAccountHandle;
377            }
378        }
379
380        call.setTargetPhoneAccount(phoneAccountHandle);
381
382        boolean isEmergencyCall = TelephonyUtil.shouldProcessAsEmergency(mContext,
383                call.getHandle());
384
385        // Do not support any more live calls.  Our options are to move a call to hold, disconnect
386        // a call, or cancel this call altogether.
387        if (!makeRoomForOutgoingCall(call, isEmergencyCall)) {
388            // just cancel at this point.
389            return null;
390        }
391
392        if (phoneAccountHandle == null && accounts.size() > 1 && !isEmergencyCall) {
393            // This is the state where the user is expected to select an account
394            call.setState(CallState.PRE_DIAL_WAIT);
395            extras.putParcelableList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS, accounts);
396        } else {
397            call.setState(CallState.CONNECTING);
398        }
399
400        call.setExtras(extras);
401
402        if (!isPotentialMMICode(handle)) {
403            addCall(call);
404        } else {
405            call.addListener(this);
406        }
407
408        return call;
409    }
410
411    /**
412     * Attempts to issue/connect the specified call.
413     *
414     * @param handle Handle to connect the call with.
415     * @param gatewayInfo Optional gateway information that can be used to route the call to the
416     *        actual dialed handle via a gateway provider. May be null.
417     * @param speakerphoneOn Whether or not to turn the speakerphone on once the call connects.
418     * @param videoState The desired video state for the outgoing call.
419     */
420    void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn,
421            int videoState) {
422        if (call == null) {
423            // don't do anything if the call no longer exists
424            Log.i(this, "Canceling unknown call.");
425            return;
426        }
427
428        final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayAddress();
429
430        if (gatewayInfo == null) {
431            Log.i(this, "Creating a new outgoing call with handle: %s", Log.piiHandle(uriHandle));
432        } else {
433            Log.i(this, "Creating a new outgoing call with gateway handle: %s, original handle: %s",
434                    Log.pii(uriHandle), Log.pii(handle));
435        }
436
437        call.setHandle(uriHandle);
438        call.setGatewayInfo(gatewayInfo);
439        call.setStartWithSpeakerphoneOn(speakerphoneOn);
440        call.setVideoState(videoState);
441
442        boolean isEmergencyCall = TelephonyUtil.shouldProcessAsEmergency(mContext,
443                call.getHandle());
444        if (isEmergencyCall) {
445            // Emergency -- CreateConnectionProcessor will choose accounts automatically
446            call.setTargetPhoneAccount(null);
447        }
448
449        if (call.getTargetPhoneAccount() != null || isEmergencyCall) {
450            // If the account has been set, proceed to place the outgoing call.
451            // Otherwise the connection will be initiated when the account is set by the user.
452            call.startCreateConnection(mPhoneAccountRegistrar);
453        }
454    }
455
456    /**
457     * Attempts to start a conference call for the specified call.
458     *
459     * @param call The call to conference.
460     * @param otherCall The other call to conference with.
461     */
462    void conference(Call call, Call otherCall) {
463        call.conferenceWith(otherCall);
464    }
465
466    /**
467     * Instructs Telecom to answer the specified call. Intended to be invoked by the in-call
468     * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by
469     * the user opting to answer said call.
470     *
471     * @param call The call to answer.
472     * @param videoState The video state in which to answer the call.
473     */
474    void answerCall(Call call, int videoState) {
475        if (!mCalls.contains(call)) {
476            Log.i(this, "Request to answer a non-existent call %s", call);
477        } else {
478            // If the foreground call is not the ringing call and it is currently isActive() or
479            // STATE_DIALING, put it on hold before answering the call.
480            if (mForegroundCall != null && mForegroundCall != call &&
481                    (mForegroundCall.isActive() ||
482                     mForegroundCall.getState() == CallState.DIALING)) {
483                if (0 == (mForegroundCall.getCallCapabilities() & PhoneCapabilities.HOLD)) {
484                    // This call does not support hold.  If it is from a different connection
485                    // service, then disconnect it, otherwise allow the connection service to
486                    // figure out the right states.
487                    if (mForegroundCall.getConnectionService() != call.getConnectionService()) {
488                        mForegroundCall.disconnect();
489                    }
490                } else {
491                    Log.v(this, "Holding active/dialing call %s before answering incoming call %s.",
492                            mForegroundCall, call);
493                    mForegroundCall.hold();
494                }
495                // TODO: Wait until we get confirmation of the active call being
496                // on-hold before answering the new call.
497                // TODO: Import logic from CallManager.acceptCall()
498            }
499
500            for (CallsManagerListener listener : mListeners) {
501                listener.onIncomingCallAnswered(call);
502            }
503
504            // We do not update the UI until we get confirmation of the answer() through
505            // {@link #markCallAsActive}.
506            call.answer(videoState);
507        }
508    }
509
510    /**
511     * Instructs Telecom to reject the specified call. Intended to be invoked by the in-call
512     * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by
513     * the user opting to reject said call.
514     */
515    void rejectCall(Call call, boolean rejectWithMessage, String textMessage) {
516        if (!mCalls.contains(call)) {
517            Log.i(this, "Request to reject a non-existent call %s", call);
518        } else {
519            for (CallsManagerListener listener : mListeners) {
520                listener.onIncomingCallRejected(call, rejectWithMessage, textMessage);
521            }
522            call.reject(rejectWithMessage, textMessage);
523        }
524    }
525
526    /**
527     * Instructs Telecom to play the specified DTMF tone within the specified call.
528     *
529     * @param digit The DTMF digit to play.
530     */
531    void playDtmfTone(Call call, char digit) {
532        if (!mCalls.contains(call)) {
533            Log.i(this, "Request to play DTMF in a non-existent call %s", call);
534        } else {
535            call.playDtmfTone(digit);
536            mDtmfLocalTonePlayer.playTone(call, digit);
537        }
538    }
539
540    /**
541     * Instructs Telecom to stop the currently playing DTMF tone, if any.
542     */
543    void stopDtmfTone(Call call) {
544        if (!mCalls.contains(call)) {
545            Log.i(this, "Request to stop DTMF in a non-existent call %s", call);
546        } else {
547            call.stopDtmfTone();
548            mDtmfLocalTonePlayer.stopTone(call);
549        }
550    }
551
552    /**
553     * Instructs Telecom to continue (or not) the current post-dial DTMF string, if any.
554     */
555    void postDialContinue(Call call, boolean proceed) {
556        if (!mCalls.contains(call)) {
557            Log.i(this, "Request to continue post-dial string in a non-existent call %s", call);
558        } else {
559            call.postDialContinue(proceed);
560        }
561    }
562
563    /**
564     * Instructs Telecom to disconnect the specified call. Intended to be invoked by the
565     * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
566     * the user hitting the end-call button.
567     */
568    void disconnectCall(Call call) {
569        Log.v(this, "disconnectCall %s", call);
570
571        if (!mCalls.contains(call)) {
572            Log.w(this, "Unknown call (%s) asked to disconnect", call);
573        } else {
574            call.disconnect();
575        }
576    }
577
578    /**
579     * Instructs Telecom to disconnect all calls.
580     */
581    void disconnectAllCalls() {
582        Log.v(this, "disconnectAllCalls");
583
584        for (Call call : mCalls) {
585            disconnectCall(call);
586        }
587    }
588
589
590    /**
591     * Instructs Telecom to put the specified call on hold. Intended to be invoked by the
592     * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
593     * the user hitting the hold button during an active call.
594     */
595    void holdCall(Call call) {
596        if (!mCalls.contains(call)) {
597            Log.w(this, "Unknown call (%s) asked to be put on hold", call);
598        } else {
599            Log.d(this, "Putting call on hold: (%s)", call);
600            call.hold();
601        }
602    }
603
604    /**
605     * Instructs Telecom to release the specified call from hold. Intended to be invoked by
606     * the in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered
607     * by the user hitting the hold button during a held call.
608     */
609    void unholdCall(Call call) {
610        if (!mCalls.contains(call)) {
611            Log.w(this, "Unknown call (%s) asked to be removed from hold", call);
612        } else {
613            Log.d(this, "unholding call: (%s)", call);
614            for (Call c : mCalls) {
615                if (c != null && c.isAlive() && c != call) {
616                    c.hold();
617                }
618            }
619            call.unhold();
620        }
621    }
622
623    /** Called by the in-call UI to change the mute state. */
624    void mute(boolean shouldMute) {
625        mCallAudioManager.mute(shouldMute);
626    }
627
628    /**
629      * Called by the in-call UI to change the audio route, for example to change from earpiece to
630      * speaker phone.
631      */
632    void setAudioRoute(int route) {
633        mCallAudioManager.setAudioRoute(route);
634    }
635
636    /** Called by the in-call UI to turn the proximity sensor on. */
637    void turnOnProximitySensor() {
638        mProximitySensorManager.turnOn();
639    }
640
641    /**
642     * Called by the in-call UI to turn the proximity sensor off.
643     * @param screenOnImmediately If true, the screen will be turned on immediately. Otherwise,
644     *        the screen will be kept off until the proximity sensor goes negative.
645     */
646    void turnOffProximitySensor(boolean screenOnImmediately) {
647        mProximitySensorManager.turnOff(screenOnImmediately);
648    }
649
650    void phoneAccountSelected(Call call, PhoneAccountHandle account) {
651        if (!mCalls.contains(call)) {
652            Log.i(this, "Attempted to add account to unknown call %s", call);
653        } else {
654            // TODO: There is an odd race condition here. Since NewOutgoingCallIntentBroadcaster and
655            // the PRE_DIAL_WAIT sequence run in parallel, if the user selects an account before the
656            // NEW_OUTGOING_CALL sequence finishes, we'll start the call immediately without
657            // respecting a rewritten number or a canceled number. This is unlikely since
658            // NEW_OUTGOING_CALL sequence, in practice, runs a lot faster than the user selecting
659            // a phone account from the in-call UI.
660            call.setTargetPhoneAccount(account);
661
662            // Note: emergency calls never go through account selection dialog so they never
663            // arrive here.
664            if (makeRoomForOutgoingCall(call, false /* isEmergencyCall */)) {
665                call.startCreateConnection(mPhoneAccountRegistrar);
666            } else {
667                call.disconnect();
668            }
669        }
670    }
671
672    /** Called when the audio state changes. */
673    void onAudioStateChanged(AudioState oldAudioState, AudioState newAudioState) {
674        Log.v(this, "onAudioStateChanged, audioState: %s -> %s", oldAudioState, newAudioState);
675        for (CallsManagerListener listener : mListeners) {
676            listener.onAudioStateChanged(oldAudioState, newAudioState);
677        }
678    }
679
680    void markCallAsRinging(Call call) {
681        setCallState(call, CallState.RINGING);
682    }
683
684    void markCallAsDialing(Call call) {
685        setCallState(call, CallState.DIALING);
686    }
687
688    void markCallAsActive(Call call) {
689        if (call.getConnectTimeMillis() == 0) {
690            call.setConnectTimeMillis(System.currentTimeMillis());
691        }
692        setCallState(call, CallState.ACTIVE);
693
694        if (call.getStartWithSpeakerphoneOn()) {
695            setAudioRoute(AudioState.ROUTE_SPEAKER);
696        }
697    }
698
699    void markCallAsOnHold(Call call) {
700        setCallState(call, CallState.ON_HOLD);
701    }
702
703    /**
704     * Marks the specified call as STATE_DISCONNECTED and notifies the in-call app. If this was the
705     * last live call, then also disconnect from the in-call controller.
706     *
707     * @param disconnectCause The disconnect cause, see {@link android.telecomm.DisconnectCause}.
708     */
709    void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
710        call.setDisconnectCause(disconnectCause);
711        setCallState(call, CallState.DISCONNECTED);
712        removeCall(call);
713    }
714
715    /**
716     * Removes an existing disconnected call, and notifies the in-call app.
717     */
718    void markCallAsRemoved(Call call) {
719        removeCall(call);
720    }
721
722    /**
723     * Cleans up any calls currently associated with the specified connection service when the
724     * service binder disconnects unexpectedly.
725     *
726     * @param service The connection service that disconnected.
727     */
728    void handleConnectionServiceDeath(ConnectionServiceWrapper service) {
729        if (service != null) {
730            for (Call call : mCalls) {
731                if (call.getConnectionService() == service) {
732                    markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR));
733                }
734            }
735        }
736    }
737
738    boolean hasAnyCalls() {
739        return !mCalls.isEmpty();
740    }
741
742    boolean hasActiveOrHoldingCall() {
743        return getFirstCallWithState(CallState.ACTIVE, CallState.ON_HOLD) != null;
744    }
745
746    boolean hasRingingCall() {
747        return getFirstCallWithState(CallState.RINGING) != null;
748    }
749
750    boolean onMediaButton(int type) {
751        if (hasAnyCalls()) {
752            if (HeadsetMediaButton.SHORT_PRESS == type) {
753                Call ringingCall = getFirstCallWithState(CallState.RINGING);
754                if (ringingCall == null) {
755                    mCallAudioManager.toggleMute();
756                    return true;
757                } else {
758                    ringingCall.answer(ringingCall.getVideoState());
759                    return true;
760                }
761            } else if (HeadsetMediaButton.LONG_PRESS == type) {
762                Log.d(this, "handleHeadsetHook: longpress -> hangup");
763                Call callToHangup = getFirstCallWithState(
764                        CallState.RINGING, CallState.DIALING, CallState.ACTIVE, CallState.ON_HOLD);
765                if (callToHangup != null) {
766                    callToHangup.disconnect();
767                    return true;
768                }
769            }
770        }
771        return false;
772    }
773
774    /**
775     * Checks to see if the specified call is the only high-level call and if so, enable the
776     * "Add-call" button. We allow you to add a second call but not a third or beyond.
777     *
778     * @param call The call to test for add-call.
779     * @return Whether the add-call feature should be enabled for the call.
780     */
781    protected boolean isAddCallCapable(Call call) {
782        if (call.getParentCall() != null) {
783            // Never true for child calls.
784            return false;
785        }
786
787        // Use canManageConference as a mechanism to check if the call is CDMA.
788        // Disable "Add Call" for CDMA calls which are conference calls.
789        boolean canManageConference = PhoneCapabilities.MANAGE_CONFERENCE
790                == (call.getCallCapabilities() & PhoneCapabilities.MANAGE_CONFERENCE);
791        if (call.isConference() && !canManageConference) {
792            return false;
793        }
794
795        // Loop through all the other calls and there exists a top level (has no parent) call
796        // that is not the specified call, return false.
797        for (Call otherCall : mCalls) {
798            if (call != otherCall && otherCall.getParentCall() == null) {
799                return false;
800            }
801        }
802        return true;
803    }
804
805    Call getRingingCall() {
806        return getFirstCallWithState(CallState.RINGING);
807    }
808
809    Call getActiveCall() {
810        return getFirstCallWithState(CallState.ACTIVE);
811    }
812
813    Call getDialingOrConnectingCall() {
814        return getFirstCallWithState(CallState.DIALING, CallState.CONNECTING);
815    }
816
817    Call getHeldCall() {
818        return getFirstCallWithState(CallState.ON_HOLD);
819    }
820
821    Call getFirstCallWithState(int... states) {
822        return getFirstCallWithState(null, states);
823    }
824
825    /**
826     * Returns the first call that it finds with the given states. The states are treated as having
827     * priority order so that any call with the first state will be returned before any call with
828     * states listed later in the parameter list.
829     *
830     * @param callToSkip Call that this method should skip while searching
831     */
832    Call getFirstCallWithState(Call callToSkip, int... states) {
833        for (int currentState : states) {
834            // check the foreground first
835            if (mForegroundCall != null && mForegroundCall.getState() == currentState) {
836                return mForegroundCall;
837            }
838
839            for (Call call : mCalls) {
840                if (Objects.equals(callToSkip, call)) {
841                    continue;
842                }
843
844                // Only operate on top-level calls
845                if (call.getParentCall() != null) {
846                    continue;
847                }
848
849                if (currentState == call.getState()) {
850                    return call;
851                }
852            }
853        }
854        return null;
855    }
856
857    Call createConferenceCall(
858            PhoneAccountHandle phoneAccount,
859            ParcelableConference parcelableConference) {
860        Call call = new Call(
861                mContext,
862                mConnectionServiceRepository,
863                null /* handle */,
864                null /* gatewayInfo */,
865                null /* connectionManagerPhoneAccount */,
866                phoneAccount,
867                false /* isIncoming */,
868                true /* isConference */);
869
870        setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()));
871        if (call.getState() == CallState.ACTIVE) {
872            call.setConnectTimeMillis(System.currentTimeMillis());
873        }
874        call.setCallCapabilities(parcelableConference.getCapabilities());
875
876        // TODO: Move this to be a part of addCall()
877        call.addListener(this);
878        addCall(call);
879        return call;
880    }
881
882    /**
883     * @return the call state currently tracked by {@link PhoneStateBroadcaster}
884     */
885    int getCallState() {
886        return mPhoneStateBroadcaster.getCallState();
887    }
888
889    /**
890     * Retrieves the {@link PhoneAccountRegistrar}.
891     *
892     * @return The {@link PhoneAccountRegistrar}.
893     */
894    PhoneAccountRegistrar getPhoneAccountRegistrar() {
895        return mPhoneAccountRegistrar;
896    }
897
898    /**
899     * Retrieves the {@link MissedCallNotifier}
900     * @return The {@link MissedCallNotifier}.
901     */
902    MissedCallNotifier getMissedCallNotifier() {
903        return mMissedCallNotifier;
904    }
905
906    /**
907     * Adds the specified call to the main list of live calls.
908     *
909     * @param call The call to add.
910     */
911    private void addCall(Call call) {
912        Log.v(this, "addCall(%s)", call);
913
914        call.addListener(this);
915        mCalls.add(call);
916
917        // TODO: Update mForegroundCall prior to invoking
918        // onCallAdded for calls which immediately take the foreground (like the first call).
919        for (CallsManagerListener listener : mListeners) {
920            listener.onCallAdded(call);
921        }
922        updateForegroundCall();
923    }
924
925    private void removeCall(Call call) {
926        Log.v(this, "removeCall(%s)", call);
927
928        call.setParentCall(null);  // need to clean up parent relationship before destroying.
929        call.removeListener(this);
930        call.clearConnectionService();
931
932        boolean shouldNotify = false;
933        if (mCalls.contains(call)) {
934            mCalls.remove(call);
935            shouldNotify = true;
936        }
937
938        // Only broadcast changes for calls that are being tracked.
939        if (shouldNotify) {
940            for (CallsManagerListener listener : mListeners) {
941                listener.onCallRemoved(call);
942            }
943            updateForegroundCall();
944        }
945    }
946
947    /**
948     * Sets the specified state on the specified call.
949     *
950     * @param call The call.
951     * @param newState The new state of the call.
952     */
953    private void setCallState(Call call, int newState) {
954        if (call == null) {
955            return;
956        }
957        int oldState = call.getState();
958        Log.i(this, "setCallState %s -> %s, call: %s", CallState.toString(oldState),
959                CallState.toString(newState), call);
960        if (newState != oldState) {
961            // Unfortunately, in the telephony world the radio is king. So if the call notifies
962            // us that the call is in a particular state, we allow it even if it doesn't make
963            // sense (e.g., STATE_ACTIVE -> STATE_RINGING).
964            // TODO: Consider putting a stop to the above and turning CallState
965            // into a well-defined state machine.
966            // TODO: Define expected state transitions here, and log when an
967            // unexpected transition occurs.
968            call.setState(newState);
969
970            // Only broadcast state change for calls that are being tracked.
971            if (mCalls.contains(call)) {
972                for (CallsManagerListener listener : mListeners) {
973                    listener.onCallStateChanged(call, oldState, newState);
974                }
975                updateForegroundCall();
976            }
977        }
978    }
979
980    /**
981     * Checks which call should be visible to the user and have audio focus.
982     */
983    private void updateForegroundCall() {
984        Call newForegroundCall = null;
985        for (Call call : mCalls) {
986            // TODO: Foreground-ness needs to be explicitly set. No call, regardless
987            // of its state will be foreground by default and instead the connection service should
988            // be notified when its calls enter and exit foreground state. Foreground will mean that
989            // the call should play audio and listen to microphone if it wants.
990
991            // Only top-level calls can be in foreground
992            if (call.getParentCall() != null) {
993                continue;
994            }
995
996            // Active calls have priority.
997            if (call.isActive()) {
998                newForegroundCall = call;
999                break;
1000            }
1001
1002            if (call.isAlive() || call.getState() == CallState.RINGING) {
1003                newForegroundCall = call;
1004                // Don't break in case there's an active call that has priority.
1005            }
1006        }
1007
1008        if (newForegroundCall != mForegroundCall) {
1009            Log.v(this, "Updating foreground call, %s -> %s.", mForegroundCall, newForegroundCall);
1010            Call oldForegroundCall = mForegroundCall;
1011            mForegroundCall = newForegroundCall;
1012            if (mForegroundCall != null && mForegroundCall.getState() == CallState.ON_HOLD) {
1013                mForegroundCall.unhold();
1014            }
1015            for (CallsManagerListener listener : mListeners) {
1016                listener.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
1017            }
1018        }
1019    }
1020
1021    private boolean isPotentialMMICode(Uri handle) {
1022        return (handle != null && handle.getSchemeSpecificPart() != null
1023                && handle.getSchemeSpecificPart().contains("#"));
1024    }
1025
1026    private int getNumCallsWithState(int... states) {
1027        int count = 0;
1028        for (int state : states) {
1029            for (Call call : mCalls) {
1030                if (call.getState() == state) {
1031                    count++;
1032                }
1033            }
1034        }
1035        return count;
1036    }
1037
1038    private boolean hasMaximumLiveCalls() {
1039        return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(LIVE_CALL_STATES);
1040    }
1041
1042    private boolean hasMaximumHoldingCalls() {
1043        return MAXIMUM_HOLD_CALLS <= getNumCallsWithState(CallState.ON_HOLD);
1044    }
1045
1046    private boolean hasMaximumRingingCalls() {
1047        return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(CallState.RINGING);
1048    }
1049
1050    private boolean hasMaximumOutgoingCalls() {
1051        return MAXIMUM_OUTGOING_CALLS <= getNumCallsWithState(OUTGOING_CALL_STATES);
1052    }
1053
1054    private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
1055        if (hasMaximumLiveCalls()) {
1056            // NOTE: If the amount of live calls changes beyond 1, this logic will probably
1057            // have to change.
1058            Call liveCall = getFirstCallWithState(call, LIVE_CALL_STATES);
1059
1060            if (hasMaximumOutgoingCalls()) {
1061                // Disconnect the current outgoing call if it's not an emergency call. If the user
1062                // tries to make two outgoing calls to different emergency call numbers, we will try
1063                // to connect the first outgoing call.
1064                if (isEmergency) {
1065                    Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES);
1066                    if (!outgoingCall.isEmergencyCall()) {
1067                        outgoingCall.disconnect();
1068                        return true;
1069                    }
1070                }
1071                return false;
1072            }
1073
1074            if (hasMaximumHoldingCalls()) {
1075                // There is no more room for any more calls, unless it's an emergency.
1076                if (isEmergency) {
1077                    // Kill the current active call, this is easier then trying to disconnect a
1078                    // holding call and hold an active call.
1079                    liveCall.disconnect();
1080                    return true;
1081                }
1082                return false;  // No more room!
1083            }
1084
1085            // We have room for at least one more holding call at this point.
1086
1087            // First thing, if we are trying to make a call with the same phone account as the live
1088            // call, then allow it so that the connection service can make its own decision about
1089            // how to handle the new call relative to the current one.
1090            if (Objects.equals(liveCall.getTargetPhoneAccount(), call.getTargetPhoneAccount())) {
1091                return true;
1092            } else if (call.getTargetPhoneAccount() == null) {
1093                // Without a phone account, we can't say reliably that the call will fail.
1094                // If the user chooses the same phone account as the live call, then it's
1095                // still possible that the call can be made (like with CDMA calls not supporting
1096                // hold but they still support adding a call by going immediately into conference
1097                // mode). Return true here and we'll run this code again after user chooses an
1098                // account.
1099                return true;
1100            }
1101
1102            // Try to hold the live call before attempting the new outgoing call.
1103            if (liveCall.can(PhoneCapabilities.HOLD)) {
1104                liveCall.hold();
1105                return true;
1106            }
1107
1108            // The live call cannot be held so we're out of luck here.  There's no room.
1109            return false;
1110        }
1111        return true;
1112    }
1113
1114    /**
1115     * Dumps the state of the {@link CallsManager}.
1116     *
1117     * @param pw The {@code IndentingPrintWriter} to write the state to.
1118     */
1119    public void dump(IndentingPrintWriter pw) {
1120        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1121        pw.increaseIndent();
1122        if (mCalls != null) {
1123            pw.println("mCalls: ");
1124            pw.increaseIndent();
1125            for (Call call : mCalls) {
1126                pw.println(call);
1127            }
1128            pw.decreaseIndent();
1129        }
1130        pw.decreaseIndent();
1131    }
1132}
1133