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