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