• 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.provider.ContactsContract.Contacts;
26 import android.telecom.CallState;
27 import android.telecom.DisconnectCause;
28 import android.telecom.Connection;
29 import android.telecom.GatewayInfo;
30 import android.telecom.ParcelableConnection;
31 import android.telecom.PhoneAccount;
32 import android.telecom.PhoneAccountHandle;
33 import android.telecom.PhoneCapabilities;
34 import android.telecom.Response;
35 import android.telecom.StatusHints;
36 import android.telecom.TelecomManager;
37 import android.telecom.VideoProfile;
38 import android.telephony.PhoneNumberUtils;
39 import android.text.TextUtils;
40 
41 import com.android.internal.telecom.IVideoProvider;
42 import com.android.internal.telephony.CallerInfo;
43 import com.android.internal.telephony.CallerInfoAsyncQuery;
44 import com.android.internal.telephony.CallerInfoAsyncQuery.OnQueryCompleteListener;
45 import com.android.internal.telephony.SmsApplication;
46 import com.android.server.telecom.ContactsAsyncHelper.OnImageLoadCompleteListener;
47 
48 import com.android.internal.util.Preconditions;
49 
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.LinkedList;
53 import java.util.List;
54 import java.util.Locale;
55 import java.util.Objects;
56 import java.util.Set;
57 import java.util.concurrent.ConcurrentHashMap;
58 
59 /**
60  *  Encapsulates all aspects of a given phone call throughout its lifecycle, starting
61  *  from the time the call intent was received by Telecom (vs. the time the call was
62  *  connected etc).
63  */
64 final class Call implements CreateConnectionResponse {
65     /**
66      * Listener for events on the call.
67      */
68     interface Listener {
onSuccessfulOutgoingCall(Call call, int callState)69         void onSuccessfulOutgoingCall(Call call, int callState);
onFailedOutgoingCall(Call call, DisconnectCause disconnectCause)70         void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause);
onSuccessfulIncomingCall(Call call)71         void onSuccessfulIncomingCall(Call call);
onFailedIncomingCall(Call call)72         void onFailedIncomingCall(Call call);
onSuccessfulUnknownCall(Call call, int callState)73         void onSuccessfulUnknownCall(Call call, int callState);
onFailedUnknownCall(Call call)74         void onFailedUnknownCall(Call call);
onRingbackRequested(Call call, boolean ringbackRequested)75         void onRingbackRequested(Call call, boolean ringbackRequested);
onPostDialWait(Call call, String remaining)76         void onPostDialWait(Call call, String remaining);
onCallCapabilitiesChanged(Call call)77         void onCallCapabilitiesChanged(Call call);
onParentChanged(Call call)78         void onParentChanged(Call call);
onChildrenChanged(Call call)79         void onChildrenChanged(Call call);
onCannedSmsResponsesLoaded(Call call)80         void onCannedSmsResponsesLoaded(Call call);
onVideoCallProviderChanged(Call call)81         void onVideoCallProviderChanged(Call call);
onCallerInfoChanged(Call call)82         void onCallerInfoChanged(Call call);
onIsVoipAudioModeChanged(Call call)83         void onIsVoipAudioModeChanged(Call call);
onStatusHintsChanged(Call call)84         void onStatusHintsChanged(Call call);
onHandleChanged(Call call)85         void onHandleChanged(Call call);
onCallerDisplayNameChanged(Call call)86         void onCallerDisplayNameChanged(Call call);
onVideoStateChanged(Call call)87         void onVideoStateChanged(Call call);
onTargetPhoneAccountChanged(Call call)88         void onTargetPhoneAccountChanged(Call call);
onConnectionManagerPhoneAccountChanged(Call call)89         void onConnectionManagerPhoneAccountChanged(Call call);
onPhoneAccountChanged(Call call)90         void onPhoneAccountChanged(Call call);
onConferenceableCallsChanged(Call call)91         void onConferenceableCallsChanged(Call call);
92     }
93 
94     abstract static class ListenerBase implements Listener {
95         @Override
onSuccessfulOutgoingCall(Call call, int callState)96         public void onSuccessfulOutgoingCall(Call call, int callState) {}
97         @Override
onFailedOutgoingCall(Call call, DisconnectCause disconnectCause)98         public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {}
99         @Override
onSuccessfulIncomingCall(Call call)100         public void onSuccessfulIncomingCall(Call call) {}
101         @Override
onFailedIncomingCall(Call call)102         public void onFailedIncomingCall(Call call) {}
103         @Override
onSuccessfulUnknownCall(Call call, int callState)104         public void onSuccessfulUnknownCall(Call call, int callState) {}
105         @Override
onFailedUnknownCall(Call call)106         public void onFailedUnknownCall(Call call) {}
107         @Override
onRingbackRequested(Call call, boolean ringbackRequested)108         public void onRingbackRequested(Call call, boolean ringbackRequested) {}
109         @Override
onPostDialWait(Call call, String remaining)110         public void onPostDialWait(Call call, String remaining) {}
111         @Override
onCallCapabilitiesChanged(Call call)112         public void onCallCapabilitiesChanged(Call call) {}
113         @Override
onParentChanged(Call call)114         public void onParentChanged(Call call) {}
115         @Override
onChildrenChanged(Call call)116         public void onChildrenChanged(Call call) {}
117         @Override
onCannedSmsResponsesLoaded(Call call)118         public void onCannedSmsResponsesLoaded(Call call) {}
119         @Override
onVideoCallProviderChanged(Call call)120         public void onVideoCallProviderChanged(Call call) {}
121         @Override
onCallerInfoChanged(Call call)122         public void onCallerInfoChanged(Call call) {}
123         @Override
onIsVoipAudioModeChanged(Call call)124         public void onIsVoipAudioModeChanged(Call call) {}
125         @Override
onStatusHintsChanged(Call call)126         public void onStatusHintsChanged(Call call) {}
127         @Override
onHandleChanged(Call call)128         public void onHandleChanged(Call call) {}
129         @Override
onCallerDisplayNameChanged(Call call)130         public void onCallerDisplayNameChanged(Call call) {}
131         @Override
onVideoStateChanged(Call call)132         public void onVideoStateChanged(Call call) {}
133         @Override
onTargetPhoneAccountChanged(Call call)134         public void onTargetPhoneAccountChanged(Call call) {}
135         @Override
onConnectionManagerPhoneAccountChanged(Call call)136         public void onConnectionManagerPhoneAccountChanged(Call call) {}
137         @Override
onPhoneAccountChanged(Call call)138         public void onPhoneAccountChanged(Call call) {}
139         @Override
onConferenceableCallsChanged(Call call)140         public void onConferenceableCallsChanged(Call call) {}
141     }
142 
143     private static final OnQueryCompleteListener sCallerInfoQueryListener =
144             new OnQueryCompleteListener() {
145                 /** ${inheritDoc} */
146                 @Override
147                 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
148                     if (cookie != null) {
149                         ((Call) cookie).setCallerInfo(callerInfo, token);
150                     }
151                 }
152             };
153 
154     private static final OnImageLoadCompleteListener sPhotoLoadListener =
155             new OnImageLoadCompleteListener() {
156                 /** ${inheritDoc} */
157                 @Override
158                 public void onImageLoadComplete(
159                         int token, Drawable photo, Bitmap photoIcon, Object cookie) {
160                     if (cookie != null) {
161                         ((Call) cookie).setPhoto(photo, photoIcon, token);
162                     }
163                 }
164             };
165 
166     private final Runnable mDirectToVoicemailRunnable = new Runnable() {
167         @Override
168         public void run() {
169             processDirectToVoicemail();
170         }
171     };
172 
173     /** True if this is an incoming call. */
174     private final boolean mIsIncoming;
175 
176     /** True if this is a currently unknown call that was not previously tracked by CallsManager,
177      *  and did not originate via the regular incoming/outgoing call code paths.
178      */
179     private boolean mIsUnknown;
180 
181     /**
182      * The time this call was created. Beyond logging and such, may also be used for bookkeeping
183      * and specifically for marking certain call attempts as failed attempts.
184      */
185     private final long mCreationTimeMillis = System.currentTimeMillis();
186 
187     /** The gateway information associated with this call. This stores the original call handle
188      * that the user is attempting to connect to via the gateway, the actual handle to dial in
189      * order to connect the call via the gateway, as well as the package name of the gateway
190      * service. */
191     private GatewayInfo mGatewayInfo;
192 
193     private PhoneAccountHandle mConnectionManagerPhoneAccountHandle;
194 
195     private PhoneAccountHandle mTargetPhoneAccountHandle;
196 
197     private final Handler mHandler = new Handler();
198 
199     private final List<Call> mConferenceableCalls = new ArrayList<>();
200 
201     private long mConnectTimeMillis = 0;
202 
203     /** The state of the call. */
204     private int mState;
205 
206     /** The handle with which to establish this call. */
207     private Uri mHandle;
208 
209     /**
210      * The presentation requirements for the handle. See {@link TelecomManager} for valid values.
211      */
212     private int mHandlePresentation;
213 
214     /** The caller display name (CNAP) set by the connection service. */
215     private String mCallerDisplayName;
216 
217     /**
218      * The presentation requirements for the handle. See {@link TelecomManager} for valid values.
219      */
220     private int mCallerDisplayNamePresentation;
221 
222     /**
223      * The connection service which is attempted or already connecting this call.
224      */
225     private ConnectionServiceWrapper mConnectionService;
226 
227     private boolean mIsEmergencyCall;
228 
229     private boolean mSpeakerphoneOn;
230 
231     /**
232      * Tracks the video states which were applicable over the duration of a call.
233      * See {@link VideoProfile} for a list of valid video states.
234      */
235     private int mVideoStateHistory;
236 
237     private int mVideoState;
238 
239     /**
240      * Disconnect cause for the call. Only valid if the state of the call is STATE_DISCONNECTED.
241      * See {@link android.telecom.DisconnectCause}.
242      */
243     private DisconnectCause mDisconnectCause = new DisconnectCause(DisconnectCause.UNKNOWN);
244 
245     /** Info used by the connection services. */
246     private Bundle mExtras = Bundle.EMPTY;
247 
248     /** Set of listeners on this call.
249      *
250      * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
251      * load factor before resizing, 1 means we only expect a single thread to
252      * access the map so make only a single shard
253      */
254     private final Set<Listener> mListeners = Collections.newSetFromMap(
255             new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
256 
257     private CreateConnectionProcessor mCreateConnectionProcessor;
258 
259     /** Caller information retrieved from the latest contact query. */
260     private CallerInfo mCallerInfo;
261 
262     /** The latest token used with a contact info query. */
263     private int mQueryToken = 0;
264 
265     /** Whether this call is requesting that Telecom play the ringback tone on its behalf. */
266     private boolean mRingbackRequested = false;
267 
268     /** Whether direct-to-voicemail query is pending. */
269     private boolean mDirectToVoicemailQueryPending;
270 
271     private int mCallCapabilities;
272 
273     private boolean mIsConference = false;
274 
275     private Call mParentCall = null;
276 
277     private List<Call> mChildCalls = new LinkedList<>();
278 
279     /** Set of text message responses allowed for this call, if applicable. */
280     private List<String> mCannedSmsResponses = Collections.EMPTY_LIST;
281 
282     /** Whether an attempt has been made to load the text message responses. */
283     private boolean mCannedSmsResponsesLoadingStarted = false;
284 
285     private IVideoProvider mVideoProvider;
286 
287     private boolean mIsVoipAudioMode;
288     private StatusHints mStatusHints;
289     private final ConnectionServiceRepository mRepository;
290     private final Context mContext;
291 
292     private boolean mWasConferencePreviouslyMerged = false;
293 
294     // For conferences which support merge/swap at their level, we retain a notion of an active call.
295     // This is used for BluetoothPhoneService.  In order to support hold/merge, it must have the notion
296     // of the current "active" call within the conference call. This maintains the "active" call and
297     // switches every time the user hits "swap".
298     private Call mConferenceLevelActiveCall = null;
299 
300     private boolean mIsLocallyDisconnecting = false;
301 
302     /**
303      * Persists the specified parameters and initializes the new instance.
304      *
305      * @param context The context.
306      * @param repository The connection service repository.
307      * @param handle The handle to dial.
308      * @param gatewayInfo Gateway information to use for the call.
309      * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
310      *         This account must be one that was registered with the
311      *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
312      * @param targetPhoneAccountHandle Account information to use for the call. This account must be
313      *         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
314      * @param isIncoming True if this is an incoming call.
315      */
Call( Context context, ConnectionServiceRepository repository, Uri handle, GatewayInfo gatewayInfo, PhoneAccountHandle connectionManagerPhoneAccountHandle, PhoneAccountHandle targetPhoneAccountHandle, boolean isIncoming, boolean isConference)316     Call(
317             Context context,
318             ConnectionServiceRepository repository,
319             Uri handle,
320             GatewayInfo gatewayInfo,
321             PhoneAccountHandle connectionManagerPhoneAccountHandle,
322             PhoneAccountHandle targetPhoneAccountHandle,
323             boolean isIncoming,
324             boolean isConference) {
325         mState = isConference ? CallState.ACTIVE : CallState.NEW;
326         mContext = context;
327         mRepository = repository;
328         setHandle(handle);
329         setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
330         mGatewayInfo = gatewayInfo;
331         setConnectionManagerPhoneAccount(connectionManagerPhoneAccountHandle);
332         setTargetPhoneAccount(targetPhoneAccountHandle);
333         mIsIncoming = isIncoming;
334         mIsConference = isConference;
335         maybeLoadCannedSmsResponses();
336     }
337 
addListener(Listener listener)338     void addListener(Listener listener) {
339         mListeners.add(listener);
340     }
341 
removeListener(Listener listener)342     void removeListener(Listener listener) {
343         if (listener != null) {
344             mListeners.remove(listener);
345         }
346     }
347 
348     /** {@inheritDoc} */
349     @Override
toString()350     public String toString() {
351         String component = null;
352         if (mConnectionService != null && mConnectionService.getComponentName() != null) {
353             component = mConnectionService.getComponentName().flattenToShortString();
354         }
355 
356         return String.format(Locale.US, "[%s, %s, %s, %s, %d, childs(%d), has_parent(%b), [%s]",
357                 System.identityHashCode(this),
358                 CallState.toString(mState),
359                 component,
360                 Log.piiHandle(mHandle),
361                 getVideoState(),
362                 getChildCalls().size(),
363                 getParentCall() != null,
364                 PhoneCapabilities.toString(getCallCapabilities()));
365     }
366 
getState()367     int getState() {
368         return mState;
369     }
370 
371     /**
372      * Sets the call state. Although there exists the notion of appropriate state transitions
373      * (see {@link CallState}), in practice those expectations break down when cellular systems
374      * misbehave and they do this very often. The result is that we do not enforce state transitions
375      * and instead keep the code resilient to unexpected state changes.
376      */
setState(int newState)377     void setState(int newState) {
378         if (mState != newState) {
379             Log.v(this, "setState %s -> %s", mState, newState);
380             mState = newState;
381             maybeLoadCannedSmsResponses();
382 
383             if (mState == CallState.DISCONNECTED) {
384                 setLocallyDisconnecting(false);
385                 fixParentAfterDisconnect();
386             }
387         }
388     }
389 
setRingbackRequested(boolean ringbackRequested)390     void setRingbackRequested(boolean ringbackRequested) {
391         mRingbackRequested = ringbackRequested;
392         for (Listener l : mListeners) {
393             l.onRingbackRequested(this, mRingbackRequested);
394         }
395     }
396 
isRingbackRequested()397     boolean isRingbackRequested() {
398         return mRingbackRequested;
399     }
400 
isConference()401     boolean isConference() {
402         return mIsConference;
403     }
404 
getHandle()405     Uri getHandle() {
406         return mHandle;
407     }
408 
getHandlePresentation()409     int getHandlePresentation() {
410         return mHandlePresentation;
411     }
412 
413 
setHandle(Uri handle)414     void setHandle(Uri handle) {
415         setHandle(handle, TelecomManager.PRESENTATION_ALLOWED);
416     }
417 
setHandle(Uri handle, int presentation)418     void setHandle(Uri handle, int presentation) {
419         if (!Objects.equals(handle, mHandle) || presentation != mHandlePresentation) {
420             mHandle = handle;
421             mHandlePresentation = presentation;
422             mIsEmergencyCall = mHandle != null && PhoneNumberUtils.isLocalEmergencyNumber(mContext,
423                     mHandle.getSchemeSpecificPart());
424             startCallerInfoLookup();
425             for (Listener l : mListeners) {
426                 l.onHandleChanged(this);
427             }
428         }
429     }
430 
getCallerDisplayName()431     String getCallerDisplayName() {
432         return mCallerDisplayName;
433     }
434 
getCallerDisplayNamePresentation()435     int getCallerDisplayNamePresentation() {
436         return mCallerDisplayNamePresentation;
437     }
438 
setCallerDisplayName(String callerDisplayName, int presentation)439     void setCallerDisplayName(String callerDisplayName, int presentation) {
440         if (!TextUtils.equals(callerDisplayName, mCallerDisplayName) ||
441                 presentation != mCallerDisplayNamePresentation) {
442             mCallerDisplayName = callerDisplayName;
443             mCallerDisplayNamePresentation = presentation;
444             for (Listener l : mListeners) {
445                 l.onCallerDisplayNameChanged(this);
446             }
447         }
448     }
449 
getName()450     String getName() {
451         return mCallerInfo == null ? null : mCallerInfo.name;
452     }
453 
getPhotoIcon()454     Bitmap getPhotoIcon() {
455         return mCallerInfo == null ? null : mCallerInfo.cachedPhotoIcon;
456     }
457 
getPhoto()458     Drawable getPhoto() {
459         return mCallerInfo == null ? null : mCallerInfo.cachedPhoto;
460     }
461 
462     /**
463      * @param disconnectCause The reason for the disconnection, represented by
464      *         {@link android.telecom.DisconnectCause}.
465      */
setDisconnectCause(DisconnectCause disconnectCause)466     void setDisconnectCause(DisconnectCause disconnectCause) {
467         // TODO: Consider combining this method with a setDisconnected() method that is totally
468         // separate from setState.
469         mDisconnectCause = disconnectCause;
470     }
471 
getDisconnectCause()472     DisconnectCause getDisconnectCause() {
473         return mDisconnectCause;
474     }
475 
isEmergencyCall()476     boolean isEmergencyCall() {
477         return mIsEmergencyCall;
478     }
479 
480     /**
481      * @return The original handle this call is associated with. In-call services should use this
482      * handle when indicating in their UI the handle that is being called.
483      */
getOriginalHandle()484     public Uri getOriginalHandle() {
485         if (mGatewayInfo != null && !mGatewayInfo.isEmpty()) {
486             return mGatewayInfo.getOriginalAddress();
487         }
488         return getHandle();
489     }
490 
getGatewayInfo()491     GatewayInfo getGatewayInfo() {
492         return mGatewayInfo;
493     }
494 
setGatewayInfo(GatewayInfo gatewayInfo)495     void setGatewayInfo(GatewayInfo gatewayInfo) {
496         mGatewayInfo = gatewayInfo;
497     }
498 
getConnectionManagerPhoneAccount()499     PhoneAccountHandle getConnectionManagerPhoneAccount() {
500         return mConnectionManagerPhoneAccountHandle;
501     }
502 
setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle)503     void setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle) {
504         if (!Objects.equals(mConnectionManagerPhoneAccountHandle, accountHandle)) {
505             mConnectionManagerPhoneAccountHandle = accountHandle;
506             for (Listener l : mListeners) {
507                 l.onConnectionManagerPhoneAccountChanged(this);
508             }
509         }
510 
511     }
512 
getTargetPhoneAccount()513     PhoneAccountHandle getTargetPhoneAccount() {
514         return mTargetPhoneAccountHandle;
515     }
516 
setTargetPhoneAccount(PhoneAccountHandle accountHandle)517     void setTargetPhoneAccount(PhoneAccountHandle accountHandle) {
518         if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) {
519             mTargetPhoneAccountHandle = accountHandle;
520             for (Listener l : mListeners) {
521                 l.onTargetPhoneAccountChanged(this);
522             }
523         }
524     }
525 
isIncoming()526     boolean isIncoming() {
527         return mIsIncoming;
528     }
529 
530     /**
531      * @return The "age" of this call object in milliseconds, which typically also represents the
532      *     period since this call was added to the set pending outgoing calls, see
533      *     mCreationTimeMillis.
534      */
getAgeMillis()535     long getAgeMillis() {
536         return System.currentTimeMillis() - mCreationTimeMillis;
537     }
538 
539     /**
540      * @return The time when this call object was created and added to the set of pending outgoing
541      *     calls.
542      */
getCreationTimeMillis()543     long getCreationTimeMillis() {
544         return mCreationTimeMillis;
545     }
546 
getConnectTimeMillis()547     long getConnectTimeMillis() {
548         return mConnectTimeMillis;
549     }
550 
setConnectTimeMillis(long connectTimeMillis)551     void setConnectTimeMillis(long connectTimeMillis) {
552         mConnectTimeMillis = connectTimeMillis;
553     }
554 
getCallCapabilities()555     int getCallCapabilities() {
556         return mCallCapabilities;
557     }
558 
setCallCapabilities(int callCapabilities)559     void setCallCapabilities(int callCapabilities) {
560         setCallCapabilities(callCapabilities, false /* forceUpdate */);
561     }
562 
setCallCapabilities(int callCapabilities, boolean forceUpdate)563     void setCallCapabilities(int callCapabilities, boolean forceUpdate) {
564         Log.v(this, "setCallCapabilities: %s", PhoneCapabilities.toString(callCapabilities));
565         if (forceUpdate || mCallCapabilities != callCapabilities) {
566            mCallCapabilities = callCapabilities;
567             for (Listener l : mListeners) {
568                 l.onCallCapabilitiesChanged(this);
569             }
570         }
571     }
572 
getParentCall()573     Call getParentCall() {
574         return mParentCall;
575     }
576 
getChildCalls()577     List<Call> getChildCalls() {
578         return mChildCalls;
579     }
580 
wasConferencePreviouslyMerged()581     boolean wasConferencePreviouslyMerged() {
582         return mWasConferencePreviouslyMerged;
583     }
584 
getConferenceLevelActiveCall()585     Call getConferenceLevelActiveCall() {
586         return mConferenceLevelActiveCall;
587     }
588 
getConnectionService()589     ConnectionServiceWrapper getConnectionService() {
590         return mConnectionService;
591     }
592 
593     /**
594      * Retrieves the {@link Context} for the call.
595      *
596      * @return The {@link Context}.
597      */
getContext()598     Context getContext() {
599         return mContext;
600     }
601 
setConnectionService(ConnectionServiceWrapper service)602     void setConnectionService(ConnectionServiceWrapper service) {
603         Preconditions.checkNotNull(service);
604 
605         clearConnectionService();
606 
607         service.incrementAssociatedCallCount();
608         mConnectionService = service;
609         mConnectionService.addCall(this);
610     }
611 
612     /**
613      * Clears the associated connection service.
614      */
clearConnectionService()615     void clearConnectionService() {
616         if (mConnectionService != null) {
617             ConnectionServiceWrapper serviceTemp = mConnectionService;
618             mConnectionService = null;
619             serviceTemp.removeCall(this);
620 
621             // Decrementing the count can cause the service to unbind, which itself can trigger the
622             // service-death code.  Since the service death code tries to clean up any associated
623             // calls, we need to make sure to remove that information (e.g., removeCall()) before
624             // we decrement. Technically, invoking removeCall() prior to decrementing is all that is
625             // necessary, but cleaning up mConnectionService prior to triggering an unbind is good
626             // to do.
627             decrementAssociatedCallCount(serviceTemp);
628         }
629     }
630 
processDirectToVoicemail()631     private void processDirectToVoicemail() {
632         if (mDirectToVoicemailQueryPending) {
633             if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
634                 Log.i(this, "Directing call to voicemail: %s.", this);
635                 // TODO: Once we move State handling from CallsManager to Call, we
636                 // will not need to set STATE_RINGING state prior to calling reject.
637                 setState(CallState.RINGING);
638                 reject(false, null);
639             } else {
640                 // TODO: Make this class (not CallsManager) responsible for changing
641                 // the call state to STATE_RINGING.
642 
643                 // TODO: Replace this with state transition to STATE_RINGING.
644                 for (Listener l : mListeners) {
645                     l.onSuccessfulIncomingCall(this);
646                 }
647             }
648 
649             mDirectToVoicemailQueryPending = false;
650         }
651     }
652 
653     /**
654      * Starts the create connection sequence. Upon completion, there should exist an active
655      * connection through a connection service (or the call will have failed).
656      *
657      * @param phoneAccountRegistrar The phone account registrar.
658      */
startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar)659     void startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar) {
660         Preconditions.checkState(mCreateConnectionProcessor == null);
661         mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this,
662                 phoneAccountRegistrar, mContext);
663         mCreateConnectionProcessor.process();
664     }
665 
666     @Override
handleCreateConnectionSuccess( CallIdMapper idMapper, ParcelableConnection connection)667     public void handleCreateConnectionSuccess(
668             CallIdMapper idMapper,
669             ParcelableConnection connection) {
670         Log.v(this, "handleCreateConnectionSuccessful %s", connection);
671         mCreateConnectionProcessor = null;
672         setTargetPhoneAccount(connection.getPhoneAccount());
673         setHandle(connection.getHandle(), connection.getHandlePresentation());
674         setCallerDisplayName(
675                 connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());
676         setCallCapabilities(connection.getCapabilities());
677         setVideoProvider(connection.getVideoProvider());
678         setVideoState(connection.getVideoState());
679         setRingbackRequested(connection.isRingbackRequested());
680         setIsVoipAudioMode(connection.getIsVoipAudioMode());
681         setStatusHints(connection.getStatusHints());
682 
683         mConferenceableCalls.clear();
684         for (String id : connection.getConferenceableConnectionIds()) {
685             mConferenceableCalls.add(idMapper.getCall(id));
686         }
687 
688         if (mIsUnknown) {
689             for (Listener l : mListeners) {
690                 l.onSuccessfulUnknownCall(this, getStateFromConnectionState(connection.getState()));
691             }
692         } else if (mIsIncoming) {
693             // We do not handle incoming calls immediately when they are verified by the connection
694             // service. We allow the caller-info-query code to execute first so that we can read the
695             // direct-to-voicemail property before deciding if we want to show the incoming call to
696             // the user or if we want to reject the call.
697             mDirectToVoicemailQueryPending = true;
698 
699             // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
700             // showing the user the incoming call screen.
701             mHandler.postDelayed(mDirectToVoicemailRunnable, Timeouts.getDirectToVoicemailMillis(
702                     mContext.getContentResolver()));
703         } else {
704             for (Listener l : mListeners) {
705                 l.onSuccessfulOutgoingCall(this,
706                         getStateFromConnectionState(connection.getState()));
707             }
708         }
709     }
710 
711     @Override
handleCreateConnectionFailure(DisconnectCause disconnectCause)712     public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
713         mCreateConnectionProcessor = null;
714         clearConnectionService();
715         setDisconnectCause(disconnectCause);
716         CallsManager.getInstance().markCallAsDisconnected(this, disconnectCause);
717 
718         if (mIsUnknown) {
719             for (Listener listener : mListeners) {
720                 listener.onFailedUnknownCall(this);
721             }
722         } else if (mIsIncoming) {
723             for (Listener listener : mListeners) {
724                 listener.onFailedIncomingCall(this);
725             }
726         } else {
727             for (Listener listener : mListeners) {
728                 listener.onFailedOutgoingCall(this, disconnectCause);
729             }
730         }
731     }
732 
733     /**
734      * Plays the specified DTMF tone.
735      */
playDtmfTone(char digit)736     void playDtmfTone(char digit) {
737         if (mConnectionService == null) {
738             Log.w(this, "playDtmfTone() request on a call without a connection service.");
739         } else {
740             Log.i(this, "Send playDtmfTone to connection service for call %s", this);
741             mConnectionService.playDtmfTone(this, digit);
742         }
743     }
744 
745     /**
746      * Stops playing any currently playing DTMF tone.
747      */
stopDtmfTone()748     void stopDtmfTone() {
749         if (mConnectionService == null) {
750             Log.w(this, "stopDtmfTone() request on a call without a connection service.");
751         } else {
752             Log.i(this, "Send stopDtmfTone to connection service for call %s", this);
753             mConnectionService.stopDtmfTone(this);
754         }
755     }
756 
757     /**
758      * Attempts to disconnect the call through the connection service.
759      */
disconnect()760     void disconnect() {
761         // Track that the call is now locally disconnecting.
762         setLocallyDisconnecting(true);
763 
764         if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT ||
765                 mState == CallState.CONNECTING) {
766             Log.v(this, "Aborting call %s", this);
767             abort();
768         } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
769             if (mConnectionService == null) {
770                 Log.e(this, new Exception(), "disconnect() request on a call without a"
771                         + " connection service.");
772             } else {
773                 Log.i(this, "Send disconnect to connection service for call: %s", this);
774                 // The call isn't officially disconnected until the connection service
775                 // confirms that the call was actually disconnected. Only then is the
776                 // association between call and connection service severed, see
777                 // {@link CallsManager#markCallAsDisconnected}.
778                 mConnectionService.disconnect(this);
779             }
780         }
781     }
782 
abort()783     void abort() {
784         if (mCreateConnectionProcessor != null) {
785             mCreateConnectionProcessor.abort();
786         } else if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT
787                 || mState == CallState.CONNECTING) {
788             handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
789         } else {
790             Log.v(this, "Cannot abort a call which isn't either PRE_DIAL_WAIT or CONNECTING");
791         }
792     }
793 
794     /**
795      * Answers the call if it is ringing.
796      *
797      * @param videoState The video state in which to answer the call.
798      */
answer(int videoState)799     void answer(int videoState) {
800         Preconditions.checkNotNull(mConnectionService);
801 
802         // Check to verify that the call is still in the ringing state. A call can change states
803         // between the time the user hits 'answer' and Telecom receives the command.
804         if (isRinging("answer")) {
805             // At this point, we are asking the connection service to answer but we don't assume
806             // that it will work. Instead, we wait until confirmation from the connectino service
807             // that the call is in a non-STATE_RINGING state before changing the UI. See
808             // {@link ConnectionServiceAdapter#setActive} and other set* methods.
809             mConnectionService.answer(this, videoState);
810         }
811     }
812 
813     /**
814      * Rejects the call if it is ringing.
815      *
816      * @param rejectWithMessage Whether to send a text message as part of the call rejection.
817      * @param textMessage An optional text message to send as part of the rejection.
818      */
reject(boolean rejectWithMessage, String textMessage)819     void reject(boolean rejectWithMessage, String textMessage) {
820         Preconditions.checkNotNull(mConnectionService);
821 
822         // Check to verify that the call is still in the ringing state. A call can change states
823         // between the time the user hits 'reject' and Telecomm receives the command.
824         if (isRinging("reject")) {
825             mConnectionService.reject(this);
826         }
827     }
828 
829     /**
830      * Puts the call on hold if it is currently active.
831      */
hold()832     void hold() {
833         Preconditions.checkNotNull(mConnectionService);
834 
835         if (mState == CallState.ACTIVE) {
836             mConnectionService.hold(this);
837         }
838     }
839 
840     /**
841      * Releases the call from hold if it is currently active.
842      */
unhold()843     void unhold() {
844         Preconditions.checkNotNull(mConnectionService);
845 
846         if (mState == CallState.ON_HOLD) {
847             mConnectionService.unhold(this);
848         }
849     }
850 
851     /** Checks if this is a live call or not. */
isAlive()852     boolean isAlive() {
853         switch (mState) {
854             case CallState.NEW:
855             case CallState.RINGING:
856             case CallState.DISCONNECTED:
857             case CallState.ABORTED:
858                 return false;
859             default:
860                 return true;
861         }
862     }
863 
isActive()864     boolean isActive() {
865         return mState == CallState.ACTIVE;
866     }
867 
getExtras()868     Bundle getExtras() {
869         return mExtras;
870     }
871 
setExtras(Bundle extras)872     void setExtras(Bundle extras) {
873         mExtras = extras;
874     }
875 
876     /**
877      * @return the uri of the contact associated with this call.
878      */
getContactUri()879     Uri getContactUri() {
880         if (mCallerInfo == null || !mCallerInfo.contactExists) {
881             return getHandle();
882         }
883         return Contacts.getLookupUri(mCallerInfo.contactIdOrZero, mCallerInfo.lookupKey);
884     }
885 
getRingtone()886     Uri getRingtone() {
887         return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
888     }
889 
onPostDialWait(String remaining)890     void onPostDialWait(String remaining) {
891         for (Listener l : mListeners) {
892             l.onPostDialWait(this, remaining);
893         }
894     }
895 
postDialContinue(boolean proceed)896     void postDialContinue(boolean proceed) {
897         mConnectionService.onPostDialContinue(this, proceed);
898     }
899 
conferenceWith(Call otherCall)900     void conferenceWith(Call otherCall) {
901         if (mConnectionService == null) {
902             Log.w(this, "conference requested on a call without a connection service.");
903         } else {
904             mConnectionService.conference(this, otherCall);
905         }
906     }
907 
splitFromConference()908     void splitFromConference() {
909         if (mConnectionService == null) {
910             Log.w(this, "splitting from conference call without a connection service");
911         } else {
912             mConnectionService.splitFromConference(this);
913         }
914     }
915 
mergeConference()916     void mergeConference() {
917         if (mConnectionService == null) {
918             Log.w(this, "merging conference calls without a connection service.");
919         } else if (can(PhoneCapabilities.MERGE_CONFERENCE)) {
920             mConnectionService.mergeConference(this);
921             mWasConferencePreviouslyMerged = true;
922         }
923     }
924 
swapConference()925     void swapConference() {
926         if (mConnectionService == null) {
927             Log.w(this, "swapping conference calls without a connection service.");
928         } else if (can(PhoneCapabilities.SWAP_CONFERENCE)) {
929             mConnectionService.swapConference(this);
930             switch (mChildCalls.size()) {
931                 case 1:
932                     mConferenceLevelActiveCall = mChildCalls.get(0);
933                     break;
934                 case 2:
935                     // swap
936                     mConferenceLevelActiveCall = mChildCalls.get(0) == mConferenceLevelActiveCall ?
937                             mChildCalls.get(1) : mChildCalls.get(0);
938                     break;
939                 default:
940                     // For anything else 0, or 3+, set it to null since it is impossible to tell.
941                     mConferenceLevelActiveCall = null;
942                     break;
943             }
944         }
945     }
946 
setParentCall(Call parentCall)947     void setParentCall(Call parentCall) {
948         if (parentCall == this) {
949             Log.e(this, new Exception(), "setting the parent to self");
950             return;
951         }
952         if (parentCall == mParentCall) {
953             // nothing to do
954             return;
955         }
956         Preconditions.checkState(parentCall == null || mParentCall == null);
957 
958         Call oldParent = mParentCall;
959         if (mParentCall != null) {
960             mParentCall.removeChildCall(this);
961         }
962         mParentCall = parentCall;
963         if (mParentCall != null) {
964             mParentCall.addChildCall(this);
965         }
966 
967         for (Listener l : mListeners) {
968             l.onParentChanged(this);
969         }
970     }
971 
setConferenceableCalls(List<Call> conferenceableCalls)972     void setConferenceableCalls(List<Call> conferenceableCalls) {
973         mConferenceableCalls.clear();
974         mConferenceableCalls.addAll(conferenceableCalls);
975 
976         for (Listener l : mListeners) {
977             l.onConferenceableCallsChanged(this);
978         }
979     }
980 
getConferenceableCalls()981     List<Call> getConferenceableCalls() {
982         return mConferenceableCalls;
983     }
984 
can(int capability)985     boolean can(int capability) {
986         return (mCallCapabilities & capability) == capability;
987     }
988 
addChildCall(Call call)989     private void addChildCall(Call call) {
990         if (!mChildCalls.contains(call)) {
991             // Set the pseudo-active call to the latest child added to the conference.
992             // See definition of mConferenceLevelActiveCall for more detail.
993             mConferenceLevelActiveCall = call;
994             mChildCalls.add(call);
995 
996             for (Listener l : mListeners) {
997                 l.onChildrenChanged(this);
998             }
999         }
1000     }
1001 
removeChildCall(Call call)1002     private void removeChildCall(Call call) {
1003         if (mChildCalls.remove(call)) {
1004             for (Listener l : mListeners) {
1005                 l.onChildrenChanged(this);
1006             }
1007         }
1008     }
1009 
1010     /**
1011      * Return whether the user can respond to this {@code Call} via an SMS message.
1012      *
1013      * @return true if the "Respond via SMS" feature should be enabled
1014      * for this incoming call.
1015      *
1016      * The general rule is that we *do* allow "Respond via SMS" except for
1017      * the few (relatively rare) cases where we know for sure it won't
1018      * work, namely:
1019      *   - a bogus or blank incoming number
1020      *   - a call from a SIP address
1021      *   - a "call presentation" that doesn't allow the number to be revealed
1022      *
1023      * In all other cases, we allow the user to respond via SMS.
1024      *
1025      * Note that this behavior isn't perfect; for example we have no way
1026      * to detect whether the incoming call is from a landline (with most
1027      * networks at least), so we still enable this feature even though
1028      * SMSes to that number will silently fail.
1029      */
isRespondViaSmsCapable()1030     boolean isRespondViaSmsCapable() {
1031         if (mState != CallState.RINGING) {
1032             return false;
1033         }
1034 
1035         if (getHandle() == null) {
1036             // No incoming number known or call presentation is "PRESENTATION_RESTRICTED", in
1037             // other words, the user should not be able to see the incoming phone number.
1038             return false;
1039         }
1040 
1041         if (PhoneNumberUtils.isUriNumber(getHandle().toString())) {
1042             // The incoming number is actually a URI (i.e. a SIP address),
1043             // not a regular PSTN phone number, and we can't send SMSes to
1044             // SIP addresses.
1045             // (TODO: That might still be possible eventually, though. Is
1046             // there some SIP-specific equivalent to sending a text message?)
1047             return false;
1048         }
1049 
1050         // Is there a valid SMS application on the phone?
1051         if (SmsApplication.getDefaultRespondViaMessageApplication(mContext,
1052                 true /*updateIfNeeded*/) == null) {
1053             return false;
1054         }
1055 
1056         // TODO: with some carriers (in certain countries) you *can* actually
1057         // tell whether a given number is a mobile phone or not. So in that
1058         // case we could potentially return false here if the incoming call is
1059         // from a land line.
1060 
1061         // If none of the above special cases apply, it's OK to enable the
1062         // "Respond via SMS" feature.
1063         return true;
1064     }
1065 
getCannedSmsResponses()1066     List<String> getCannedSmsResponses() {
1067         return mCannedSmsResponses;
1068     }
1069 
1070     /**
1071      * We need to make sure that before we move a call to the disconnected state, it no
1072      * longer has any parent/child relationships.  We want to do this to ensure that the InCall
1073      * Service always has the right data in the right order.  We also want to do it in telecom so
1074      * that the insurance policy lives in the framework side of things.
1075      */
fixParentAfterDisconnect()1076     private void fixParentAfterDisconnect() {
1077         setParentCall(null);
1078     }
1079 
1080     /**
1081      * @return True if the call is ringing, else logs the action name.
1082      */
isRinging(String actionName)1083     private boolean isRinging(String actionName) {
1084         if (mState == CallState.RINGING) {
1085             return true;
1086         }
1087 
1088         Log.i(this, "Request to %s a non-ringing call %s", actionName, this);
1089         return false;
1090     }
1091 
1092     @SuppressWarnings("rawtypes")
decrementAssociatedCallCount(ServiceBinder binder)1093     private void decrementAssociatedCallCount(ServiceBinder binder) {
1094         if (binder != null) {
1095             binder.decrementAssociatedCallCount();
1096         }
1097     }
1098 
1099     /**
1100      * Looks up contact information based on the current handle.
1101      */
startCallerInfoLookup()1102     private void startCallerInfoLookup() {
1103         String number = mHandle == null ? null : mHandle.getSchemeSpecificPart();
1104 
1105         mQueryToken++;  // Updated so that previous queries can no longer set the information.
1106         mCallerInfo = null;
1107         if (!TextUtils.isEmpty(number)) {
1108             Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
1109             CallerInfoAsyncQuery.startQuery(
1110                     mQueryToken,
1111                     mContext,
1112                     number,
1113                     sCallerInfoQueryListener,
1114                     this);
1115         }
1116     }
1117 
1118     /**
1119      * Saves the specified caller info if the specified token matches that of the last query
1120      * that was made.
1121      *
1122      * @param callerInfo The new caller information to set.
1123      * @param token The token used with this query.
1124      */
setCallerInfo(CallerInfo callerInfo, int token)1125     private void setCallerInfo(CallerInfo callerInfo, int token) {
1126         Preconditions.checkNotNull(callerInfo);
1127 
1128         if (mQueryToken == token) {
1129             mCallerInfo = callerInfo;
1130             Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
1131 
1132             if (mCallerInfo.contactDisplayPhotoUri != null) {
1133                 Log.d(this, "Searching person uri %s for call %s",
1134                         mCallerInfo.contactDisplayPhotoUri, this);
1135                 ContactsAsyncHelper.startObtainPhotoAsync(
1136                         token,
1137                         mContext,
1138                         mCallerInfo.contactDisplayPhotoUri,
1139                         sPhotoLoadListener,
1140                         this);
1141                 // Do not call onCallerInfoChanged yet in this case.  We call it in setPhoto().
1142             } else {
1143                 for (Listener l : mListeners) {
1144                     l.onCallerInfoChanged(this);
1145                 }
1146             }
1147 
1148             processDirectToVoicemail();
1149         }
1150     }
1151 
getCallerInfo()1152     CallerInfo getCallerInfo() {
1153         return mCallerInfo;
1154     }
1155 
1156     /**
1157      * Saves the specified photo information if the specified token matches that of the last query.
1158      *
1159      * @param photo The photo as a drawable.
1160      * @param photoIcon The photo as a small icon.
1161      * @param token The token used with this query.
1162      */
setPhoto(Drawable photo, Bitmap photoIcon, int token)1163     private void setPhoto(Drawable photo, Bitmap photoIcon, int token) {
1164         if (mQueryToken == token) {
1165             mCallerInfo.cachedPhoto = photo;
1166             mCallerInfo.cachedPhotoIcon = photoIcon;
1167 
1168             for (Listener l : mListeners) {
1169                 l.onCallerInfoChanged(this);
1170             }
1171         }
1172     }
1173 
maybeLoadCannedSmsResponses()1174     private void maybeLoadCannedSmsResponses() {
1175         if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
1176             Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
1177             mCannedSmsResponsesLoadingStarted = true;
1178             RespondViaSmsManager.getInstance().loadCannedTextMessages(
1179                     new Response<Void, List<String>>() {
1180                         @Override
1181                         public void onResult(Void request, List<String>... result) {
1182                             if (result.length > 0) {
1183                                 Log.d(this, "maybeLoadCannedSmsResponses: got %s", result[0]);
1184                                 mCannedSmsResponses = result[0];
1185                                 for (Listener l : mListeners) {
1186                                     l.onCannedSmsResponsesLoaded(Call.this);
1187                                 }
1188                             }
1189                         }
1190 
1191                         @Override
1192                         public void onError(Void request, int code, String msg) {
1193                             Log.w(Call.this, "Error obtaining canned SMS responses: %d %s", code,
1194                                     msg);
1195                         }
1196                     },
1197                     mContext
1198             );
1199         } else {
1200             Log.d(this, "maybeLoadCannedSmsResponses: doing nothing");
1201         }
1202     }
1203 
1204     /**
1205      * Sets speakerphone option on when call begins.
1206      */
setStartWithSpeakerphoneOn(boolean startWithSpeakerphone)1207     public void setStartWithSpeakerphoneOn(boolean startWithSpeakerphone) {
1208         mSpeakerphoneOn = startWithSpeakerphone;
1209     }
1210 
1211     /**
1212      * Returns speakerphone option.
1213      *
1214      * @return Whether or not speakerphone should be set automatically when call begins.
1215      */
getStartWithSpeakerphoneOn()1216     public boolean getStartWithSpeakerphoneOn() {
1217         return mSpeakerphoneOn;
1218     }
1219 
1220     /**
1221      * Sets a video call provider for the call.
1222      */
setVideoProvider(IVideoProvider videoProvider)1223     public void setVideoProvider(IVideoProvider videoProvider) {
1224         mVideoProvider = videoProvider;
1225         for (Listener l : mListeners) {
1226             l.onVideoCallProviderChanged(Call.this);
1227         }
1228     }
1229 
1230     /**
1231      * @return Return the {@link Connection.VideoProvider} binder.
1232      */
getVideoProvider()1233     public IVideoProvider getVideoProvider() {
1234         return mVideoProvider;
1235     }
1236 
1237     /**
1238      * The current video state for the call.
1239      * Valid values: see {@link VideoProfile.VideoState}.
1240      */
getVideoState()1241     public int getVideoState() {
1242         return mVideoState;
1243     }
1244 
1245     /**
1246      * Returns the video states which were applicable over the duration of a call.
1247      * See {@link VideoProfile} for a list of valid video states.
1248      *
1249      * @return The video states applicable over the duration of the call.
1250      */
getVideoStateHistory()1251     public int getVideoStateHistory() {
1252         return mVideoStateHistory;
1253     }
1254 
1255     /**
1256      * Determines the current video state for the call.
1257      * For an outgoing call determines the desired video state for the call.
1258      * Valid values: see {@link VideoProfile.VideoState}
1259      *
1260      * @param videoState The video state for the call.
1261      */
setVideoState(int videoState)1262     public void setVideoState(int videoState) {
1263         // Track which video states were applicable over the duration of the call.
1264         mVideoStateHistory = mVideoStateHistory | videoState;
1265 
1266         mVideoState = videoState;
1267         for (Listener l : mListeners) {
1268             l.onVideoStateChanged(this);
1269         }
1270     }
1271 
getIsVoipAudioMode()1272     public boolean getIsVoipAudioMode() {
1273         return mIsVoipAudioMode;
1274     }
1275 
setIsVoipAudioMode(boolean audioModeIsVoip)1276     public void setIsVoipAudioMode(boolean audioModeIsVoip) {
1277         mIsVoipAudioMode = audioModeIsVoip;
1278         for (Listener l : mListeners) {
1279             l.onIsVoipAudioModeChanged(this);
1280         }
1281     }
1282 
getStatusHints()1283     public StatusHints getStatusHints() {
1284         return mStatusHints;
1285     }
1286 
setStatusHints(StatusHints statusHints)1287     public void setStatusHints(StatusHints statusHints) {
1288         mStatusHints = statusHints;
1289         for (Listener l : mListeners) {
1290             l.onStatusHintsChanged(this);
1291         }
1292     }
1293 
isUnknown()1294     public boolean isUnknown() {
1295         return mIsUnknown;
1296     }
1297 
setIsUnknown(boolean isUnknown)1298     public void setIsUnknown(boolean isUnknown) {
1299         mIsUnknown = isUnknown;
1300     }
1301 
1302     /**
1303      * Determines if this call is in a disconnecting state.
1304      *
1305      * @return {@code true} if this call is locally disconnecting.
1306      */
isLocallyDisconnecting()1307     public boolean isLocallyDisconnecting() {
1308         return mIsLocallyDisconnecting;
1309     }
1310 
1311     /**
1312      * Sets whether this call is in a disconnecting state.
1313      *
1314      * @param isLocallyDisconnecting {@code true} if this call is locally disconnecting.
1315      */
setLocallyDisconnecting(boolean isLocallyDisconnecting)1316     private void setLocallyDisconnecting(boolean isLocallyDisconnecting) {
1317         mIsLocallyDisconnecting = isLocallyDisconnecting;
1318     }
1319 
getStateFromConnectionState(int state)1320     static int getStateFromConnectionState(int state) {
1321         switch (state) {
1322             case Connection.STATE_INITIALIZING:
1323                 return CallState.CONNECTING;
1324             case Connection.STATE_ACTIVE:
1325                 return CallState.ACTIVE;
1326             case Connection.STATE_DIALING:
1327                 return CallState.DIALING;
1328             case Connection.STATE_DISCONNECTED:
1329                 return CallState.DISCONNECTED;
1330             case Connection.STATE_HOLDING:
1331                 return CallState.ON_HOLD;
1332             case Connection.STATE_NEW:
1333                 return CallState.NEW;
1334             case Connection.STATE_RINGING:
1335                 return CallState.RINGING;
1336         }
1337         return CallState.DISCONNECTED;
1338     }
1339 }
1340