• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.graphics.Bitmap;
21 import android.graphics.drawable.Drawable;
22 import android.net.Uri;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.Looper;
26 import android.os.RemoteException;
27 import android.os.Trace;
28 import android.provider.ContactsContract.Contacts;
29 import android.telecom.DisconnectCause;
30 import android.telecom.Connection;
31 import android.telecom.GatewayInfo;
32 import android.telecom.InCallService.VideoCall;
33 import android.telecom.ParcelableConnection;
34 import android.telecom.PhoneAccount;
35 import android.telecom.PhoneAccountHandle;
36 import android.telecom.Response;
37 import android.telecom.StatusHints;
38 import android.telecom.TelecomManager;
39 import android.telecom.VideoProfile;
40 import android.telephony.PhoneNumberUtils;
41 import android.text.TextUtils;
42 
43 import com.android.internal.annotations.VisibleForTesting;
44 import com.android.internal.telecom.IVideoProvider;
45 import com.android.internal.telephony.CallerInfo;
46 import com.android.internal.telephony.CallerInfoAsyncQuery.OnQueryCompleteListener;
47 import com.android.internal.telephony.SmsApplication;
48 import com.android.server.telecom.ContactsAsyncHelper.OnImageLoadCompleteListener;
49 import com.android.internal.util.Preconditions;
50 
51 import java.util.ArrayList;
52 import java.util.Collections;
53 import java.util.LinkedList;
54 import java.util.List;
55 import java.util.Locale;
56 import java.util.Objects;
57 import java.util.Set;
58 import java.util.concurrent.ConcurrentHashMap;
59 
60 /**
61  *  Encapsulates all aspects of a given phone call throughout its lifecycle, starting
62  *  from the time the call intent was received by Telecom (vs. the time the call was
63  *  connected etc).
64  */
65 @VisibleForTesting
66 public class Call implements CreateConnectionResponse {
67     /**
68      * Listener for events on the call.
69      */
70     interface Listener {
onSuccessfulOutgoingCall(Call call, int callState)71         void onSuccessfulOutgoingCall(Call call, int callState);
onFailedOutgoingCall(Call call, DisconnectCause disconnectCause)72         void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause);
onSuccessfulIncomingCall(Call call)73         void onSuccessfulIncomingCall(Call call);
onFailedIncomingCall(Call call)74         void onFailedIncomingCall(Call call);
onSuccessfulUnknownCall(Call call, int callState)75         void onSuccessfulUnknownCall(Call call, int callState);
onFailedUnknownCall(Call call)76         void onFailedUnknownCall(Call call);
onRingbackRequested(Call call, boolean ringbackRequested)77         void onRingbackRequested(Call call, boolean ringbackRequested);
onPostDialWait(Call call, String remaining)78         void onPostDialWait(Call call, String remaining);
onPostDialChar(Call call, char nextChar)79         void onPostDialChar(Call call, char nextChar);
onConnectionCapabilitiesChanged(Call call)80         void onConnectionCapabilitiesChanged(Call call);
onParentChanged(Call call)81         void onParentChanged(Call call);
onChildrenChanged(Call call)82         void onChildrenChanged(Call call);
onCannedSmsResponsesLoaded(Call call)83         void onCannedSmsResponsesLoaded(Call call);
onVideoCallProviderChanged(Call call)84         void onVideoCallProviderChanged(Call call);
onCallerInfoChanged(Call call)85         void onCallerInfoChanged(Call call);
onIsVoipAudioModeChanged(Call call)86         void onIsVoipAudioModeChanged(Call call);
onStatusHintsChanged(Call call)87         void onStatusHintsChanged(Call call);
onExtrasChanged(Call call)88         void onExtrasChanged(Call call);
onHandleChanged(Call call)89         void onHandleChanged(Call call);
onCallerDisplayNameChanged(Call call)90         void onCallerDisplayNameChanged(Call call);
onVideoStateChanged(Call call)91         void onVideoStateChanged(Call call);
onTargetPhoneAccountChanged(Call call)92         void onTargetPhoneAccountChanged(Call call);
onConnectionManagerPhoneAccountChanged(Call call)93         void onConnectionManagerPhoneAccountChanged(Call call);
onPhoneAccountChanged(Call call)94         void onPhoneAccountChanged(Call call);
onConferenceableCallsChanged(Call call)95         void onConferenceableCallsChanged(Call call);
onCanceledViaNewOutgoingCallBroadcast(Call call)96         boolean onCanceledViaNewOutgoingCallBroadcast(Call call);
97     }
98 
99     public abstract static class ListenerBase implements Listener {
100         @Override
onSuccessfulOutgoingCall(Call call, int callState)101         public void onSuccessfulOutgoingCall(Call call, int callState) {}
102         @Override
onFailedOutgoingCall(Call call, DisconnectCause disconnectCause)103         public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {}
104         @Override
onSuccessfulIncomingCall(Call call)105         public void onSuccessfulIncomingCall(Call call) {}
106         @Override
onFailedIncomingCall(Call call)107         public void onFailedIncomingCall(Call call) {}
108         @Override
onSuccessfulUnknownCall(Call call, int callState)109         public void onSuccessfulUnknownCall(Call call, int callState) {}
110         @Override
onFailedUnknownCall(Call call)111         public void onFailedUnknownCall(Call call) {}
112         @Override
onRingbackRequested(Call call, boolean ringbackRequested)113         public void onRingbackRequested(Call call, boolean ringbackRequested) {}
114         @Override
onPostDialWait(Call call, String remaining)115         public void onPostDialWait(Call call, String remaining) {}
116         @Override
onPostDialChar(Call call, char nextChar)117         public void onPostDialChar(Call call, char nextChar) {}
118         @Override
onConnectionCapabilitiesChanged(Call call)119         public void onConnectionCapabilitiesChanged(Call call) {}
120         @Override
onParentChanged(Call call)121         public void onParentChanged(Call call) {}
122         @Override
onChildrenChanged(Call call)123         public void onChildrenChanged(Call call) {}
124         @Override
onCannedSmsResponsesLoaded(Call call)125         public void onCannedSmsResponsesLoaded(Call call) {}
126         @Override
onVideoCallProviderChanged(Call call)127         public void onVideoCallProviderChanged(Call call) {}
128         @Override
onCallerInfoChanged(Call call)129         public void onCallerInfoChanged(Call call) {}
130         @Override
onIsVoipAudioModeChanged(Call call)131         public void onIsVoipAudioModeChanged(Call call) {}
132         @Override
onStatusHintsChanged(Call call)133         public void onStatusHintsChanged(Call call) {}
134         @Override
onExtrasChanged(Call call)135         public void onExtrasChanged(Call call) {}
136         @Override
onHandleChanged(Call call)137         public void onHandleChanged(Call call) {}
138         @Override
onCallerDisplayNameChanged(Call call)139         public void onCallerDisplayNameChanged(Call call) {}
140         @Override
onVideoStateChanged(Call call)141         public void onVideoStateChanged(Call call) {}
142         @Override
onTargetPhoneAccountChanged(Call call)143         public void onTargetPhoneAccountChanged(Call call) {}
144         @Override
onConnectionManagerPhoneAccountChanged(Call call)145         public void onConnectionManagerPhoneAccountChanged(Call call) {}
146         @Override
onPhoneAccountChanged(Call call)147         public void onPhoneAccountChanged(Call call) {}
148         @Override
onConferenceableCallsChanged(Call call)149         public void onConferenceableCallsChanged(Call call) {}
150         @Override
onCanceledViaNewOutgoingCallBroadcast(Call call)151         public boolean onCanceledViaNewOutgoingCallBroadcast(Call call) {
152             return false;
153         }
154     }
155 
156     private final OnQueryCompleteListener mCallerInfoQueryListener =
157             new OnQueryCompleteListener() {
158                 /** ${inheritDoc} */
159                 @Override
160                 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
161                     synchronized (mLock) {
162                         if (cookie != null) {
163                             ((Call) cookie).setCallerInfo(callerInfo, token);
164                         }
165                     }
166                 }
167             };
168 
169     private final OnImageLoadCompleteListener mPhotoLoadListener =
170             new OnImageLoadCompleteListener() {
171                 /** ${inheritDoc} */
172                 @Override
173                 public void onImageLoadComplete(
174                         int token, Drawable photo, Bitmap photoIcon, Object cookie) {
175                     synchronized (mLock) {
176                         if (cookie != null) {
177                             ((Call) cookie).setPhoto(photo, photoIcon, token);
178                         }
179                     }
180                 }
181             };
182 
183     private final Runnable mDirectToVoicemailRunnable = new Runnable() {
184         @Override
185         public void run() {
186             synchronized (mLock) {
187                 processDirectToVoicemail();
188             }
189         }
190     };
191 
192     /** True if this is an incoming call. */
193     private final boolean mIsIncoming;
194 
195     /** True if this is a currently unknown call that was not previously tracked by CallsManager,
196      *  and did not originate via the regular incoming/outgoing call code paths.
197      */
198     private boolean mIsUnknown;
199 
200     /**
201      * The time this call was created. Beyond logging and such, may also be used for bookkeeping
202      * and specifically for marking certain call attempts as failed attempts.
203      */
204     private long mCreationTimeMillis = System.currentTimeMillis();
205 
206     /** The time this call was made active. */
207     private long mConnectTimeMillis = 0;
208 
209     /** The time this call was disconnected. */
210     private long mDisconnectTimeMillis = 0;
211 
212     /** The gateway information associated with this call. This stores the original call handle
213      * that the user is attempting to connect to via the gateway, the actual handle to dial in
214      * order to connect the call via the gateway, as well as the package name of the gateway
215      * service. */
216     private GatewayInfo mGatewayInfo;
217 
218     private PhoneAccountHandle mConnectionManagerPhoneAccountHandle;
219 
220     private PhoneAccountHandle mTargetPhoneAccountHandle;
221 
222     private final Handler mHandler = new Handler(Looper.getMainLooper());
223 
224     private final List<Call> mConferenceableCalls = new ArrayList<>();
225 
226     /** The state of the call. */
227     private int mState;
228 
229     /** The handle with which to establish this call. */
230     private Uri mHandle;
231 
232     /**
233      * The presentation requirements for the handle. See {@link TelecomManager} for valid values.
234      */
235     private int mHandlePresentation;
236 
237     /** The caller display name (CNAP) set by the connection service. */
238     private String mCallerDisplayName;
239 
240     /**
241      * The presentation requirements for the handle. See {@link TelecomManager} for valid values.
242      */
243     private int mCallerDisplayNamePresentation;
244 
245     /**
246      * The connection service which is attempted or already connecting this call.
247      */
248     private ConnectionServiceWrapper mConnectionService;
249 
250     private boolean mIsEmergencyCall;
251 
252     private boolean mSpeakerphoneOn;
253 
254     /**
255      * Tracks the video states which were applicable over the duration of a call.
256      * See {@link VideoProfile} for a list of valid video states.
257      * <p>
258      * Video state history is tracked when the call is active, and when a call is rejected or
259      * missed.
260      */
261     private int mVideoStateHistory;
262 
263     private int mVideoState;
264 
265     /**
266      * Disconnect cause for the call. Only valid if the state of the call is STATE_DISCONNECTED.
267      * See {@link android.telecom.DisconnectCause}.
268      */
269     private DisconnectCause mDisconnectCause = new DisconnectCause(DisconnectCause.UNKNOWN);
270 
271     private Bundle mIntentExtras = new Bundle();
272 
273     /** Set of listeners on this call.
274      *
275      * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
276      * load factor before resizing, 1 means we only expect a single thread to
277      * access the map so make only a single shard
278      */
279     private final Set<Listener> mListeners = Collections.newSetFromMap(
280             new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
281 
282     private CreateConnectionProcessor mCreateConnectionProcessor;
283 
284     /** Caller information retrieved from the latest contact query. */
285     private CallerInfo mCallerInfo;
286 
287     /** The latest token used with a contact info query. */
288     private int mQueryToken = 0;
289 
290     /** Whether this call is requesting that Telecom play the ringback tone on its behalf. */
291     private boolean mRingbackRequested = false;
292 
293     /** Whether direct-to-voicemail query is pending. */
294     private boolean mDirectToVoicemailQueryPending;
295 
296     private int mConnectionCapabilities;
297 
298     private boolean mIsConference = false;
299 
300     private Call mParentCall = null;
301 
302     private List<Call> mChildCalls = new LinkedList<>();
303 
304     /** Set of text message responses allowed for this call, if applicable. */
305     private List<String> mCannedSmsResponses = Collections.EMPTY_LIST;
306 
307     /** Whether an attempt has been made to load the text message responses. */
308     private boolean mCannedSmsResponsesLoadingStarted = false;
309 
310     private IVideoProvider mVideoProvider;
311     private VideoProviderProxy mVideoProviderProxy;
312 
313     private boolean mIsVoipAudioMode;
314     private StatusHints mStatusHints;
315     private Bundle mExtras;
316     private final ConnectionServiceRepository mRepository;
317     private final ContactsAsyncHelper mContactsAsyncHelper;
318     private final Context mContext;
319     private final CallsManager mCallsManager;
320     private final TelecomSystem.SyncRoot mLock;
321     private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
322 
323     private boolean mWasConferencePreviouslyMerged = false;
324 
325     // For conferences which support merge/swap at their level, we retain a notion of an active
326     // call. This is used for BluetoothPhoneService.  In order to support hold/merge, it must have
327     // the notion of the current "active" call within the conference call. This maintains the
328     // "active" call and switches every time the user hits "swap".
329     private Call mConferenceLevelActiveCall = null;
330 
331     private boolean mIsLocallyDisconnecting = false;
332 
333     /**
334      * Persists the specified parameters and initializes the new instance.
335      *
336      * @param context The context.
337      * @param repository The connection service repository.
338      * @param handle The handle to dial.
339      * @param gatewayInfo Gateway information to use for the call.
340      * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
341      *         This account must be one that was registered with the
342      *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
343      * @param targetPhoneAccountHandle Account information to use for the call. This account must be
344      *         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
345      * @param isIncoming True if this is an incoming call.
346      */
Call( Context context, CallsManager callsManager, TelecomSystem.SyncRoot lock, ConnectionServiceRepository repository, ContactsAsyncHelper contactsAsyncHelper, CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory, Uri handle, GatewayInfo gatewayInfo, PhoneAccountHandle connectionManagerPhoneAccountHandle, PhoneAccountHandle targetPhoneAccountHandle, boolean isIncoming, boolean isConference)347     public Call(
348             Context context,
349             CallsManager callsManager,
350             TelecomSystem.SyncRoot lock,
351             ConnectionServiceRepository repository,
352             ContactsAsyncHelper contactsAsyncHelper,
353             CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
354             Uri handle,
355             GatewayInfo gatewayInfo,
356             PhoneAccountHandle connectionManagerPhoneAccountHandle,
357             PhoneAccountHandle targetPhoneAccountHandle,
358             boolean isIncoming,
359             boolean isConference) {
360         mState = isConference ? CallState.ACTIVE : CallState.NEW;
361         mContext = context;
362         mCallsManager = callsManager;
363         mLock = lock;
364         mRepository = repository;
365         mContactsAsyncHelper = contactsAsyncHelper;
366         mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
367         setHandle(handle);
368         mGatewayInfo = gatewayInfo;
369         setConnectionManagerPhoneAccount(connectionManagerPhoneAccountHandle);
370         setTargetPhoneAccount(targetPhoneAccountHandle);
371         mIsIncoming = isIncoming;
372         mIsConference = isConference;
373         maybeLoadCannedSmsResponses();
374 
375         Log.event(this, Log.Events.CREATED);
376     }
377 
378     /**
379      * Persists the specified parameters and initializes the new instance.
380      *
381      * @param context The context.
382      * @param repository The connection service repository.
383      * @param handle The handle to dial.
384      * @param gatewayInfo Gateway information to use for the call.
385      * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
386      *         This account must be one that was registered with the
387      *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
388      * @param targetPhoneAccountHandle Account information to use for the call. This account must be
389      *         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
390      * @param isIncoming True if this is an incoming call.
391      * @param connectTimeMillis The connection time of the call.
392      */
Call( Context context, CallsManager callsManager, TelecomSystem.SyncRoot lock, ConnectionServiceRepository repository, ContactsAsyncHelper contactsAsyncHelper, CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory, Uri handle, GatewayInfo gatewayInfo, PhoneAccountHandle connectionManagerPhoneAccountHandle, PhoneAccountHandle targetPhoneAccountHandle, boolean isIncoming, boolean isConference, long connectTimeMillis)393     Call(
394             Context context,
395             CallsManager callsManager,
396             TelecomSystem.SyncRoot lock,
397             ConnectionServiceRepository repository,
398             ContactsAsyncHelper contactsAsyncHelper,
399             CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
400             Uri handle,
401             GatewayInfo gatewayInfo,
402             PhoneAccountHandle connectionManagerPhoneAccountHandle,
403             PhoneAccountHandle targetPhoneAccountHandle,
404             boolean isIncoming,
405             boolean isConference,
406             long connectTimeMillis) {
407         this(context, callsManager, lock, repository, contactsAsyncHelper,
408                 callerInfoAsyncQueryFactory, handle, gatewayInfo,
409                 connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, isIncoming,
410                 isConference);
411 
412         mConnectTimeMillis = connectTimeMillis;
413     }
414 
addListener(Listener listener)415     public void addListener(Listener listener) {
416         mListeners.add(listener);
417     }
418 
removeListener(Listener listener)419     public void removeListener(Listener listener) {
420         if (listener != null) {
421             mListeners.remove(listener);
422         }
423     }
424 
destroy()425     public void destroy() {
426         Log.event(this, Log.Events.DESTROYED);
427     }
428 
429     /** {@inheritDoc} */
430     @Override
toString()431     public String toString() {
432         String component = null;
433         if (mConnectionService != null && mConnectionService.getComponentName() != null) {
434             component = mConnectionService.getComponentName().flattenToShortString();
435         }
436 
437 
438 
439         return String.format(Locale.US, "[%s, %s, %s, %s, %s, childs(%d), has_parent(%b), [%s]]",
440                 System.identityHashCode(this),
441                 CallState.toString(mState),
442                 component,
443                 Log.piiHandle(mHandle),
444                 getVideoStateDescription(getVideoState()),
445                 getChildCalls().size(),
446                 getParentCall() != null,
447                 Connection.capabilitiesToString(getConnectionCapabilities()));
448     }
449 
450     /**
451      * Builds a debug-friendly description string for a video state.
452      * <p>
453      * A = audio active, T = video transmission active, R = video reception active, P = video
454      * paused.
455      *
456      * @param videoState The video state.
457      * @return A string indicating which bits are set in the video state.
458      */
getVideoStateDescription(int videoState)459     private String getVideoStateDescription(int videoState) {
460         StringBuilder sb = new StringBuilder();
461         sb.append("A");
462 
463         if (VideoProfile.isTransmissionEnabled(videoState)) {
464             sb.append("T");
465         }
466 
467         if (VideoProfile.isReceptionEnabled(videoState)) {
468             sb.append("R");
469         }
470 
471         if (VideoProfile.isPaused(videoState)) {
472             sb.append("P");
473         }
474 
475         return sb.toString();
476     }
477 
getState()478     int getState() {
479         return mState;
480     }
481 
shouldContinueProcessingAfterDisconnect()482     private boolean shouldContinueProcessingAfterDisconnect() {
483         // Stop processing once the call is active.
484         if (!CreateConnectionTimeout.isCallBeingPlaced(this)) {
485             return false;
486         }
487 
488         // Make sure that there are additional connection services to process.
489         if (mCreateConnectionProcessor == null
490             || !mCreateConnectionProcessor.isProcessingComplete()
491             || !mCreateConnectionProcessor.hasMorePhoneAccounts()) {
492             return false;
493         }
494 
495         if (mDisconnectCause == null) {
496             return false;
497         }
498 
499         // Continue processing if the current attempt failed or timed out.
500         return mDisconnectCause.getCode() == DisconnectCause.ERROR ||
501             mCreateConnectionProcessor.isCallTimedOut();
502     }
503 
504     /**
505      * Sets the call state. Although there exists the notion of appropriate state transitions
506      * (see {@link CallState}), in practice those expectations break down when cellular systems
507      * misbehave and they do this very often. The result is that we do not enforce state transitions
508      * and instead keep the code resilient to unexpected state changes.
509      */
setState(int newState, String tag)510     public void setState(int newState, String tag) {
511         if (mState != newState) {
512             Log.v(this, "setState %s -> %s", mState, newState);
513 
514             if (newState == CallState.DISCONNECTED && shouldContinueProcessingAfterDisconnect()) {
515                 Log.w(this, "continuing processing disconnected call with another service");
516                 mCreateConnectionProcessor.continueProcessingIfPossible(this, mDisconnectCause);
517                 return;
518             }
519 
520             mState = newState;
521             maybeLoadCannedSmsResponses();
522 
523             if (mState == CallState.ACTIVE || mState == CallState.ON_HOLD) {
524                 if (mConnectTimeMillis == 0) {
525                     // We check to see if mConnectTime is already set to prevent the
526                     // call from resetting active time when it goes in and out of
527                     // ACTIVE/ON_HOLD
528                     mConnectTimeMillis = System.currentTimeMillis();
529                 }
530 
531                 // Video state changes are normally tracked against history when a call is active.
532                 // When the call goes active we need to be sure we track the history in case the
533                 // state never changes during the duration of the call -- we want to ensure we
534                 // always know the state at the start of the call.
535                 mVideoStateHistory = mVideoStateHistory | mVideoState;
536 
537                 // We're clearly not disconnected, so reset the disconnected time.
538                 mDisconnectTimeMillis = 0;
539             } else if (mState == CallState.DISCONNECTED) {
540                 mDisconnectTimeMillis = System.currentTimeMillis();
541                 setLocallyDisconnecting(false);
542                 fixParentAfterDisconnect();
543             }
544             if (mState == CallState.DISCONNECTED &&
545                     mDisconnectCause.getCode() == DisconnectCause.MISSED) {
546                 // Ensure when an incoming call is missed that the video state history is updated.
547                 mVideoStateHistory |= mVideoState;
548             }
549 
550             // Log the state transition event
551             String event = null;
552             Object data = null;
553             switch (newState) {
554                 case CallState.ACTIVE:
555                     event = Log.Events.SET_ACTIVE;
556                     break;
557                 case CallState.CONNECTING:
558                     event = Log.Events.SET_CONNECTING;
559                     break;
560                 case CallState.DIALING:
561                     event = Log.Events.SET_DIALING;
562                     break;
563                 case CallState.DISCONNECTED:
564                     event = Log.Events.SET_DISCONNECTED;
565                     data = getDisconnectCause();
566                     break;
567                 case CallState.DISCONNECTING:
568                     event = Log.Events.SET_DISCONNECTING;
569                     break;
570                 case CallState.ON_HOLD:
571                     event = Log.Events.SET_HOLD;
572                     break;
573                 case CallState.SELECT_PHONE_ACCOUNT:
574                     event = Log.Events.SET_SELECT_PHONE_ACCOUNT;
575                     break;
576                 case CallState.RINGING:
577                     event = Log.Events.SET_RINGING;
578                     break;
579             }
580             if (event != null) {
581                 // The string data should be just the tag.
582                 String stringData = tag;
583                 if (data != null) {
584                     // If data exists, add it to tag.  If no tag, just use data.toString().
585                     stringData = stringData == null ? data.toString() : stringData + "> " + data;
586                 }
587                 Log.event(this, event, stringData);
588             }
589         }
590     }
591 
setRingbackRequested(boolean ringbackRequested)592     void setRingbackRequested(boolean ringbackRequested) {
593         mRingbackRequested = ringbackRequested;
594         for (Listener l : mListeners) {
595             l.onRingbackRequested(this, mRingbackRequested);
596         }
597     }
598 
isRingbackRequested()599     boolean isRingbackRequested() {
600         return mRingbackRequested;
601     }
602 
isConference()603     boolean isConference() {
604         return mIsConference;
605     }
606 
getHandle()607     public Uri getHandle() {
608         return mHandle;
609     }
610 
getHandlePresentation()611     int getHandlePresentation() {
612         return mHandlePresentation;
613     }
614 
615 
setHandle(Uri handle)616     void setHandle(Uri handle) {
617         setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
618     }
619 
setHandle(Uri handle, int presentation)620     public void setHandle(Uri handle, int presentation) {
621         if (!Objects.equals(handle, mHandle) || presentation != mHandlePresentation) {
622             mHandlePresentation = presentation;
623             if (mHandlePresentation == TelecomManager.PRESENTATION_RESTRICTED ||
624                     mHandlePresentation == TelecomManager.PRESENTATION_UNKNOWN) {
625                 mHandle = null;
626             } else {
627                 mHandle = handle;
628                 if (mHandle != null && !PhoneAccount.SCHEME_VOICEMAIL.equals(mHandle.getScheme())
629                         && TextUtils.isEmpty(mHandle.getSchemeSpecificPart())) {
630                     // If the number is actually empty, set it to null, unless this is a
631                     // SCHEME_VOICEMAIL uri which always has an empty number.
632                     mHandle = null;
633                 }
634             }
635 
636             // Let's not allow resetting of the emergency flag. Once a call becomes an emergency
637             // call, it will remain so for the rest of it's lifetime.
638             if (!mIsEmergencyCall) {
639                 mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(
640                         mContext, mHandle.getSchemeSpecificPart());
641             }
642             startCallerInfoLookup();
643             for (Listener l : mListeners) {
644                 l.onHandleChanged(this);
645             }
646         }
647     }
648 
getCallerDisplayName()649     String getCallerDisplayName() {
650         return mCallerDisplayName;
651     }
652 
getCallerDisplayNamePresentation()653     int getCallerDisplayNamePresentation() {
654         return mCallerDisplayNamePresentation;
655     }
656 
setCallerDisplayName(String callerDisplayName, int presentation)657     void setCallerDisplayName(String callerDisplayName, int presentation) {
658         if (!TextUtils.equals(callerDisplayName, mCallerDisplayName) ||
659                 presentation != mCallerDisplayNamePresentation) {
660             mCallerDisplayName = callerDisplayName;
661             mCallerDisplayNamePresentation = presentation;
662             for (Listener l : mListeners) {
663                 l.onCallerDisplayNameChanged(this);
664             }
665         }
666     }
667 
getName()668     public String getName() {
669         return mCallerInfo == null ? null : mCallerInfo.name;
670     }
671 
getPhotoIcon()672     public Bitmap getPhotoIcon() {
673         return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
674     }
675 
getPhoto()676     public Drawable getPhoto() {
677         return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
678     }
679 
680     /**
681      * @param disconnectCause The reason for the disconnection, represented by
682      *         {@link android.telecom.DisconnectCause}.
683      */
setDisconnectCause(DisconnectCause disconnectCause)684     public void setDisconnectCause(DisconnectCause disconnectCause) {
685         // TODO: Consider combining this method with a setDisconnected() method that is totally
686         // separate from setState.
687         mDisconnectCause = disconnectCause;
688     }
689 
getDisconnectCause()690     public DisconnectCause getDisconnectCause() {
691         return mDisconnectCause;
692     }
693 
isEmergencyCall()694     boolean isEmergencyCall() {
695         return mIsEmergencyCall;
696     }
697 
698     /**
699      * @return The original handle this call is associated with. In-call services should use this
700      * handle when indicating in their UI the handle that is being called.
701      */
getOriginalHandle()702     public Uri getOriginalHandle() {
703         if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
704             return mGatewayInfo.getOriginalAddress();
705         }
706         return getHandle();
707     }
708 
getGatewayInfo()709     GatewayInfo getGatewayInfo() {
710         return mGatewayInfo;
711     }
712 
setGatewayInfo(GatewayInfo gatewayInfo)713     void setGatewayInfo(GatewayInfo gatewayInfo) {
714         mGatewayInfo = gatewayInfo;
715     }
716 
getConnectionManagerPhoneAccount()717     PhoneAccountHandle getConnectionManagerPhoneAccount() {
718         return mConnectionManagerPhoneAccountHandle;
719     }
720 
setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle)721     void setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle) {
722         if (!Objects.equals(mConnectionManagerPhoneAccountHandle, accountHandle)) {
723             mConnectionManagerPhoneAccountHandle = accountHandle;
724             for (Listener l : mListeners) {
725                 l.onConnectionManagerPhoneAccountChanged(this);
726             }
727         }
728 
729     }
730 
getTargetPhoneAccount()731     PhoneAccountHandle getTargetPhoneAccount() {
732         return mTargetPhoneAccountHandle;
733     }
734 
setTargetPhoneAccount(PhoneAccountHandle accountHandle)735     void setTargetPhoneAccount(PhoneAccountHandle accountHandle) {
736         if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) {
737             mTargetPhoneAccountHandle = accountHandle;
738             for (Listener l : mListeners) {
739                 l.onTargetPhoneAccountChanged(this);
740             }
741         }
742     }
743 
isIncoming()744     boolean isIncoming() {
745         return mIsIncoming;
746     }
747 
748     /**
749      * @return The "age" of this call object in milliseconds, which typically also represents the
750      *     period since this call was added to the set pending outgoing calls, see
751      *     mCreationTimeMillis.
752      */
getAgeMillis()753     long getAgeMillis() {
754         if (mState == CallState.DISCONNECTED &&
755                 (mDisconnectCause.getCode() == DisconnectCause.REJECTED ||
756                  mDisconnectCause.getCode() == DisconnectCause.MISSED)) {
757             // Rejected and missed calls have no age. They're immortal!!
758             return 0;
759         } else if (mConnectTimeMillis == 0) {
760             // Age is measured in the amount of time the call was active. A zero connect time
761             // indicates that we never went active, so return 0 for the age.
762             return 0;
763         } else if (mDisconnectTimeMillis == 0) {
764             // We connected, but have not yet disconnected
765             return System.currentTimeMillis() - mConnectTimeMillis;
766         }
767 
768         return mDisconnectTimeMillis - mConnectTimeMillis;
769     }
770 
771     /**
772      * @return The time when this call object was created and added to the set of pending outgoing
773      *     calls.
774      */
getCreationTimeMillis()775     public long getCreationTimeMillis() {
776         return mCreationTimeMillis;
777     }
778 
setCreationTimeMillis(long time)779     public void setCreationTimeMillis(long time) {
780         mCreationTimeMillis = time;
781     }
782 
getConnectTimeMillis()783     long getConnectTimeMillis() {
784         return mConnectTimeMillis;
785     }
786 
getConnectionCapabilities()787     int getConnectionCapabilities() {
788         return mConnectionCapabilities;
789     }
790 
setConnectionCapabilities(int connectionCapabilities)791     void setConnectionCapabilities(int connectionCapabilities) {
792         setConnectionCapabilities(connectionCapabilities, false /* forceUpdate */);
793     }
794 
setConnectionCapabilities(int connectionCapabilities, boolean forceUpdate)795     void setConnectionCapabilities(int connectionCapabilities, boolean forceUpdate) {
796         Log.v(this, "setConnectionCapabilities: %s", Connection.capabilitiesToString(
797                 connectionCapabilities));
798         if (forceUpdate || mConnectionCapabilities != connectionCapabilities) {
799            mConnectionCapabilities = connectionCapabilities;
800             for (Listener l : mListeners) {
801                 l.onConnectionCapabilitiesChanged(this);
802             }
803         }
804     }
805 
getParentCall()806     Call getParentCall() {
807         return mParentCall;
808     }
809 
getChildCalls()810     List<Call> getChildCalls() {
811         return mChildCalls;
812     }
813 
wasConferencePreviouslyMerged()814     boolean wasConferencePreviouslyMerged() {
815         return mWasConferencePreviouslyMerged;
816     }
817 
getConferenceLevelActiveCall()818     Call getConferenceLevelActiveCall() {
819         return mConferenceLevelActiveCall;
820     }
821 
getConnectionService()822     ConnectionServiceWrapper getConnectionService() {
823         return mConnectionService;
824     }
825 
826     /**
827      * Retrieves the {@link Context} for the call.
828      *
829      * @return The {@link Context}.
830      */
getContext()831     Context getContext() {
832         return mContext;
833     }
834 
setConnectionService(ConnectionServiceWrapper service)835     void setConnectionService(ConnectionServiceWrapper service) {
836         Preconditions.checkNotNull(service);
837 
838         clearConnectionService();
839 
840         service.incrementAssociatedCallCount();
841         mConnectionService = service;
842         mConnectionService.addCall(this);
843     }
844 
845     /**
846      * Clears the associated connection service.
847      */
clearConnectionService()848     void clearConnectionService() {
849         if (mConnectionService != null) {
850             ConnectionServiceWrapper serviceTemp = mConnectionService;
851             mConnectionService = null;
852             serviceTemp.removeCall(this);
853 
854             // Decrementing the count can cause the service to unbind, which itself can trigger the
855             // service-death code.  Since the service death code tries to clean up any associated
856             // calls, we need to make sure to remove that information (e.g., removeCall()) before
857             // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
858             // necessary, but cleaning up mConnectionService prior to triggering an unbind is good
859             // to do.
860             decrementAssociatedCallCount(serviceTemp);
861         }
862     }
863 
processDirectToVoicemail()864     private void processDirectToVoicemail() {
865         if (mDirectToVoicemailQueryPending) {
866             if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
867                 Log.i(this, "Directing call to voicemail: %s.", this);
868                 // TODO: Once we move State handling from CallsManager to Call, we
869                 // will not need to set STATE_RINGING state prior to calling reject.
870                 setState(CallState.RINGING, "directing to voicemail");
871                 reject(false, null);
872             } else {
873                 // TODO: Make this class (not CallsManager) responsible for changing
874                 // the call state to STATE_RINGING.
875 
876                 // TODO: Replace this with state transition to STATE_RINGING.
877                 for (Listener l : mListeners) {
878                     l.onSuccessfulIncomingCall(this);
879                 }
880             }
881 
882             mDirectToVoicemailQueryPending = false;
883         }
884     }
885 
886     /**
887      * Starts the create connection sequence. Upon completion, there should exist an active
888      * connection through a connection service (or the call will have failed).
889      *
890      * @param phoneAccountRegistrar The phone account registrar.
891      */
startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar)892     void startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar) {
893         Preconditions.checkState(mCreateConnectionProcessor == null);
894         mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this,
895                 phoneAccountRegistrar, mContext);
896         mCreateConnectionProcessor.process();
897     }
898 
899     @Override
handleCreateConnectionSuccess( CallIdMapper idMapper, ParcelableConnection connection)900     public void handleCreateConnectionSuccess(
901             CallIdMapper idMapper,
902             ParcelableConnection connection) {
903         Log.v(this, "handleCreateConnectionSuccessful %s", connection);
904         setTargetPhoneAccount(connection.getPhoneAccount());
905         setHandle(connection.getHandle(), connection.getHandlePresentation());
906         setCallerDisplayName(
907                 connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());
908         setConnectionCapabilities(connection.getConnectionCapabilities());
909         setVideoProvider(connection.getVideoProvider());
910         setVideoState(connection.getVideoState());
911         setRingbackRequested(connection.isRingbackRequested());
912         setIsVoipAudioMode(connection.getIsVoipAudioMode());
913         setStatusHints(connection.getStatusHints());
914         setExtras(connection.getExtras());
915 
916         mConferenceableCalls.clear();
917         for (String id : connection.getConferenceableConnectionIds()) {
918             mConferenceableCalls.add(idMapper.getCall(id));
919         }
920 
921         if (mIsUnknown) {
922             for (Listener l : mListeners) {
923                 l.onSuccessfulUnknownCall(this, getStateFromConnectionState(connection.getState()));
924             }
925         } else if (mIsIncoming) {
926             // We do not handle incoming calls immediately when they are verified by the connection
927             // service. We allow the caller-info-query code to execute first so that we can read the
928             // direct-to-voicemail property before deciding if we want to show the incoming call to
929             // the user or if we want to reject the call.
930             mDirectToVoicemailQueryPending = true;
931 
932             // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
933             // showing the user the incoming call screen.
934             mHandler.postDelayed(mDirectToVoicemailRunnable, Timeouts.getDirectToVoicemailMillis(
935                     mContext.getContentResolver()));
936         } else {
937             for (Listener l : mListeners) {
938                 l.onSuccessfulOutgoingCall(this,
939                         getStateFromConnectionState(connection.getState()));
940             }
941         }
942     }
943 
944     @Override
handleCreateConnectionFailure(DisconnectCause disconnectCause)945     public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
946         clearConnectionService();
947         setDisconnectCause(disconnectCause);
948         mCallsManager.markCallAsDisconnected(this, disconnectCause);
949 
950         if (mIsUnknown) {
951             for (Listener listener : mListeners) {
952                 listener.onFailedUnknownCall(this);
953             }
954         } else if (mIsIncoming) {
955             for (Listener listener : mListeners) {
956                 listener.onFailedIncomingCall(this);
957             }
958         } else {
959             for (Listener listener : mListeners) {
960                 listener.onFailedOutgoingCall(this, disconnectCause);
961             }
962         }
963     }
964 
965     /**
966      * Plays the specified DTMF tone.
967      */
playDtmfTone(char digit)968     void playDtmfTone(char digit) {
969         if (mConnectionService == null) {
970             Log.w(this, "playDtmfTone() request on a call without a connection service.");
971         } else {
972             Log.i(this, "Send playDtmfTone to connection service for call %s", this);
973             mConnectionService.playDtmfTone(this, digit);
974             Log.event(this, Log.Events.START_DTMF, Log.pii(digit));
975         }
976     }
977 
978     /**
979      * Stops playing any currently playing DTMF tone.
980      */
stopDtmfTone()981     void stopDtmfTone() {
982         if (mConnectionService == null) {
983             Log.w(this, "stopDtmfTone() request on a call without a connection service.");
984         } else {
985             Log.i(this, "Send stopDtmfTone to connection service for call %s", this);
986             Log.event(this, Log.Events.STOP_DTMF);
987             mConnectionService.stopDtmfTone(this);
988         }
989     }
990 
disconnect()991     void disconnect() {
992         disconnect(false);
993     }
994 
995     /**
996      * Attempts to disconnect the call through the connection service.
997      */
disconnect(boolean wasViaNewOutgoingCallBroadcaster)998     void disconnect(boolean wasViaNewOutgoingCallBroadcaster) {
999         Log.event(this, Log.Events.REQUEST_DISCONNECT);
1000 
1001         // Track that the call is now locally disconnecting.
1002         setLocallyDisconnecting(true);
1003 
1004         if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT ||
1005                 mState == CallState.CONNECTING) {
1006             Log.v(this, "Aborting call %s", this);
1007             abort(wasViaNewOutgoingCallBroadcaster);
1008         } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
1009             if (mConnectionService == null) {
1010                 Log.e(this, new Exception(), "disconnect() request on a call without a"
1011                         + " connection service.");
1012             } else {
1013                 Log.i(this, "Send disconnect to connection service for call: %s", this);
1014                 // The call isn't officially disconnected until the connection service
1015                 // confirms that the call was actually disconnected. Only then is the
1016                 // association between call and connection service severed, see
1017                 // {@link CallsManager#markCallAsDisconnected}.
1018                 mConnectionService.disconnect(this);
1019             }
1020         }
1021     }
1022 
abort(boolean wasViaNewOutgoingCallBroadcaster)1023     void abort(boolean wasViaNewOutgoingCallBroadcaster) {
1024         if (mCreateConnectionProcessor != null &&
1025                 !mCreateConnectionProcessor.isProcessingComplete()) {
1026             mCreateConnectionProcessor.abort();
1027         } else if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT
1028                 || mState == CallState.CONNECTING) {
1029             if (wasViaNewOutgoingCallBroadcaster) {
1030                 // If the cancelation was from NEW_OUTGOING_CALL, then we do not automatically
1031                 // destroy the call.  Instead, we announce the cancelation and CallsManager handles
1032                 // it through a timer. Since apps often cancel calls through NEW_OUTGOING_CALL and
1033                 // then re-dial them quickly using a gateway, allowing the first call to end
1034                 // causes jank. This timeout allows CallsManager to transition the first call into
1035                 // the second call so that in-call only ever sees a single call...eliminating the
1036                 // jank altogether.
1037                 for (Listener listener : mListeners) {
1038                     if (listener.onCanceledViaNewOutgoingCallBroadcast(this)) {
1039                         // The first listener to handle this wins. A return value of true means that
1040                         // the listener will handle the disconnection process later and so we
1041                         // should not continue it here.
1042                         setLocallyDisconnecting(false);
1043                         return;
1044                     }
1045                 }
1046             }
1047 
1048             handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
1049         } else {
1050             Log.v(this, "Cannot abort a call which is neither SELECT_PHONE_ACCOUNT or CONNECTING");
1051         }
1052     }
1053 
1054     /**
1055      * Answers the call if it is ringing.
1056      *
1057      * @param videoState The video state in which to answer the call.
1058      */
answer(int videoState)1059     void answer(int videoState) {
1060         Preconditions.checkNotNull(mConnectionService);
1061 
1062         // Check to verify that the call is still in the ringing state. A call can change states
1063         // between the time the user hits 'answer' and Telecom receives the command.
1064         if (isRinging("answer")) {
1065             // At this point, we are asking the connection service to answer but we don't assume
1066             // that it will work. Instead, we wait until confirmation from the connectino service
1067             // that the call is in a non-STATE_RINGING state before changing the UI. See
1068             // {@link ConnectionServiceAdapter#setActive} and other set* methods.
1069             mConnectionService.answer(this, videoState);
1070             Log.event(this, Log.Events.REQUEST_ACCEPT);
1071         }
1072     }
1073 
1074     /**
1075      * Rejects the call if it is ringing.
1076      *
1077      * @param rejectWithMessage Whether to send a text message as part of the call rejection.
1078      * @param textMessage An optional text message to send as part of the rejection.
1079      */
reject(boolean rejectWithMessage, String textMessage)1080     void reject(boolean rejectWithMessage, String textMessage) {
1081         Preconditions.checkNotNull(mConnectionService);
1082 
1083         // Check to verify that the call is still in the ringing state. A call can change states
1084         // between the time the user hits 'reject' and Telecomm receives the command.
1085         if (isRinging("reject")) {
1086             // Ensure video state history tracks video state at time of rejection.
1087             mVideoStateHistory |= mVideoState;
1088 
1089             mConnectionService.reject(this);
1090             Log.event(this, Log.Events.REQUEST_REJECT);
1091         }
1092     }
1093 
1094     /**
1095      * Puts the call on hold if it is currently active.
1096      */
hold()1097     void hold() {
1098         Preconditions.checkNotNull(mConnectionService);
1099 
1100         if (mState == CallState.ACTIVE) {
1101             mConnectionService.hold(this);
1102             Log.event(this, Log.Events.REQUEST_HOLD);
1103         }
1104     }
1105 
1106     /**
1107      * Releases the call from hold if it is currently active.
1108      */
unhold()1109     void unhold() {
1110         Preconditions.checkNotNull(mConnectionService);
1111 
1112         if (mState == CallState.ON_HOLD) {
1113             mConnectionService.unhold(this);
1114             Log.event(this, Log.Events.REQUEST_UNHOLD);
1115         }
1116     }
1117 
1118     /** Checks if this is a live call or not. */
isAlive()1119     boolean isAlive() {
1120         switch (mState) {
1121             case CallState.NEW:
1122             case CallState.RINGING:
1123             case CallState.DISCONNECTED:
1124             case CallState.ABORTED:
1125                 return false;
1126             default:
1127                 return true;
1128         }
1129     }
1130 
isActive()1131     boolean isActive() {
1132         return mState == CallState.ACTIVE;
1133     }
1134 
getExtras()1135     Bundle getExtras() {
1136         return mExtras;
1137     }
1138 
setExtras(Bundle extras)1139     void setExtras(Bundle extras) {
1140         mExtras = extras;
1141         for (Listener l : mListeners) {
1142             l.onExtrasChanged(this);
1143         }
1144     }
1145 
getIntentExtras()1146     Bundle getIntentExtras() {
1147         return mIntentExtras;
1148     }
1149 
setIntentExtras(Bundle extras)1150     void setIntentExtras(Bundle extras) {
1151         mIntentExtras = extras;
1152     }
1153 
1154     /**
1155      * @return the uri of the contact associated with this call.
1156      */
getContactUri()1157     Uri getContactUri() {
1158         if (mCallerInfo == null || !mCallerInfo.contactExists) {
1159             return getHandle();
1160         }
1161         return Contacts.getLookupUri(mCallerInfo.contactIdOrZero, mCallerInfo.lookupKey);
1162     }
1163 
getRingtone()1164     Uri getRingtone() {
1165         return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
1166     }
1167 
onPostDialWait(String remaining)1168     void onPostDialWait(String remaining) {
1169         for (Listener l : mListeners) {
1170             l.onPostDialWait(this, remaining);
1171         }
1172     }
1173 
onPostDialChar(char nextChar)1174     void onPostDialChar(char nextChar) {
1175         for (Listener l : mListeners) {
1176             l.onPostDialChar(this, nextChar);
1177         }
1178     }
1179 
postDialContinue(boolean proceed)1180     void postDialContinue(boolean proceed) {
1181         mConnectionService.onPostDialContinue(this, proceed);
1182     }
1183 
conferenceWith(Call otherCall)1184     void conferenceWith(Call otherCall) {
1185         if (mConnectionService == null) {
1186             Log.w(this, "conference requested on a call without a connection service.");
1187         } else {
1188             Log.event(this, Log.Events.CONFERENCE_WITH, otherCall);
1189             mConnectionService.conference(this, otherCall);
1190         }
1191     }
1192 
splitFromConference()1193     void splitFromConference() {
1194         if (mConnectionService == null) {
1195             Log.w(this, "splitting from conference call without a connection service");
1196         } else {
1197             Log.event(this, Log.Events.SPLIT_CONFERENCE);
1198             mConnectionService.splitFromConference(this);
1199         }
1200     }
1201 
mergeConference()1202     void mergeConference() {
1203         if (mConnectionService == null) {
1204             Log.w(this, "merging conference calls without a connection service.");
1205         } else if (can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
1206             Log.event(this, Log.Events.CONFERENCE_WITH);
1207             mConnectionService.mergeConference(this);
1208             mWasConferencePreviouslyMerged = true;
1209         }
1210     }
1211 
swapConference()1212     void swapConference() {
1213         if (mConnectionService == null) {
1214             Log.w(this, "swapping conference calls without a connection service.");
1215         } else if (can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
1216             Log.event(this, Log.Events.SWAP);
1217             mConnectionService.swapConference(this);
1218             switch (mChildCalls.size()) {
1219                 case 1:
1220                     mConferenceLevelActiveCall = mChildCalls.get(0);
1221                     break;
1222                 case 2:
1223                     // swap
1224                     mConferenceLevelActiveCall = mChildCalls.get(0) == mConferenceLevelActiveCall ?
1225                             mChildCalls.get(1) : mChildCalls.get(0);
1226                     break;
1227                 default:
1228                     // For anything else 0, or 3+, set it to null since it is impossible to tell.
1229                     mConferenceLevelActiveCall = null;
1230                     break;
1231             }
1232         }
1233     }
1234 
setParentCall(Call parentCall)1235     void setParentCall(Call parentCall) {
1236         if (parentCall == this) {
1237             Log.e(this, new Exception(), "setting the parent to self");
1238             return;
1239         }
1240         if (parentCall == mParentCall) {
1241             // nothing to do
1242             return;
1243         }
1244         Preconditions.checkState(parentCall == null || mParentCall == null);
1245 
1246         Call oldParent = mParentCall;
1247         if (mParentCall != null) {
1248             mParentCall.removeChildCall(this);
1249         }
1250         mParentCall = parentCall;
1251         if (mParentCall != null) {
1252             mParentCall.addChildCall(this);
1253         }
1254 
1255         Log.event(this, Log.Events.SET_PARENT, mParentCall);
1256         for (Listener l : mListeners) {
1257             l.onParentChanged(this);
1258         }
1259     }
1260 
setConferenceableCalls(List<Call> conferenceableCalls)1261     void setConferenceableCalls(List<Call> conferenceableCalls) {
1262         mConferenceableCalls.clear();
1263         mConferenceableCalls.addAll(conferenceableCalls);
1264 
1265         for (Listener l : mListeners) {
1266             l.onConferenceableCallsChanged(this);
1267         }
1268     }
1269 
getConferenceableCalls()1270     List<Call> getConferenceableCalls() {
1271         return mConferenceableCalls;
1272     }
1273 
can(int capability)1274     boolean can(int capability) {
1275         return (mConnectionCapabilities & capability) == capability;
1276     }
1277 
addChildCall(Call call)1278     private void addChildCall(Call call) {
1279         if (!mChildCalls.contains(call)) {
1280             // Set the pseudo-active call to the latest child added to the conference.
1281             // See definition of mConferenceLevelActiveCall for more detail.
1282             mConferenceLevelActiveCall = call;
1283             mChildCalls.add(call);
1284 
1285             Log.event(this, Log.Events.ADD_CHILD, call);
1286 
1287             for (Listener l : mListeners) {
1288                 l.onChildrenChanged(this);
1289             }
1290         }
1291     }
1292 
removeChildCall(Call call)1293     private void removeChildCall(Call call) {
1294         if (mChildCalls.remove(call)) {
1295             Log.event(this, Log.Events.REMOVE_CHILD, call);
1296             for (Listener l : mListeners) {
1297                 l.onChildrenChanged(this);
1298             }
1299         }
1300     }
1301 
1302     /**
1303      * Return whether the user can respond to this {@code Call} via an SMS message.
1304      *
1305      * @return true if the "Respond via SMS" feature should be enabled
1306      * for this incoming call.
1307      *
1308      * The general rule is that we *do* allow "Respond via SMS" except for
1309      * the few (relatively rare) cases where we know for sure it won't
1310      * work, namely:
1311      *   - a bogus or blank incoming number
1312      *   - a call from a SIP address
1313      *   - a "call presentation" that doesn't allow the number to be revealed
1314      *
1315      * In all other cases, we allow the user to respond via SMS.
1316      *
1317      * Note that this behavior isn't perfect; for example we have no way
1318      * to detect whether the incoming call is from a landline (with most
1319      * networks at least), so we still enable this feature even though
1320      * SMSes to that number will silently fail.
1321      */
isRespondViaSmsCapable()1322     boolean isRespondViaSmsCapable() {
1323         if (mState != CallState.RINGING) {
1324             return false;
1325         }
1326 
1327         if (getHandle() == null) {
1328             // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in
1329             // other words, the user should not be able to see the incoming phone number.
1330             return false;
1331         }
1332 
1333         if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
1334             // The incoming number is actually a URI (i.e. a SIP address),
1335             // not a regular PSTN phone number, and we can't send SMSes to
1336             // SIP addresses.
1337             // (TODO: That might still be possible eventually, though. Is
1338             // there some SIP-specific equivalent to sending a text message?)
1339             return false;
1340         }
1341 
1342         // Is there a valid SMS application on the phone?
1343         if (SmsApplication.getDefaultRespondViaMessageApplication(mContext,
1344                 true /*updateIfNeeded*/) == null) {
1345             return false;
1346         }
1347 
1348         // TODO: with some carriers (in certain countries) you *can* actually
1349         // tell whether a given number is a mobile phone or not. So in that
1350         // case we could potentially return false here if the incoming call is
1351         // from a land line.
1352 
1353         // If none of the above special cases apply, it's OK to enable the
1354         // "Respond via SMS" feature.
1355         return true;
1356     }
1357 
getCannedSmsResponses()1358     List<String> getCannedSmsResponses() {
1359         return mCannedSmsResponses;
1360     }
1361 
1362     /**
1363      * We need to make sure that before we move a call to the disconnected state, it no
1364      * longer has any parent/child relationships.  We want to do this to ensure that the InCall
1365      * Service always has the right data in the right order.  We also want to do it in telecom so
1366      * that the insurance policy lives in the framework side of things.
1367      */
fixParentAfterDisconnect()1368     private void fixParentAfterDisconnect() {
1369         setParentCall(null);
1370     }
1371 
1372     /**
1373      * @return True if the call is ringing, else logs the action name.
1374      */
isRinging(String actionName)1375     private boolean isRinging(String actionName) {
1376         if (mState == CallState.RINGING) {
1377             return true;
1378         }
1379 
1380         Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
1381         return false;
1382     }
1383 
1384     @SuppressWarnings("rawtypes")
decrementAssociatedCallCount(ServiceBinder binder)1385     private void decrementAssociatedCallCount(ServiceBinder binder) {
1386         if (binder != null) {
1387             binder.decrementAssociatedCallCount();
1388         }
1389     }
1390 
1391     /**
1392      * Looks up contact information based on the current handle.
1393      */
startCallerInfoLookup()1394     private void startCallerInfoLookup() {
1395         final String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
1396 
1397         mQueryToken++;  // Updated so that previous queries can no longer set the information.
1398         mCallerInfo = null;
1399         if (!TextUtils.isEmpty(number)) {
1400             Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
1401             mHandler.post(new Runnable() {
1402                 @Override
1403                 public void run() {
1404                     mCallerInfoAsyncQueryFactory.startQuery(
1405                             mQueryToken,
1406                             mContext,
1407                             number,
1408                             mCallerInfoQueryListener,
1409                             Call.this);
1410                 }
1411             });
1412         }
1413     }
1414 
1415     /**
1416      * Saves the specified caller info if the specified token matches that of the last query
1417      * that was made.
1418      *
1419      * @param callerInfo The new caller information to set.
1420      * @param token The token used with this query.
1421      */
setCallerInfo(CallerInfo callerInfo, int token)1422     private void setCallerInfo(CallerInfo callerInfo, int token) {
1423         Trace.beginSection("setCallerInfo");
1424         Preconditions.checkNotNull(callerInfo);
1425 
1426         if (mQueryToken == token) {
1427             mCallerInfo = callerInfo;
1428             Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
1429 
1430             if (mCallerInfo.contactDisplayPhotoUri != null) {
1431                 Log.d(this, "Searching person uri %s for call %s",
1432                         mCallerInfo.contactDisplayPhotoUri, this);
1433                 mContactsAsyncHelper.startObtainPhotoAsync(
1434                         token,
1435                         mContext,
1436                         mCallerInfo.contactDisplayPhotoUri,
1437                         mPhotoLoadListener,
1438                         this);
1439                 // Do not call onCallerInfoChanged yet in this case.  We call it in setPhoto().
1440             } else {
1441                 for (Listener l : mListeners) {
1442                     l.onCallerInfoChanged(this);
1443                 }
1444             }
1445 
1446             processDirectToVoicemail();
1447         }
1448         Trace.endSection();
1449     }
1450 
getCallerInfo()1451     CallerInfo getCallerInfo() {
1452         return mCallerInfo;
1453     }
1454 
1455     /**
1456      * Saves the specified photo information if the specified token matches that of the last query.
1457      *
1458      * @param photo The photo as a drawable.
1459      * @param photoIcon The photo as a small icon.
1460      * @param token The token used with this query.
1461      */
setPhoto(Drawable photo, Bitmap photoIcon, int token)1462     private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
1463         if (mQueryToken == token) {
1464             mCallerInfo.cachedPhoto = photo;
1465             mCallerInfo.cachedPhotoIcon = photoIcon;
1466 
1467             for (Listener l : mListeners) {
1468                 l.onCallerInfoChanged(this);
1469             }
1470         }
1471     }
1472 
maybeLoadCannedSmsResponses()1473     private void maybeLoadCannedSmsResponses() {
1474         if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
1475             Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
1476             mCannedSmsResponsesLoadingStarted = true;
1477             mCallsManager.getRespondViaSmsManager().loadCannedTextMessages(
1478                     new Response<Void, List<String>>() {
1479                         @Override
1480                         public void onResult(Void request, List<String>... result) {
1481                             if (result.length > 0) {
1482                                 Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]);
1483                                 mCannedSmsResponses = result[0];
1484                                 for (Listener l : mListeners) {
1485                                     l.onCannedSmsResponsesLoaded(Call.this);
1486                                 }
1487                             }
1488                         }
1489 
1490                         @Override
1491                         public void onError(Void request, int code, String msg) {
1492                             Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code,
1493                                     msg);
1494                         }
1495                     },
1496                     mContext
1497             );
1498         } else {
1499             Log.d(this, "maybeLoadCannedSmsResponses: doing nothing");
1500         }
1501     }
1502 
1503     /**
1504      * Sets speakerphone option on when call begins.
1505      */
setStartWithSpeakerphoneOn(boolean startWithSpeakerphone)1506     public void setStartWithSpeakerphoneOn(boolean startWithSpeakerphone) {
1507         mSpeakerphoneOn = startWithSpeakerphone;
1508     }
1509 
1510     /**
1511      * Returns speakerphone option.
1512      *
1513      * @return Whether or not speakerphone should be set automatically when call begins.
1514      */
getStartWithSpeakerphoneOn()1515     public boolean getStartWithSpeakerphoneOn() {
1516         return mSpeakerphoneOn;
1517     }
1518 
1519     /**
1520      * Sets a video call provider for the call.
1521      */
setVideoProvider(IVideoProvider videoProvider)1522     public void setVideoProvider(IVideoProvider videoProvider) {
1523         Log.v(this, "setVideoProvider");
1524 
1525         if (videoProvider != null ) {
1526             try {
1527                 mVideoProviderProxy = new VideoProviderProxy(mLock, videoProvider, this);
1528             } catch (RemoteException ignored) {
1529                 // Ignore RemoteException.
1530             }
1531         } else {
1532             mVideoProviderProxy = null;
1533         }
1534 
1535         mVideoProvider = videoProvider;
1536 
1537         for (Listener l : mListeners) {
1538             l.onVideoCallProviderChanged(Call.this);
1539         }
1540     }
1541 
1542     /**
1543      * @return The {@link Connection.VideoProvider} binder.
1544      */
getVideoProvider()1545     public IVideoProvider getVideoProvider() {
1546         if (mVideoProviderProxy == null) {
1547             return null;
1548         }
1549 
1550         return mVideoProviderProxy.getInterface();
1551     }
1552 
1553     /**
1554      * @return The {@link VideoProviderProxy} for this call.
1555      */
getVideoProviderProxy()1556     public VideoProviderProxy getVideoProviderProxy() {
1557         return mVideoProviderProxy;
1558     }
1559 
1560     /**
1561      * The current video state for the call.
1562      * See {@link VideoProfile} for a list of valid video states.
1563      */
getVideoState()1564     public int getVideoState() {
1565         return mVideoState;
1566     }
1567 
1568     /**
1569      * Returns the video states which were applicable over the duration of a call.
1570      * See {@link VideoProfile} for a list of valid video states.
1571      *
1572      * @return The video states applicable over the duration of the call.
1573      */
getVideoStateHistory()1574     public int getVideoStateHistory() {
1575         return mVideoStateHistory;
1576     }
1577 
1578     /**
1579      * Determines the current video state for the call.
1580      * For an outgoing call determines the desired video state for the call.
1581      * Valid values: see {@link VideoProfile}
1582      *
1583      * @param videoState The video state for the call.
1584      */
setVideoState(int videoState)1585     public void setVideoState(int videoState) {
1586         // Track which video states were applicable over the duration of the call.
1587         // Only track the call state when the call is active or disconnected.  This ensures we do
1588         // not include the video state when:
1589         // - Call is incoming (but not answered).
1590         // - Call it outgoing (but not answered).
1591         // We include the video state when disconnected to ensure that rejected calls reflect the
1592         // appropriate video state.
1593         if (isActive() || getState() == CallState.DISCONNECTED) {
1594             mVideoStateHistory = mVideoStateHistory | videoState;
1595         }
1596 
1597         mVideoState = videoState;
1598         for (Listener l : mListeners) {
1599             l.onVideoStateChanged(this);
1600         }
1601     }
1602 
getIsVoipAudioMode()1603     public boolean getIsVoipAudioMode() {
1604         return mIsVoipAudioMode;
1605     }
1606 
setIsVoipAudioMode(boolean audioModeIsVoip)1607     public void setIsVoipAudioMode(boolean audioModeIsVoip) {
1608         mIsVoipAudioMode = audioModeIsVoip;
1609         for (Listener l : mListeners) {
1610             l.onIsVoipAudioModeChanged(this);
1611         }
1612     }
1613 
getStatusHints()1614     public StatusHints getStatusHints() {
1615         return mStatusHints;
1616     }
1617 
setStatusHints(StatusHints statusHints)1618     public void setStatusHints(StatusHints statusHints) {
1619         mStatusHints = statusHints;
1620         for (Listener l : mListeners) {
1621             l.onStatusHintsChanged(this);
1622         }
1623     }
1624 
isUnknown()1625     public boolean isUnknown() {
1626         return mIsUnknown;
1627     }
1628 
setIsUnknown(boolean isUnknown)1629     public void setIsUnknown(boolean isUnknown) {
1630         mIsUnknown = isUnknown;
1631     }
1632 
1633     /**
1634      * Determines if this call is in a disconnecting state.
1635      *
1636      * @return {@code true} if this call is locally disconnecting.
1637      */
isLocallyDisconnecting()1638     public boolean isLocallyDisconnecting() {
1639         return mIsLocallyDisconnecting;
1640     }
1641 
1642     /**
1643      * Sets whether this call is in a disconnecting state.
1644      *
1645      * @param isLocallyDisconnecting {@code true} if this call is locally disconnecting.
1646      */
setLocallyDisconnecting(boolean isLocallyDisconnecting)1647     private void setLocallyDisconnecting(boolean isLocallyDisconnecting) {
1648         mIsLocallyDisconnecting = isLocallyDisconnecting;
1649     }
1650 
getStateFromConnectionState(int state)1651     static int getStateFromConnectionState(int state) {
1652         switch (state) {
1653             case Connection.STATE_INITIALIZING:
1654                 return CallState.CONNECTING;
1655             case Connection.STATE_ACTIVE:
1656                 return CallState.ACTIVE;
1657             case Connection.STATE_DIALING:
1658                 return CallState.DIALING;
1659             case Connection.STATE_DISCONNECTED:
1660                 return CallState.DISCONNECTED;
1661             case Connection.STATE_HOLDING:
1662                 return CallState.ON_HOLD;
1663             case Connection.STATE_NEW:
1664                 return CallState.NEW;
1665             case Connection.STATE_RINGING:
1666                 return CallState.RINGING;
1667         }
1668         return CallState.DISCONNECTED;
1669     }
1670 
1671     /**
1672      * Determines if this call is in disconnected state and waiting to be destroyed.
1673      *
1674      * @return {@code true} if this call is disconected.
1675      */
isDisconnected()1676     public boolean isDisconnected() {
1677         return (getState() == CallState.DISCONNECTED || getState() == CallState.ABORTED);
1678     }
1679 }
1680