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