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