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 com.android.contacts.common.CallUtil;
     20 import com.android.contacts.common.testing.NeededForTesting;
     21 import com.android.incallui.CallList.Listener;
     22 
     23 import android.content.Context;
     24 import android.hardware.camera2.CameraCharacteristics;
     25 import android.net.Uri;
     26 import android.os.Bundle;
     27 import android.os.Trace;
     28 import android.telecom.DisconnectCause;
     29 import android.telecom.GatewayInfo;
     30 import android.telecom.InCallService.VideoCall;
     31 import android.telecom.PhoneAccountHandle;
     32 import android.telecom.VideoProfile;
     33 import android.text.TextUtils;
     34 
     35 import java.util.ArrayList;
     36 import java.util.List;
     37 import java.util.Locale;
     38 
     39 /**
     40  * Describes a single call and its state.
     41  */
     42 @NeededForTesting
     43 public class Call {
     44     /* Defines different states of this call */
     45     public static class State {
     46         public static final int INVALID = 0;
     47         public static final int NEW = 1;            /* The call is new. */
     48         public static final int IDLE = 2;           /* The call is idle.  Nothing active */
     49         public static final int ACTIVE = 3;         /* There is an active call */
     50         public static final int INCOMING = 4;       /* A normal incoming phone call */
     51         public static final int CALL_WAITING = 5;   /* Incoming call while another is active */
     52         public static final int DIALING = 6;        /* An outgoing call during dial phase */
     53         public static final int REDIALING = 7;      /* Subsequent dialing attempt after a failure */
     54         public static final int ONHOLD = 8;         /* An active phone call placed on hold */
     55         public static final int DISCONNECTING = 9;  /* A call is being ended. */
     56         public static final int DISCONNECTED = 10;  /* State after a call disconnects */
     57         public static final int CONFERENCED = 11;   /* Call part of a conference call */
     58         public static final int SELECT_PHONE_ACCOUNT = 12; /* Waiting for account selection */
     59         public static final int CONNECTING = 13;    /* Waiting for Telecomm broadcast to finish */
     60 
     61 
     62         public static boolean isConnectingOrConnected(int state) {
     63             switch(state) {
     64                 case ACTIVE:
     65                 case INCOMING:
     66                 case CALL_WAITING:
     67                 case CONNECTING:
     68                 case DIALING:
     69                 case REDIALING:
     70                 case ONHOLD:
     71                 case CONFERENCED:
     72                     return true;
     73                 default:
     74             }
     75             return false;
     76         }
     77 
     78         public static boolean isDialing(int state) {
     79             return state == DIALING || state == REDIALING;
     80         }
     81 
     82         public static String toString(int state) {
     83             switch (state) {
     84                 case INVALID:
     85                     return "INVALID";
     86                 case NEW:
     87                     return "NEW";
     88                 case IDLE:
     89                     return "IDLE";
     90                 case ACTIVE:
     91                     return "ACTIVE";
     92                 case INCOMING:
     93                     return "INCOMING";
     94                 case CALL_WAITING:
     95                     return "CALL_WAITING";
     96                 case DIALING:
     97                     return "DIALING";
     98                 case REDIALING:
     99                     return "REDIALING";
    100                 case ONHOLD:
    101                     return "ONHOLD";
    102                 case DISCONNECTING:
    103                     return "DISCONNECTING";
    104                 case DISCONNECTED:
    105                     return "DISCONNECTED";
    106                 case CONFERENCED:
    107                     return "CONFERENCED";
    108                 case SELECT_PHONE_ACCOUNT:
    109                     return "SELECT_PHONE_ACCOUNT";
    110                 case CONNECTING:
    111                     return "CONNECTING";
    112                 default:
    113                     return "UNKNOWN";
    114             }
    115         }
    116     }
    117 
    118     /**
    119      * Defines different states of session modify requests, which are used to upgrade to video, or
    120      * downgrade to audio.
    121      */
    122     public static class SessionModificationState {
    123         public static final int NO_REQUEST = 0;
    124         public static final int WAITING_FOR_RESPONSE = 1;
    125         public static final int REQUEST_FAILED = 2;
    126         public static final int RECEIVED_UPGRADE_TO_VIDEO_REQUEST = 3;
    127         public static final int UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT = 4;
    128         public static final int REQUEST_REJECTED = 5;
    129     }
    130 
    131     public static class VideoSettings {
    132         public static final int CAMERA_DIRECTION_UNKNOWN = -1;
    133         public static final int CAMERA_DIRECTION_FRONT_FACING =
    134                 CameraCharacteristics.LENS_FACING_FRONT;
    135         public static final int CAMERA_DIRECTION_BACK_FACING =
    136                 CameraCharacteristics.LENS_FACING_BACK;
    137 
    138         private int mCameraDirection = CAMERA_DIRECTION_UNKNOWN;
    139 
    140         /**
    141          * Sets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN,
    142          * the video state of the call should be used to infer the camera direction.
    143          *
    144          * @see {@link CameraCharacteristics#LENS_FACING_FRONT}
    145          * @see {@link CameraCharacteristics#LENS_FACING_BACK}
    146          */
    147         public void setCameraDir(int cameraDirection) {
    148             if (cameraDirection == CAMERA_DIRECTION_FRONT_FACING
    149                || cameraDirection == CAMERA_DIRECTION_BACK_FACING) {
    150                 mCameraDirection = cameraDirection;
    151             } else {
    152                 mCameraDirection = CAMERA_DIRECTION_UNKNOWN;
    153             }
    154         }
    155 
    156         /**
    157          * Gets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN,
    158          * the video state of the call should be used to infer the camera direction.
    159          *
    160          * @see {@link CameraCharacteristics#LENS_FACING_FRONT}
    161          * @see {@link CameraCharacteristics#LENS_FACING_BACK}
    162          */
    163         public int getCameraDir() {
    164             return mCameraDirection;
    165         }
    166 
    167         public String toString() {
    168             return "(CameraDir:" + getCameraDir() + ")";
    169         }
    170     }
    171 
    172 
    173     private static final String ID_PREFIX = Call.class.getSimpleName() + "_";
    174     private static int sIdCounter = 0;
    175 
    176     private android.telecom.Call.Callback mTelecomCallCallback =
    177             new android.telecom.Call.Callback() {
    178                 @Override
    179                 public void onStateChanged(android.telecom.Call call, int newState) {
    180                     Log.d(this, "TelecommCallCallback onStateChanged call=" + call + " newState="
    181                             + newState);
    182                     update();
    183                 }
    184 
    185                 @Override
    186                 public void onParentChanged(android.telecom.Call call,
    187                         android.telecom.Call newParent) {
    188                     Log.d(this, "TelecommCallCallback onParentChanged call=" + call + " newParent="
    189                             + newParent);
    190                     update();
    191                 }
    192 
    193                 @Override
    194                 public void onChildrenChanged(android.telecom.Call call,
    195                         List<android.telecom.Call> children) {
    196                     update();
    197                 }
    198 
    199                 @Override
    200                 public void onDetailsChanged(android.telecom.Call call,
    201                         android.telecom.Call.Details details) {
    202                     Log.d(this, "TelecommCallCallback onStateChanged call=" + call + " details="
    203                             + details);
    204                     update();
    205                 }
    206 
    207                 @Override
    208                 public void onCannedTextResponsesLoaded(android.telecom.Call call,
    209                         List<String> cannedTextResponses) {
    210                     Log.d(this, "TelecommCallCallback onStateChanged call=" + call
    211                             + " cannedTextResponses=" + cannedTextResponses);
    212                     update();
    213                 }
    214 
    215                 @Override
    216                 public void onPostDialWait(android.telecom.Call call,
    217                         String remainingPostDialSequence) {
    218                     Log.d(this, "TelecommCallCallback onStateChanged call=" + call
    219                             + " remainingPostDialSequence=" + remainingPostDialSequence);
    220                     update();
    221                 }
    222 
    223                 @Override
    224                 public void onVideoCallChanged(android.telecom.Call call,
    225                         VideoCall videoCall) {
    226                     Log.d(this, "TelecommCallCallback onStateChanged call=" + call + " videoCall="
    227                             + videoCall);
    228                     update();
    229                 }
    230 
    231                 @Override
    232                 public void onCallDestroyed(android.telecom.Call call) {
    233                     Log.d(this, "TelecommCallCallback onStateChanged call=" + call);
    234                     call.unregisterCallback(mTelecomCallCallback);
    235                 }
    236 
    237                 @Override
    238                 public void onConferenceableCallsChanged(android.telecom.Call call,
    239                         List<android.telecom.Call> conferenceableCalls) {
    240                     update();
    241                 }
    242             };
    243 
    244     private android.telecom.Call mTelecommCall;
    245     private final String mId;
    246     private int mState = State.INVALID;
    247     private DisconnectCause mDisconnectCause;
    248     private int mSessionModificationState;
    249     private final List<String> mChildCallIds = new ArrayList<>();
    250     private final VideoSettings mVideoSettings = new VideoSettings();
    251     /**
    252      * mModifyToVideoState is used to store requested upgrade / downgrade video state
    253      */
    254     private int mModifyToVideoState = VideoProfile.STATE_AUDIO_ONLY;
    255 
    256     private InCallVideoCallCallback mVideoCallCallback;
    257 
    258     /**
    259      * Used only to create mock calls for testing
    260      */
    261     @NeededForTesting
    262     Call(int state) {
    263         mTelecommCall = null;
    264         mId = ID_PREFIX + Integer.toString(sIdCounter++);
    265         setState(state);
    266     }
    267 
    268     public Call(android.telecom.Call telecommCall) {
    269         mTelecommCall = telecommCall;
    270         mId = ID_PREFIX + Integer.toString(sIdCounter++);
    271         updateFromTelecommCall();
    272         mTelecommCall.registerCallback(mTelecomCallCallback);
    273     }
    274 
    275     public android.telecom.Call getTelecommCall() {
    276         return mTelecommCall;
    277     }
    278 
    279     /**
    280      * @return video settings of the call, null if the call is not a video call.
    281      * @see VideoProfile
    282      */
    283     public VideoSettings getVideoSettings() {
    284         return mVideoSettings;
    285     }
    286 
    287     private void update() {
    288         Trace.beginSection("Update");
    289         int oldState = getState();
    290         updateFromTelecommCall();
    291         if (oldState != getState() && getState() == Call.State.DISCONNECTED) {
    292             CallList.getInstance().onDisconnect(this);
    293         } else {
    294             CallList.getInstance().onUpdate(this);
    295         }
    296         Trace.endSection();
    297     }
    298 
    299     private void updateFromTelecommCall() {
    300         Log.d(this, "updateFromTelecommCall: " + mTelecommCall.toString());
    301         setState(translateState(mTelecommCall.getState()));
    302         setDisconnectCause(mTelecommCall.getDetails().getDisconnectCause());
    303 
    304         if (mTelecommCall.getVideoCall() != null) {
    305             if (mVideoCallCallback == null) {
    306                 mVideoCallCallback = new InCallVideoCallCallback(this);
    307             }
    308             mTelecommCall.getVideoCall().registerCallback(mVideoCallCallback);
    309         }
    310 
    311         mChildCallIds.clear();
    312         for (int i = 0; i < mTelecommCall.getChildren().size(); i++) {
    313             mChildCallIds.add(
    314                     CallList.getInstance().getCallByTelecommCall(
    315                             mTelecommCall.getChildren().get(i)).getId());
    316         }
    317     }
    318 
    319     private static int translateState(int state) {
    320         switch (state) {
    321             case android.telecom.Call.STATE_NEW:
    322             case android.telecom.Call.STATE_CONNECTING:
    323                 return Call.State.CONNECTING;
    324             case android.telecom.Call.STATE_SELECT_PHONE_ACCOUNT:
    325                 return Call.State.SELECT_PHONE_ACCOUNT;
    326             case android.telecom.Call.STATE_DIALING:
    327                 return Call.State.DIALING;
    328             case android.telecom.Call.STATE_RINGING:
    329                 return Call.State.INCOMING;
    330             case android.telecom.Call.STATE_ACTIVE:
    331                 return Call.State.ACTIVE;
    332             case android.telecom.Call.STATE_HOLDING:
    333                 return Call.State.ONHOLD;
    334             case android.telecom.Call.STATE_DISCONNECTED:
    335                 return Call.State.DISCONNECTED;
    336             case android.telecom.Call.STATE_DISCONNECTING:
    337                 return Call.State.DISCONNECTING;
    338             default:
    339                 return Call.State.INVALID;
    340         }
    341     }
    342 
    343     public String getId() {
    344         return mId;
    345     }
    346 
    347     public String getNumber() {
    348         if (mTelecommCall == null) {
    349             return null;
    350         }
    351         if (mTelecommCall.getDetails().getGatewayInfo() != null) {
    352             return mTelecommCall.getDetails().getGatewayInfo()
    353                     .getOriginalAddress().getSchemeSpecificPart();
    354         }
    355         return getHandle() == null ? null : getHandle().getSchemeSpecificPart();
    356     }
    357 
    358     public Uri getHandle() {
    359         return mTelecommCall == null ? null : mTelecommCall.getDetails().getHandle();
    360     }
    361 
    362     public int getState() {
    363         if (mTelecommCall != null && mTelecommCall.getParent() != null) {
    364             return State.CONFERENCED;
    365         } else {
    366             return mState;
    367         }
    368     }
    369 
    370     public void setState(int state) {
    371         mState = state;
    372     }
    373 
    374     public int getNumberPresentation() {
    375         return mTelecommCall == null ? null : mTelecommCall.getDetails().getHandlePresentation();
    376     }
    377 
    378     public int getCnapNamePresentation() {
    379         return mTelecommCall == null ? null
    380                 : mTelecommCall.getDetails().getCallerDisplayNamePresentation();
    381     }
    382 
    383     public String getCnapName() {
    384         return mTelecommCall == null ? null
    385                 : getTelecommCall().getDetails().getCallerDisplayName();
    386     }
    387 
    388     public Bundle getIntentExtras() {
    389         return mTelecommCall == null ? null : mTelecommCall.getDetails().getIntentExtras();
    390     }
    391 
    392     public Bundle getExtras() {
    393         return mTelecommCall == null ? null : mTelecommCall.getDetails().getExtras();
    394     }
    395 
    396     /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
    397     public DisconnectCause getDisconnectCause() {
    398         if (mState == State.DISCONNECTED || mState == State.IDLE) {
    399             return mDisconnectCause;
    400         }
    401 
    402         return new DisconnectCause(DisconnectCause.UNKNOWN);
    403     }
    404 
    405     public void setDisconnectCause(DisconnectCause disconnectCause) {
    406         mDisconnectCause = disconnectCause;
    407     }
    408 
    409     /** Returns the possible text message responses. */
    410     public List<String> getCannedSmsResponses() {
    411         return mTelecommCall.getCannedTextResponses();
    412     }
    413 
    414     /** Checks if the call supports the given set of capabilities supplied as a bit mask. */
    415     public boolean can(int capabilities) {
    416         int supportedCapabilities = mTelecommCall.getDetails().getCallCapabilities();
    417 
    418         if ((capabilities & android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) {
    419             // We allow you to merge if the capabilities allow it or if it is a call with
    420             // conferenceable calls.
    421             if (mTelecommCall.getConferenceableCalls().isEmpty() &&
    422                 ((android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE
    423                         & supportedCapabilities) == 0)) {
    424                 // Cannot merge calls if there are no calls to merge with.
    425                 return false;
    426             }
    427             capabilities &= ~android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE;
    428         }
    429         return (capabilities == (capabilities & mTelecommCall.getDetails().getCallCapabilities()));
    430     }
    431 
    432     public boolean hasProperty(int property) {
    433         return mTelecommCall.getDetails().hasProperty(property);
    434     }
    435 
    436     /** Gets the time when the call first became active. */
    437     public long getConnectTimeMillis() {
    438         return mTelecommCall.getDetails().getConnectTimeMillis();
    439     }
    440 
    441     public boolean isConferenceCall() {
    442         return mTelecommCall.getDetails().hasProperty(
    443                 android.telecom.Call.Details.PROPERTY_CONFERENCE);
    444     }
    445 
    446     public GatewayInfo getGatewayInfo() {
    447         return mTelecommCall == null ? null : mTelecommCall.getDetails().getGatewayInfo();
    448     }
    449 
    450     public PhoneAccountHandle getAccountHandle() {
    451         return mTelecommCall == null ? null : mTelecommCall.getDetails().getAccountHandle();
    452     }
    453 
    454     public VideoCall getVideoCall() {
    455         return mTelecommCall == null ? null : mTelecommCall.getVideoCall();
    456     }
    457 
    458     public List<String> getChildCallIds() {
    459         return mChildCallIds;
    460     }
    461 
    462     public String getParentId() {
    463         android.telecom.Call parentCall = mTelecommCall.getParent();
    464         if (parentCall != null) {
    465             return CallList.getInstance().getCallByTelecommCall(parentCall).getId();
    466         }
    467         return null;
    468     }
    469 
    470     public int getVideoState() {
    471         return mTelecommCall.getDetails().getVideoState();
    472     }
    473 
    474     public boolean isVideoCall(Context context) {
    475         return CallUtil.isVideoEnabled(context) &&
    476                 CallUtils.isVideoCall(getVideoState());
    477     }
    478 
    479     /**
    480      * This method is called when we request for a video upgrade or downgrade. This handles the
    481      * session modification state RECEIVED_UPGRADE_TO_VIDEO_REQUEST and sets the video state we
    482      * want to upgrade/downgrade to.
    483      */
    484     public void setSessionModificationTo(int videoState) {
    485         Log.d(this, "setSessionModificationTo - video state= " + videoState);
    486         if (videoState == getVideoState()) {
    487             mSessionModificationState = Call.SessionModificationState.NO_REQUEST;
    488             Log.w(this,"setSessionModificationTo - Clearing session modification state");
    489         } else {
    490             mSessionModificationState =
    491                 Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
    492             setModifyToVideoState(videoState);
    493             CallList.getInstance().onUpgradeToVideo(this);
    494         }
    495 
    496         Log.d(this, "setSessionModificationTo - mSessionModificationState="
    497             + mSessionModificationState + " video state= " + videoState);
    498         update();
    499     }
    500 
    501     /**
    502      * This method is called to handle any other session modification states other than
    503      * RECEIVED_UPGRADE_TO_VIDEO_REQUEST. We set the modification state and reset the video state
    504      * when an upgrade request has been completed or failed.
    505      */
    506     public void setSessionModificationState(int state) {
    507         if (state == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
    508             Log.e(this,
    509             "setSessionModificationState not to be called for RECEIVED_UPGRADE_TO_VIDEO_REQUEST");
    510             return;
    511         }
    512 
    513         boolean hasChanged = mSessionModificationState != state;
    514         mSessionModificationState = state;
    515         Log.d(this, "setSessionModificationState " + state + " mSessionModificationState="
    516                 + mSessionModificationState);
    517         if (hasChanged) {
    518             CallList.getInstance().onSessionModificationStateChange(this, state);
    519         }
    520     }
    521 
    522     private void setModifyToVideoState(int newVideoState) {
    523         mModifyToVideoState = newVideoState;
    524     }
    525 
    526     public int getModifyToVideoState() {
    527         return mModifyToVideoState;
    528     }
    529 
    530     public static boolean areSame(Call call1, Call call2) {
    531         if (call1 == null && call2 == null) {
    532             return true;
    533         } else if (call1 == null || call2 == null) {
    534             return false;
    535         }
    536 
    537         // otherwise compare call Ids
    538         return call1.getId().equals(call2.getId());
    539     }
    540 
    541     public static boolean areSameNumber(Call call1, Call call2) {
    542         if (call1 == null && call2 == null) {
    543             return true;
    544         } else if (call1 == null || call2 == null) {
    545             return false;
    546         }
    547 
    548         // otherwise compare call Numbers
    549         return TextUtils.equals(call1.getNumber(), call2.getNumber());
    550     }
    551 
    552     public int getSessionModificationState() {
    553         return mSessionModificationState;
    554     }
    555 
    556     @Override
    557     public String toString() {
    558         if (mTelecommCall == null) {
    559             // This should happen only in testing since otherwise we would never have a null
    560             // Telecom call.
    561             return String.valueOf(mId);
    562         }
    563 
    564         return String.format(Locale.US, "[%s, %s, %s, children:%s, parent:%s, conferenceable:%s, " +
    565                 "videoState:%s, mSessionModificationState:%d, VideoSettings:%s]",
    566                 mId,
    567                 State.toString(getState()),
    568                 android.telecom.Call.Details
    569                         .capabilitiesToString(mTelecommCall.getDetails().getCallCapabilities()),
    570                 mChildCallIds,
    571                 getParentId(),
    572                 this.mTelecommCall.getConferenceableCalls(),
    573                 VideoProfile.videoStateToString(mTelecommCall.getDetails().getVideoState()),
    574                 mSessionModificationState,
    575                 getVideoSettings());
    576     }
    577 
    578     public String toSimpleString() {
    579         return super.toString();
    580     }
    581 }
    582