Home | History | Annotate | Download | only in incallui
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.incallui;
     18 
     19 import android.content.Context;
     20 import android.hardware.camera2.CameraCharacteristics;
     21 import android.net.Uri;
     22 import android.os.Bundle;
     23 import android.os.Trace;
     24 import android.telecom.Call.Details;
     25 import android.telecom.Connection;
     26 import android.telecom.DisconnectCause;
     27 import android.telecom.GatewayInfo;
     28 import android.telecom.InCallService.VideoCall;
     29 import android.telecom.PhoneAccount;
     30 import android.telecom.PhoneAccountHandle;
     31 import android.telecom.TelecomManager;
     32 import android.telecom.VideoProfile;
     33 import android.text.TextUtils;
     34 
     35 import com.android.contacts.common.CallUtil;
     36 import com.android.contacts.common.compat.CallSdkCompat;
     37 import com.android.contacts.common.compat.CompatUtils;
     38 import com.android.contacts.common.compat.SdkVersionOverride;
     39 import com.android.contacts.common.compat.telecom.TelecomManagerCompat;
     40 import com.android.contacts.common.testing.NeededForTesting;
     41 import com.android.dialer.util.IntentUtil;
     42 import com.android.incallui.util.TelecomCallUtil;
     43 
     44 import java.util.ArrayList;
     45 import java.util.List;
     46 import java.util.Locale;
     47 import java.util.Objects;
     48 
     49 /**
     50  * Describes a single call and its state.
     51  */
     52 @NeededForTesting
     53 public class Call {
     54     /* Defines different states of this call */
     55     public static class State {
     56         public static final int INVALID = 0;
     57         public static final int NEW = 1;            /* The call is new. */
     58         public static final int IDLE = 2;           /* The call is idle.  Nothing active */
     59         public static final int ACTIVE = 3;         /* There is an active call */
     60         public static final int INCOMING = 4;       /* A normal incoming phone call */
     61         public static final int CALL_WAITING = 5;   /* Incoming call while another is active */
     62         public static final int DIALING = 6;        /* An outgoing call during dial phase */
     63         public static final int REDIALING = 7;      /* Subsequent dialing attempt after a failure */
     64         public static final int ONHOLD = 8;         /* An active phone call placed on hold */
     65         public static final int DISCONNECTING = 9;  /* A call is being ended. */
     66         public static final int DISCONNECTED = 10;  /* State after a call disconnects */
     67         public static final int CONFERENCED = 11;   /* Call part of a conference call */
     68         public static final int SELECT_PHONE_ACCOUNT = 12; /* Waiting for account selection */
     69         public static final int CONNECTING = 13;    /* Waiting for Telecom broadcast to finish */
     70         public static final int BLOCKED = 14;       /* The number was found on the block list */
     71 
     72 
     73         public static boolean isConnectingOrConnected(int state) {
     74             switch(state) {
     75                 case ACTIVE:
     76                 case INCOMING:
     77                 case CALL_WAITING:
     78                 case CONNECTING:
     79                 case DIALING:
     80                 case REDIALING:
     81                 case ONHOLD:
     82                 case CONFERENCED:
     83                     return true;
     84                 default:
     85             }
     86             return false;
     87         }
     88 
     89         public static boolean isDialing(int state) {
     90             return state == DIALING || state == REDIALING;
     91         }
     92 
     93         public static String toString(int state) {
     94             switch (state) {
     95                 case INVALID:
     96                     return "INVALID";
     97                 case NEW:
     98                     return "NEW";
     99                 case IDLE:
    100                     return "IDLE";
    101                 case ACTIVE:
    102                     return "ACTIVE";
    103                 case INCOMING:
    104                     return "INCOMING";
    105                 case CALL_WAITING:
    106                     return "CALL_WAITING";
    107                 case DIALING:
    108                     return "DIALING";
    109                 case REDIALING:
    110                     return "REDIALING";
    111                 case ONHOLD:
    112                     return "ONHOLD";
    113                 case DISCONNECTING:
    114                     return "DISCONNECTING";
    115                 case DISCONNECTED:
    116                     return "DISCONNECTED";
    117                 case CONFERENCED:
    118                     return "CONFERENCED";
    119                 case SELECT_PHONE_ACCOUNT:
    120                     return "SELECT_PHONE_ACCOUNT";
    121                 case CONNECTING:
    122                     return "CONNECTING";
    123                 case BLOCKED:
    124                     return "BLOCKED";
    125                 default:
    126                     return "UNKNOWN";
    127             }
    128         }
    129     }
    130 
    131     /**
    132      * Defines different states of session modify requests, which are used to upgrade to video, or
    133      * downgrade to audio.
    134      */
    135     public static class SessionModificationState {
    136         public static final int NO_REQUEST = 0;
    137         public static final int WAITING_FOR_RESPONSE = 1;
    138         public static final int REQUEST_FAILED = 2;
    139         public static final int RECEIVED_UPGRADE_TO_VIDEO_REQUEST = 3;
    140         public static final int UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT = 4;
    141         public static final int REQUEST_REJECTED = 5;
    142     }
    143 
    144     public static class VideoSettings {
    145         public static final int CAMERA_DIRECTION_UNKNOWN = -1;
    146         public static final int CAMERA_DIRECTION_FRONT_FACING =
    147                 CameraCharacteristics.LENS_FACING_FRONT;
    148         public static final int CAMERA_DIRECTION_BACK_FACING =
    149                 CameraCharacteristics.LENS_FACING_BACK;
    150 
    151         private int mCameraDirection = CAMERA_DIRECTION_UNKNOWN;
    152 
    153         /**
    154          * Sets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN,
    155          * the video state of the call should be used to infer the camera direction.
    156          *
    157          * @see {@link CameraCharacteristics#LENS_FACING_FRONT}
    158          * @see {@link CameraCharacteristics#LENS_FACING_BACK}
    159          */
    160         public void setCameraDir(int cameraDirection) {
    161             if (cameraDirection == CAMERA_DIRECTION_FRONT_FACING
    162                || cameraDirection == CAMERA_DIRECTION_BACK_FACING) {
    163                 mCameraDirection = cameraDirection;
    164             } else {
    165                 mCameraDirection = CAMERA_DIRECTION_UNKNOWN;
    166             }
    167         }
    168 
    169         /**
    170          * Gets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN,
    171          * the video state of the call should be used to infer the camera direction.
    172          *
    173          * @see {@link CameraCharacteristics#LENS_FACING_FRONT}
    174          * @see {@link CameraCharacteristics#LENS_FACING_BACK}
    175          */
    176         public int getCameraDir() {
    177             return mCameraDirection;
    178         }
    179 
    180         @Override
    181         public String toString() {
    182             return "(CameraDir:" + getCameraDir() + ")";
    183         }
    184     }
    185 
    186     /**
    187      * Tracks any state variables that is useful for logging. There is some amount of overlap with
    188      * existing call member variables, but this duplication helps to ensure that none of these
    189      * logging variables will interface with/and affect call logic.
    190      */
    191     public static class LogState {
    192 
    193         // Contact lookup type constants
    194         // Unknown lookup result (lookup not completed yet?)
    195         public static final int LOOKUP_UNKNOWN = 0;
    196         public static final int LOOKUP_NOT_FOUND = 1;
    197         public static final int LOOKUP_LOCAL_CONTACT = 2;
    198         public static final int LOOKUP_LOCAL_CACHE = 3;
    199         public static final int LOOKUP_REMOTE_CONTACT = 4;
    200         public static final int LOOKUP_EMERGENCY = 5;
    201         public static final int LOOKUP_VOICEMAIL = 6;
    202 
    203         // Call initiation type constants
    204         public static final int INITIATION_UNKNOWN = 0;
    205         public static final int INITIATION_INCOMING = 1;
    206         public static final int INITIATION_DIALPAD = 2;
    207         public static final int INITIATION_SPEED_DIAL = 3;
    208         public static final int INITIATION_REMOTE_DIRECTORY = 4;
    209         public static final int INITIATION_SMART_DIAL = 5;
    210         public static final int INITIATION_REGULAR_SEARCH = 6;
    211         public static final int INITIATION_CALL_LOG = 7;
    212         public static final int INITIATION_CALL_LOG_FILTER = 8;
    213         public static final int INITIATION_VOICEMAIL_LOG = 9;
    214         public static final int INITIATION_CALL_DETAILS = 10;
    215         public static final int INITIATION_QUICK_CONTACTS = 11;
    216         public static final int INITIATION_EXTERNAL = 12;
    217 
    218         public DisconnectCause disconnectCause;
    219         public boolean isIncoming = false;
    220         public int contactLookupResult = LOOKUP_UNKNOWN;
    221         public int callInitiationMethod = INITIATION_EXTERNAL;
    222         // If this was a conference call, the total number of calls involved in the conference.
    223         public int conferencedCalls = 0;
    224         public long duration = 0;
    225         public boolean isLogged = false;
    226 
    227         @Override
    228         public String toString() {
    229             return String.format(Locale.US, "["
    230                         + "%s, " // DisconnectCause toString already describes the object type
    231                         + "isIncoming: %s, "
    232                         + "contactLookup: %s, "
    233                         + "callInitiation: %s, "
    234                         + "duration: %s"
    235                         + "]",
    236                     disconnectCause,
    237                     isIncoming,
    238                     lookupToString(contactLookupResult),
    239                     initiationToString(callInitiationMethod),
    240                     duration);
    241         }
    242 
    243         private static String lookupToString(int lookupType) {
    244             switch (lookupType) {
    245                 case LOOKUP_LOCAL_CONTACT:
    246                     return "Local";
    247                 case LOOKUP_LOCAL_CACHE:
    248                     return "Cache";
    249                 case LOOKUP_REMOTE_CONTACT:
    250                     return "Remote";
    251                 case LOOKUP_EMERGENCY:
    252                     return "Emergency";
    253                 case LOOKUP_VOICEMAIL:
    254                     return "Voicemail";
    255                 default:
    256                     return "Not found";
    257             }
    258         }
    259 
    260         private static String initiationToString(int initiationType) {
    261             switch (initiationType) {
    262                 case INITIATION_INCOMING:
    263                     return "Incoming";
    264                 case INITIATION_DIALPAD:
    265                     return "Dialpad";
    266                 case INITIATION_SPEED_DIAL:
    267                     return "Speed Dial";
    268                 case INITIATION_REMOTE_DIRECTORY:
    269                     return "Remote Directory";
    270                 case INITIATION_SMART_DIAL:
    271                     return "Smart Dial";
    272                 case INITIATION_REGULAR_SEARCH:
    273                     return "Regular Search";
    274                 case INITIATION_CALL_LOG:
    275                     return "Call Log";
    276                 case INITIATION_CALL_LOG_FILTER:
    277                     return "Call Log Filter";
    278                 case INITIATION_VOICEMAIL_LOG:
    279                     return "Voicemail Log";
    280                 case INITIATION_CALL_DETAILS:
    281                     return "Call Details";
    282                 case INITIATION_QUICK_CONTACTS:
    283                     return "Quick Contacts";
    284                 default:
    285                     return "Unknown";
    286             }
    287         }
    288     }
    289 
    290 
    291     private static final String ID_PREFIX = Call.class.getSimpleName() + "_";
    292     private static int sIdCounter = 0;
    293 
    294     private final android.telecom.Call.Callback mTelecomCallCallback =
    295         new android.telecom.Call.Callback() {
    296             @Override
    297             public void onStateChanged(android.telecom.Call call, int newState) {
    298                 Log.d(this, "TelecomCallCallback onStateChanged call=" + call + " newState="
    299                         + newState);
    300                 update();
    301             }
    302 
    303             @Override
    304             public void onParentChanged(android.telecom.Call call,
    305                     android.telecom.Call newParent) {
    306                 Log.d(this, "TelecomCallCallback onParentChanged call=" + call + " newParent="
    307                         + newParent);
    308                 update();
    309             }
    310 
    311             @Override
    312             public void onChildrenChanged(android.telecom.Call call,
    313                     List<android.telecom.Call> children) {
    314                 update();
    315             }
    316 
    317             @Override
    318             public void onDetailsChanged(android.telecom.Call call,
    319                     android.telecom.Call.Details details) {
    320                 Log.d(this, "TelecomCallCallback onStateChanged call=" + call + " details="
    321                         + details);
    322                 update();
    323             }
    324 
    325             @Override
    326             public void onCannedTextResponsesLoaded(android.telecom.Call call,
    327                     List<String> cannedTextResponses) {
    328                 Log.d(this, "TelecomCallCallback onStateChanged call=" + call
    329                         + " cannedTextResponses=" + cannedTextResponses);
    330                 update();
    331             }
    332 
    333             @Override
    334             public void onPostDialWait(android.telecom.Call call,
    335                     String remainingPostDialSequence) {
    336                 Log.d(this, "TelecomCallCallback onStateChanged call=" + call
    337                         + " remainingPostDialSequence=" + remainingPostDialSequence);
    338                 update();
    339             }
    340 
    341             @Override
    342             public void onVideoCallChanged(android.telecom.Call call,
    343                     VideoCall videoCall) {
    344                 Log.d(this, "TelecomCallCallback onStateChanged call=" + call + " videoCall="
    345                         + videoCall);
    346                 update();
    347             }
    348 
    349             @Override
    350             public void onCallDestroyed(android.telecom.Call call) {
    351                 Log.d(this, "TelecomCallCallback onStateChanged call=" + call);
    352                 call.unregisterCallback(this);
    353             }
    354 
    355             @Override
    356             public void onConferenceableCallsChanged(android.telecom.Call call,
    357                     List<android.telecom.Call> conferenceableCalls) {
    358                 update();
    359             }
    360     };
    361 
    362     private android.telecom.Call mTelecomCall;
    363     private boolean mIsEmergencyCall;
    364     private Uri mHandle;
    365     private final String mId;
    366     private int mState = State.INVALID;
    367     private DisconnectCause mDisconnectCause;
    368     private int mSessionModificationState;
    369     private final List<String> mChildCallIds = new ArrayList<>();
    370     private final VideoSettings mVideoSettings = new VideoSettings();
    371     private int mVideoState;
    372 
    373     /**
    374      * mRequestedVideoState is used to store requested upgrade / downgrade video state
    375      */
    376     private int mRequestedVideoState = VideoProfile.STATE_AUDIO_ONLY;
    377 
    378     private InCallVideoCallCallback mVideoCallCallback;
    379     private boolean mIsVideoCallCallbackRegistered;
    380     private String mChildNumber;
    381     private String mLastForwardedNumber;
    382     private String mCallSubject;
    383     private PhoneAccountHandle mPhoneAccountHandle;
    384 
    385     /**
    386      * Indicates whether the phone account associated with this call supports specifying a call
    387      * subject.
    388      */
    389     private boolean mIsCallSubjectSupported;
    390 
    391     private long mTimeAddedMs;
    392 
    393     private LogState mLogState = new LogState();
    394 
    395     /**
    396      * Used only to create mock calls for testing
    397      */
    398     @NeededForTesting
    399     Call(int state) {
    400         mTelecomCall = null;
    401         mId = ID_PREFIX + Integer.toString(sIdCounter++);
    402         setState(state);
    403     }
    404 
    405     /**
    406      * Creates a new instance of a {@link Call}.  Registers a callback for
    407      * {@link android.telecom.Call} events.
    408      */
    409     public Call(android.telecom.Call telecomCall) {
    410         this(telecomCall, true /* registerCallback */);
    411     }
    412 
    413     /**
    414      * Creates a new instance of a {@link Call}.  Optionally registers a callback for
    415      * {@link android.telecom.Call} events.
    416      *
    417      * Intended for use when creating a {@link Call} instance for use with the
    418      * {@link ContactInfoCache}, where we do not want to register callbacks for the new call.
    419      */
    420     public Call(android.telecom.Call telecomCall, boolean registerCallback) {
    421         mTelecomCall = telecomCall;
    422         mId = ID_PREFIX + Integer.toString(sIdCounter++);
    423 
    424         updateFromTelecomCall(registerCallback);
    425 
    426         if (registerCallback) {
    427             mTelecomCall.registerCallback(mTelecomCallCallback);
    428         }
    429 
    430         mTimeAddedMs = System.currentTimeMillis();
    431     }
    432 
    433     public android.telecom.Call getTelecomCall() {
    434         return mTelecomCall;
    435     }
    436 
    437     /**
    438      * @return video settings of the call, null if the call is not a video call.
    439      * @see VideoProfile
    440      */
    441     public VideoSettings getVideoSettings() {
    442         return mVideoSettings;
    443     }
    444 
    445     private void update() {
    446         Trace.beginSection("Update");
    447         int oldState = getState();
    448         // We want to potentially register a video call callback here.
    449         updateFromTelecomCall(true /* registerCallback */);
    450         if (oldState != getState() && getState() == Call.State.DISCONNECTED) {
    451             CallList.getInstance().onDisconnect(this);
    452         } else {
    453             CallList.getInstance().onUpdate(this);
    454         }
    455         Trace.endSection();
    456     }
    457 
    458     private void updateFromTelecomCall(boolean registerCallback) {
    459         Log.d(this, "updateFromTelecomCall: " + mTelecomCall.toString());
    460         final int translatedState = translateState(mTelecomCall.getState());
    461         if (mState != State.BLOCKED) {
    462             setState(translatedState);
    463             setDisconnectCause(mTelecomCall.getDetails().getDisconnectCause());
    464             maybeCancelVideoUpgrade(mTelecomCall.getDetails().getVideoState());
    465         }
    466 
    467         if (registerCallback && mTelecomCall.getVideoCall() != null) {
    468             if (mVideoCallCallback == null) {
    469                 mVideoCallCallback = new InCallVideoCallCallback(this);
    470             }
    471             mTelecomCall.getVideoCall().registerCallback(mVideoCallCallback);
    472             mIsVideoCallCallbackRegistered = true;
    473         }
    474 
    475         mChildCallIds.clear();
    476         final int numChildCalls = mTelecomCall.getChildren().size();
    477         for (int i = 0; i < numChildCalls; i++) {
    478             mChildCallIds.add(
    479                     CallList.getInstance().getCallByTelecomCall(
    480                             mTelecomCall.getChildren().get(i)).getId());
    481         }
    482 
    483         // The number of conferenced calls can change over the course of the call, so use the
    484         // maximum number of conferenced child calls as the metric for conference call usage.
    485         mLogState.conferencedCalls = Math.max(numChildCalls, mLogState.conferencedCalls);
    486 
    487         updateFromCallExtras(mTelecomCall.getDetails().getExtras());
    488 
    489         // If the handle of the call has changed, update state for the call determining if it is an
    490         // emergency call.
    491         Uri newHandle = mTelecomCall.getDetails().getHandle();
    492         if (!Objects.equals(mHandle, newHandle)) {
    493             mHandle = newHandle;
    494             updateEmergencyCallState();
    495         }
    496 
    497         // If the phone account handle of the call is set, cache capability bit indicating whether
    498         // the phone account supports call subjects.
    499         PhoneAccountHandle newPhoneAccountHandle = mTelecomCall.getDetails().getAccountHandle();
    500         if (!Objects.equals(mPhoneAccountHandle, newPhoneAccountHandle)) {
    501             mPhoneAccountHandle = newPhoneAccountHandle;
    502 
    503             if (mPhoneAccountHandle != null) {
    504                 TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager();
    505                 PhoneAccount phoneAccount =
    506                         TelecomManagerCompat.getPhoneAccount(mgr, mPhoneAccountHandle);
    507                 if (phoneAccount != null) {
    508                     mIsCallSubjectSupported = phoneAccount.hasCapabilities(
    509                             PhoneAccount.CAPABILITY_CALL_SUBJECT);
    510                 }
    511             }
    512         }
    513     }
    514 
    515     /**
    516      * Tests corruption of the {@code callExtras} bundle by calling {@link
    517      * Bundle#containsKey(String)}. If the bundle is corrupted a {@link IllegalArgumentException}
    518      * will be thrown and caught by this function.
    519      *
    520      * @param callExtras the bundle to verify
    521      * @returns {@code true} if the bundle is corrupted, {@code false} otherwise.
    522      */
    523     protected boolean areCallExtrasCorrupted(Bundle callExtras) {
    524         /**
    525          * There's currently a bug in Telephony service (b/25613098) that could corrupt the
    526          * extras bundle, resulting in a IllegalArgumentException while validating data under
    527          * {@link Bundle#containsKey(String)}.
    528          */
    529         try {
    530             callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS);
    531             return false;
    532         } catch (IllegalArgumentException e) {
    533             Log.e(this, "CallExtras is corrupted, ignoring exception", e);
    534             return true;
    535         }
    536     }
    537 
    538     protected void updateFromCallExtras(Bundle callExtras) {
    539         if (callExtras == null || areCallExtrasCorrupted(callExtras)) {
    540             /**
    541              * If the bundle is corrupted, abandon information update as a work around. These are
    542              * not critical for the dialer to function.
    543              */
    544             return;
    545         }
    546         // Check for a change in the child address and notify any listeners.
    547         if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) {
    548             String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS);
    549             if (!Objects.equals(childNumber, mChildNumber)) {
    550                 mChildNumber = childNumber;
    551                 CallList.getInstance().onChildNumberChange(this);
    552             }
    553         }
    554 
    555         // Last forwarded number comes in as an array of strings.  We want to choose the
    556         // last item in the array.  The forwarding numbers arrive independently of when the
    557         // call is originally set up, so we need to notify the the UI of the change.
    558         if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) {
    559             ArrayList<String> lastForwardedNumbers =
    560                     callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER);
    561 
    562             if (lastForwardedNumbers != null) {
    563                 String lastForwardedNumber = null;
    564                 if (!lastForwardedNumbers.isEmpty()) {
    565                     lastForwardedNumber = lastForwardedNumbers.get(
    566                             lastForwardedNumbers.size() - 1);
    567                 }
    568 
    569                 if (!Objects.equals(lastForwardedNumber, mLastForwardedNumber)) {
    570                     mLastForwardedNumber = lastForwardedNumber;
    571                     CallList.getInstance().onLastForwardedNumberChange(this);
    572                 }
    573             }
    574         }
    575 
    576         // Call subject is present in the extras at the start of call, so we do not need to
    577         // notify any other listeners of this.
    578         if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) {
    579             String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT);
    580             if (!Objects.equals(mCallSubject, callSubject)) {
    581                 mCallSubject = callSubject;
    582             }
    583         }
    584     }
    585 
    586     /**
    587      * Determines if a received upgrade to video request should be cancelled.  This can happen if
    588      * another InCall UI responds to the upgrade to video request.
    589      *
    590      * @param newVideoState The new video state.
    591      */
    592     private void maybeCancelVideoUpgrade(int newVideoState) {
    593         boolean isVideoStateChanged = mVideoState != newVideoState;
    594 
    595         if (mSessionModificationState == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST
    596                 && isVideoStateChanged) {
    597 
    598             Log.v(this, "maybeCancelVideoUpgrade : cancelling upgrade notification");
    599             setSessionModificationState(SessionModificationState.NO_REQUEST);
    600         }
    601         mVideoState = newVideoState;
    602     }
    603     private static int translateState(int state) {
    604         switch (state) {
    605             case android.telecom.Call.STATE_NEW:
    606             case android.telecom.Call.STATE_CONNECTING:
    607                 return Call.State.CONNECTING;
    608             case android.telecom.Call.STATE_SELECT_PHONE_ACCOUNT:
    609                 return Call.State.SELECT_PHONE_ACCOUNT;
    610             case android.telecom.Call.STATE_DIALING:
    611                 return Call.State.DIALING;
    612             case android.telecom.Call.STATE_RINGING:
    613                 return Call.State.INCOMING;
    614             case android.telecom.Call.STATE_ACTIVE:
    615                 return Call.State.ACTIVE;
    616             case android.telecom.Call.STATE_HOLDING:
    617                 return Call.State.ONHOLD;
    618             case android.telecom.Call.STATE_DISCONNECTED:
    619                 return Call.State.DISCONNECTED;
    620             case android.telecom.Call.STATE_DISCONNECTING:
    621                 return Call.State.DISCONNECTING;
    622             default:
    623                 return Call.State.INVALID;
    624         }
    625     }
    626 
    627     public String getId() {
    628         return mId;
    629     }
    630 
    631     public long getTimeAddedMs() {
    632         return mTimeAddedMs;
    633     }
    634 
    635     public String getNumber() {
    636         return TelecomCallUtil.getNumber(mTelecomCall);
    637     }
    638 
    639     public void blockCall() {
    640         mTelecomCall.reject(false, null);
    641         setState(State.BLOCKED);
    642     }
    643 
    644     public Uri getHandle() {
    645         return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandle();
    646     }
    647 
    648     public boolean isEmergencyCall() {
    649         return mIsEmergencyCall;
    650     }
    651 
    652     public int getState() {
    653         if (mTelecomCall != null && mTelecomCall.getParent() != null) {
    654             return State.CONFERENCED;
    655         } else {
    656             return mState;
    657         }
    658     }
    659 
    660     public void setState(int state) {
    661         mState = state;
    662         if (mState == State.INCOMING) {
    663             mLogState.isIncoming = true;
    664         } else if (mState == State.DISCONNECTED) {
    665             mLogState.duration = getConnectTimeMillis() == 0 ?
    666                     0: System.currentTimeMillis() - getConnectTimeMillis();
    667         }
    668     }
    669 
    670     public int getNumberPresentation() {
    671         return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandlePresentation();
    672     }
    673 
    674     public int getCnapNamePresentation() {
    675         return mTelecomCall == null ? null
    676                 : mTelecomCall.getDetails().getCallerDisplayNamePresentation();
    677     }
    678 
    679     public String getCnapName() {
    680         return mTelecomCall == null ? null
    681                 : getTelecomCall().getDetails().getCallerDisplayName();
    682     }
    683 
    684     public Bundle getIntentExtras() {
    685         return mTelecomCall.getDetails().getIntentExtras();
    686     }
    687 
    688     public Bundle getExtras() {
    689         return mTelecomCall == null ? null : mTelecomCall.getDetails().getExtras();
    690     }
    691 
    692     /**
    693      * @return The child number for the call, or {@code null} if none specified.
    694      */
    695     public String getChildNumber() {
    696         return mChildNumber;
    697     }
    698 
    699     /**
    700      * @return The last forwarded number for the call, or {@code null} if none specified.
    701      */
    702     public String getLastForwardedNumber() {
    703         return mLastForwardedNumber;
    704     }
    705 
    706     /**
    707      * @return The call subject, or {@code null} if none specified.
    708      */
    709     public String getCallSubject() {
    710         return mCallSubject;
    711     }
    712 
    713     /**
    714      * @return {@code true} if the call's phone account supports call subjects, {@code false}
    715      *      otherwise.
    716      */
    717     public boolean isCallSubjectSupported() {
    718         return mIsCallSubjectSupported;
    719     }
    720 
    721     /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
    722     public DisconnectCause getDisconnectCause() {
    723         if (mState == State.DISCONNECTED || mState == State.IDLE) {
    724             return mDisconnectCause;
    725         }
    726 
    727         return new DisconnectCause(DisconnectCause.UNKNOWN);
    728     }
    729 
    730     public void setDisconnectCause(DisconnectCause disconnectCause) {
    731         mDisconnectCause = disconnectCause;
    732         mLogState.disconnectCause = mDisconnectCause;
    733     }
    734 
    735     /** Returns the possible text message responses. */
    736     public List<String> getCannedSmsResponses() {
    737         return mTelecomCall.getCannedTextResponses();
    738     }
    739 
    740     /** Checks if the call supports the given set of capabilities supplied as a bit mask. */
    741     public boolean can(int capabilities) {
    742         int supportedCapabilities = mTelecomCall.getDetails().getCallCapabilities();
    743 
    744         if ((capabilities & android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) {
    745             // We allow you to merge if the capabilities allow it or if it is a call with
    746             // conferenceable calls.
    747             if (mTelecomCall.getConferenceableCalls().isEmpty() &&
    748                 ((android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE
    749                         & supportedCapabilities) == 0)) {
    750                 // Cannot merge calls if there are no calls to merge with.
    751                 return false;
    752             }
    753             capabilities &= ~android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE;
    754         }
    755         return (capabilities == (capabilities & mTelecomCall.getDetails().getCallCapabilities()));
    756     }
    757 
    758     public boolean hasProperty(int property) {
    759         return mTelecomCall.getDetails().hasProperty(property);
    760     }
    761 
    762     /** Gets the time when the call first became active. */
    763     public long getConnectTimeMillis() {
    764         return mTelecomCall.getDetails().getConnectTimeMillis();
    765     }
    766 
    767     public boolean isConferenceCall() {
    768         return hasProperty(android.telecom.Call.Details.PROPERTY_CONFERENCE);
    769     }
    770 
    771     public GatewayInfo getGatewayInfo() {
    772         return mTelecomCall == null ? null : mTelecomCall.getDetails().getGatewayInfo();
    773     }
    774 
    775     public PhoneAccountHandle getAccountHandle() {
    776         return mTelecomCall == null ? null : mTelecomCall.getDetails().getAccountHandle();
    777     }
    778 
    779     /**
    780      * @return The {@link VideoCall} instance associated with the {@link android.telecom.Call}.
    781      *      Will return {@code null} until {@link #updateFromTelecomCall()} has registered a valid
    782      *      callback on the {@link VideoCall}.
    783      */
    784     public VideoCall getVideoCall() {
    785         return mTelecomCall == null || !mIsVideoCallCallbackRegistered ? null
    786                 : mTelecomCall.getVideoCall();
    787     }
    788 
    789     public List<String> getChildCallIds() {
    790         return mChildCallIds;
    791     }
    792 
    793     public String getParentId() {
    794         android.telecom.Call parentCall = mTelecomCall.getParent();
    795         if (parentCall != null) {
    796             return CallList.getInstance().getCallByTelecomCall(parentCall).getId();
    797         }
    798         return null;
    799     }
    800 
    801     public int getVideoState() {
    802         return mTelecomCall.getDetails().getVideoState();
    803     }
    804 
    805     public boolean isVideoCall(Context context) {
    806         return CallUtil.isVideoEnabled(context) &&
    807                 VideoUtils.isVideoCall(getVideoState());
    808     }
    809 
    810     /**
    811      * Handles incoming session modification requests.  Stores the pending video request and sets
    812      * the session modification state to
    813      * {@link SessionModificationState#RECEIVED_UPGRADE_TO_VIDEO_REQUEST} so that we can keep track
    814      * of the fact the request was received.  Only upgrade requests require user confirmation and
    815      * will be handled by this method.  The remote user can turn off their own camera without
    816      * confirmation.
    817      *
    818      * @param videoState The requested video state.
    819      */
    820     public void setRequestedVideoState(int videoState) {
    821         Log.d(this, "setRequestedVideoState - video state= " + videoState);
    822         if (videoState == getVideoState()) {
    823             mSessionModificationState = Call.SessionModificationState.NO_REQUEST;
    824             Log.w(this,"setRequestedVideoState - Clearing session modification state");
    825             return;
    826         }
    827 
    828         mSessionModificationState = Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
    829         mRequestedVideoState = videoState;
    830         CallList.getInstance().onUpgradeToVideo(this);
    831 
    832         Log.d(this, "setRequestedVideoState - mSessionModificationState="
    833             + mSessionModificationState + " video state= " + videoState);
    834         update();
    835     }
    836 
    837     /**
    838      * Set the session modification state.  Used to keep track of pending video session modification
    839      * operations and to inform listeners of these changes.
    840      *
    841      * @param state the new session modification state.
    842      */
    843     public void setSessionModificationState(int state) {
    844         boolean hasChanged = mSessionModificationState != state;
    845         mSessionModificationState = state;
    846         Log.d(this, "setSessionModificationState " + state + " mSessionModificationState="
    847                 + mSessionModificationState);
    848         if (hasChanged) {
    849             CallList.getInstance().onSessionModificationStateChange(this, state);
    850         }
    851     }
    852 
    853     /**
    854      * Determines if the call handle is an emergency number or not and caches the result to avoid
    855      * repeated calls to isEmergencyNumber.
    856      */
    857     private void updateEmergencyCallState() {
    858         mIsEmergencyCall = TelecomCallUtil.isEmergencyCall(mTelecomCall);
    859     }
    860 
    861     /**
    862      * Gets the video state which was requested via a session modification request.
    863      *
    864      * @return The video state.
    865      */
    866     public int getRequestedVideoState() {
    867         return mRequestedVideoState;
    868     }
    869 
    870     public static boolean areSame(Call call1, Call call2) {
    871         if (call1 == null && call2 == null) {
    872             return true;
    873         } else if (call1 == null || call2 == null) {
    874             return false;
    875         }
    876 
    877         // otherwise compare call Ids
    878         return call1.getId().equals(call2.getId());
    879     }
    880 
    881     public static boolean areSameNumber(Call call1, Call call2) {
    882         if (call1 == null && call2 == null) {
    883             return true;
    884         } else if (call1 == null || call2 == null) {
    885             return false;
    886         }
    887 
    888         // otherwise compare call Numbers
    889         return TextUtils.equals(call1.getNumber(), call2.getNumber());
    890     }
    891 
    892     /**
    893      *  Gets the current video session modification state.
    894      *
    895      * @return The session modification state.
    896      */
    897     public int getSessionModificationState() {
    898         return mSessionModificationState;
    899     }
    900 
    901     public LogState getLogState() {
    902         return mLogState;
    903     }
    904 
    905     /**
    906      * Determines if the call is an external call.
    907      *
    908      * An external call is one which does not exist locally for the
    909      * {@link android.telecom.ConnectionService} it is associated with.
    910      *
    911      * External calls are only supported in N and higher.
    912      *
    913      * @return {@code true} if the call is an external call, {@code false} otherwise.
    914      */
    915     public boolean isExternalCall() {
    916         return CompatUtils.isNCompatible() &&
    917                 hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL);
    918     }
    919 
    920     /**
    921      * Determines if the external call is pullable.
    922      *
    923      * An external call is one which does not exist locally for the
    924      * {@link android.telecom.ConnectionService} it is associated with.  An external call may be
    925      * "pullable", which means that the user can request it be transferred to the current device.
    926      *
    927      * External calls are only supported in N and higher.
    928      *
    929      * @return {@code true} if the call is an external call, {@code false} otherwise.
    930      */
    931     public boolean isPullableExternalCall() {
    932         return CompatUtils.isNCompatible() &&
    933                 (mTelecomCall.getDetails().getCallCapabilities()
    934                         & CallSdkCompat.Details.CAPABILITY_CAN_PULL_CALL)
    935                         == CallSdkCompat.Details.CAPABILITY_CAN_PULL_CALL;
    936     }
    937 
    938     /**
    939      * Logging utility methods
    940      */
    941     public void logCallInitiationType() {
    942         if (isExternalCall()) {
    943             return;
    944         }
    945 
    946         if (getState() == State.INCOMING) {
    947             getLogState().callInitiationMethod = LogState.INITIATION_INCOMING;
    948         } else if (getIntentExtras() != null) {
    949             getLogState().callInitiationMethod =
    950                 getIntentExtras().getInt(IntentUtil.EXTRA_CALL_INITIATION_TYPE,
    951                         LogState.INITIATION_EXTERNAL);
    952         }
    953     }
    954 
    955     @Override
    956     public String toString() {
    957         if (mTelecomCall == null) {
    958             // This should happen only in testing since otherwise we would never have a null
    959             // Telecom call.
    960             return String.valueOf(mId);
    961         }
    962 
    963         return String.format(Locale.US, "[%s, %s, %s, %s, children:%s, parent:%s, " +
    964                 "conferenceable:%s, videoState:%s, mSessionModificationState:%d, VideoSettings:%s]",
    965                 mId,
    966                 State.toString(getState()),
    967                 Details.capabilitiesToString(mTelecomCall.getDetails().getCallCapabilities()),
    968                 Details.propertiesToString(mTelecomCall.getDetails().getCallProperties()),
    969                 mChildCallIds,
    970                 getParentId(),
    971                 this.mTelecomCall.getConferenceableCalls(),
    972                 VideoProfile.videoStateToString(mTelecomCall.getDetails().getVideoState()),
    973                 mSessionModificationState,
    974                 getVideoSettings());
    975     }
    976 
    977     public String toSimpleString() {
    978         return super.toString();
    979     }
    980 }
    981