• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
isConnectingOrConnected(int state)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 
isDialing(int state)89         public static boolean isDialing(int state) {
90             return state == DIALING || state == REDIALING;
91         }
92 
toString(int state)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          */
setCameraDir(int cameraDirection)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          */
getCameraDir()176         public int getCameraDir() {
177             return mCameraDirection;
178         }
179 
180         @Override
toString()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
toString()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 
lookupToString(int lookupType)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 
initiationToString(int initiationType)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
Call(int state)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      */
Call(android.telecom.Call telecomCall)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      */
Call(android.telecom.Call telecomCall, boolean registerCallback)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 
getTelecomCall()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      */
getVideoSettings()441     public VideoSettings getVideoSettings() {
442         return mVideoSettings;
443     }
444 
update()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 
updateFromTelecomCall(boolean registerCallback)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      */
areCallExtrasCorrupted(Bundle callExtras)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 
updateFromCallExtras(Bundle callExtras)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      */
maybeCancelVideoUpgrade(int newVideoState)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     }
translateState(int state)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 
getId()627     public String getId() {
628         return mId;
629     }
630 
getTimeAddedMs()631     public long getTimeAddedMs() {
632         return mTimeAddedMs;
633     }
634 
getNumber()635     public String getNumber() {
636         return TelecomCallUtil.getNumber(mTelecomCall);
637     }
638 
blockCall()639     public void blockCall() {
640         mTelecomCall.reject(false, null);
641         setState(State.BLOCKED);
642     }
643 
getHandle()644     public Uri getHandle() {
645         return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandle();
646     }
647 
isEmergencyCall()648     public boolean isEmergencyCall() {
649         return mIsEmergencyCall;
650     }
651 
getState()652     public int getState() {
653         if (mTelecomCall != null && mTelecomCall.getParent() != null) {
654             return State.CONFERENCED;
655         } else {
656             return mState;
657         }
658     }
659 
setState(int state)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 
getNumberPresentation()670     public int getNumberPresentation() {
671         return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandlePresentation();
672     }
673 
getCnapNamePresentation()674     public int getCnapNamePresentation() {
675         return mTelecomCall == null ? null
676                 : mTelecomCall.getDetails().getCallerDisplayNamePresentation();
677     }
678 
getCnapName()679     public String getCnapName() {
680         return mTelecomCall == null ? null
681                 : getTelecomCall().getDetails().getCallerDisplayName();
682     }
683 
getIntentExtras()684     public Bundle getIntentExtras() {
685         return mTelecomCall.getDetails().getIntentExtras();
686     }
687 
getExtras()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      */
getChildNumber()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      */
getLastForwardedNumber()702     public String getLastForwardedNumber() {
703         return mLastForwardedNumber;
704     }
705 
706     /**
707      * @return The call subject, or {@code null} if none specified.
708      */
getCallSubject()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      */
isCallSubjectSupported()717     public boolean isCallSubjectSupported() {
718         return mIsCallSubjectSupported;
719     }
720 
721     /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
getDisconnectCause()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 
setDisconnectCause(DisconnectCause disconnectCause)730     public void setDisconnectCause(DisconnectCause disconnectCause) {
731         mDisconnectCause = disconnectCause;
732         mLogState.disconnectCause = mDisconnectCause;
733     }
734 
735     /** Returns the possible text message responses. */
getCannedSmsResponses()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. */
can(int capabilities)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 
hasProperty(int property)758     public boolean hasProperty(int property) {
759         return mTelecomCall.getDetails().hasProperty(property);
760     }
761 
762     /** Gets the time when the call first became active. */
getConnectTimeMillis()763     public long getConnectTimeMillis() {
764         return mTelecomCall.getDetails().getConnectTimeMillis();
765     }
766 
isConferenceCall()767     public boolean isConferenceCall() {
768         return hasProperty(android.telecom.Call.Details.PROPERTY_CONFERENCE);
769     }
770 
getGatewayInfo()771     public GatewayInfo getGatewayInfo() {
772         return mTelecomCall == null ? null : mTelecomCall.getDetails().getGatewayInfo();
773     }
774 
getAccountHandle()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      */
getVideoCall()784     public VideoCall getVideoCall() {
785         return mTelecomCall == null || !mIsVideoCallCallbackRegistered ? null
786                 : mTelecomCall.getVideoCall();
787     }
788 
getChildCallIds()789     public List<String> getChildCallIds() {
790         return mChildCallIds;
791     }
792 
getParentId()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 
getVideoState()801     public int getVideoState() {
802         return mTelecomCall.getDetails().getVideoState();
803     }
804 
isVideoCall(Context context)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      */
setRequestedVideoState(int videoState)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      */
setSessionModificationState(int state)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      */
updateEmergencyCallState()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      */
getRequestedVideoState()866     public int getRequestedVideoState() {
867         return mRequestedVideoState;
868     }
869 
areSame(Call call1, Call call2)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 
areSameNumber(Call call1, Call call2)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      */
getSessionModificationState()897     public int getSessionModificationState() {
898         return mSessionModificationState;
899     }
900 
getLogState()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      */
isExternalCall()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      */
isPullableExternalCall()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      */
logCallInitiationType()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
toString()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 
toSimpleString()977     public String toSimpleString() {
978         return super.toString();
979     }
980 }
981