blob: 15a0233e8eb264d610fa60e608f368f086c4b05a [file] [log] [blame]
Eric Erfanianccca3152017-02-22 16:32:36 -08001/*
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
17package com.android.incallui.call;
18
19import android.content.Context;
20import android.hardware.camera2.CameraCharacteristics;
21import android.net.Uri;
22import android.os.Build.VERSION;
23import android.os.Build.VERSION_CODES;
24import android.os.Bundle;
25import android.os.Trace;
26import android.support.annotation.IntDef;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070027import android.support.annotation.NonNull;
Eric Erfanianccca3152017-02-22 16:32:36 -080028import android.support.annotation.Nullable;
29import android.telecom.Call;
30import android.telecom.Call.Details;
31import android.telecom.Connection;
32import android.telecom.DisconnectCause;
33import android.telecom.GatewayInfo;
34import android.telecom.InCallService.VideoCall;
35import android.telecom.PhoneAccount;
36import android.telecom.PhoneAccountHandle;
37import android.telecom.StatusHints;
38import android.telecom.TelecomManager;
39import android.telecom.VideoProfile;
40import android.telephony.PhoneNumberUtils;
41import android.text.TextUtils;
42import com.android.contacts.common.compat.CallCompat;
43import com.android.contacts.common.compat.TelephonyManagerCompat;
44import com.android.contacts.common.compat.telecom.TelecomManagerCompat;
45import com.android.dialer.callintent.CallIntentParser;
46import com.android.dialer.callintent.nano.CallInitiationType;
47import com.android.dialer.callintent.nano.CallSpecificAppData;
48import com.android.dialer.common.Assert;
49import com.android.dialer.common.ConfigProviderBindings;
50import com.android.dialer.common.LogUtil;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070051import com.android.dialer.enrichedcall.EnrichedCallComponent;
Eric Erfanianccca3152017-02-22 16:32:36 -080052import com.android.dialer.logging.nano.ContactLookupResult;
Eric Erfanianccca3152017-02-22 16:32:36 -080053import com.android.incallui.latencyreport.LatencyReport;
54import com.android.incallui.util.TelecomCallUtil;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070055import com.android.incallui.videotech.VideoTech;
56import com.android.incallui.videotech.VideoTech.VideoTechListener;
57import com.android.incallui.videotech.empty.EmptyVideoTech;
58import com.android.incallui.videotech.ims.ImsVideoTech;
59import com.android.incallui.videotech.rcs.RcsVideoShare;
Eric Erfanianccca3152017-02-22 16:32:36 -080060import java.lang.annotation.Retention;
61import java.lang.annotation.RetentionPolicy;
62import java.util.ArrayList;
63import java.util.List;
64import java.util.Locale;
65import java.util.Objects;
66import java.util.UUID;
67import java.util.concurrent.CopyOnWriteArrayList;
68import java.util.concurrent.TimeUnit;
69
70/** Describes a single call and its state. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -070071public class DialerCall implements VideoTechListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080072
73 public static final int CALL_HISTORY_STATUS_UNKNOWN = 0;
74 public static final int CALL_HISTORY_STATUS_PRESENT = 1;
75 public static final int CALL_HISTORY_STATUS_NOT_PRESENT = 2;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070076
77 // Hard coded property for {@code Call}. Upstreamed change from Motorola.
78 // TODO(b/35359461): Move it to Telecom in framework.
79 public static final int PROPERTY_CODEC_KNOWN = 0x04000000;
80
Eric Erfanianccca3152017-02-22 16:32:36 -080081 private static final String ID_PREFIX = "DialerCall_";
82 private static final String CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS =
83 "emergency_callback_window_millis";
84 private static int sIdCounter = 0;
85
86 /**
87 * The unique call ID for every call. This will help us to identify each call and allow us the
88 * ability to stitch impressions to calls if needed.
89 */
90 private final String uniqueCallId = UUID.randomUUID().toString();
91
92 private final Call mTelecomCall;
93 private final LatencyReport mLatencyReport;
94 private final String mId;
95 private final List<String> mChildCallIds = new ArrayList<>();
Eric Erfanianccca3152017-02-22 16:32:36 -080096 private final LogState mLogState = new LogState();
97 private final Context mContext;
98 private final DialerCallDelegate mDialerCallDelegate;
99 private final List<DialerCallListener> mListeners = new CopyOnWriteArrayList<>();
100 private final List<CannedTextResponsesLoadedListener> mCannedTextResponsesLoadedListeners =
101 new CopyOnWriteArrayList<>();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700102 private final VideoTechManager mVideoTechManager;
Eric Erfanianccca3152017-02-22 16:32:36 -0800103
104 private boolean mIsEmergencyCall;
105 private Uri mHandle;
106 private int mState = State.INVALID;
107 private DisconnectCause mDisconnectCause;
108
109 private boolean hasShownWiFiToLteHandoverToast;
110 private boolean doNotShowDialogForHandoffToWifiFailure;
111
Eric Erfanianccca3152017-02-22 16:32:36 -0800112 private String mChildNumber;
113 private String mLastForwardedNumber;
114 private String mCallSubject;
115 private PhoneAccountHandle mPhoneAccountHandle;
116 @CallHistoryStatus private int mCallHistoryStatus = CALL_HISTORY_STATUS_UNKNOWN;
117 private boolean mIsSpam;
118 private boolean mIsBlocked;
119 private boolean isInUserSpamList;
120 private boolean isInUserWhiteList;
121 private boolean isInGlobalSpamList;
122 private boolean didShowCameraPermission;
123 private String callProviderLabel;
124 private String callbackNumber;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700125 private int mCameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
Eric Erfanianccca3152017-02-22 16:32:36 -0800126
127 public static String getNumberFromHandle(Uri handle) {
128 return handle == null ? "" : handle.getSchemeSpecificPart();
129 }
130
131 /**
132 * Whether the call is put on hold by remote party. This is different than the {@link
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700133 * State#ONHOLD} state which indicates that the call is being held locally on the device.
Eric Erfanianccca3152017-02-22 16:32:36 -0800134 */
135 private boolean isRemotelyHeld;
136
137 /**
138 * Indicates whether the phone account associated with this call supports specifying a call
139 * subject.
140 */
141 private boolean mIsCallSubjectSupported;
142
143 private final Call.Callback mTelecomCallCallback =
144 new Call.Callback() {
145 @Override
146 public void onStateChanged(Call call, int newState) {
147 LogUtil.v("TelecomCallCallback.onStateChanged", "call=" + call + " newState=" + newState);
148 update();
149 }
150
151 @Override
152 public void onParentChanged(Call call, Call newParent) {
153 LogUtil.v(
154 "TelecomCallCallback.onParentChanged", "call=" + call + " newParent=" + newParent);
155 update();
156 }
157
158 @Override
159 public void onChildrenChanged(Call call, List<Call> children) {
160 update();
161 }
162
163 @Override
164 public void onDetailsChanged(Call call, Call.Details details) {
165 LogUtil.v("TelecomCallCallback.onStateChanged", " call=" + call + " details=" + details);
166 update();
167 }
168
169 @Override
170 public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {
171 LogUtil.v(
172 "TelecomCallCallback.onStateChanged",
173 "call=" + call + " cannedTextResponses=" + cannedTextResponses);
174 for (CannedTextResponsesLoadedListener listener : mCannedTextResponsesLoadedListeners) {
175 listener.onCannedTextResponsesLoaded(DialerCall.this);
176 }
177 }
178
179 @Override
180 public void onPostDialWait(Call call, String remainingPostDialSequence) {
181 LogUtil.v(
182 "TelecomCallCallback.onStateChanged",
183 "call=" + call + " remainingPostDialSequence=" + remainingPostDialSequence);
184 update();
185 }
186
187 @Override
188 public void onVideoCallChanged(Call call, VideoCall videoCall) {
189 LogUtil.v(
190 "TelecomCallCallback.onStateChanged", "call=" + call + " videoCall=" + videoCall);
191 update();
192 }
193
194 @Override
195 public void onCallDestroyed(Call call) {
196 LogUtil.v("TelecomCallCallback.onStateChanged", "call=" + call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700197 unregisterCallback();
Eric Erfanianccca3152017-02-22 16:32:36 -0800198 }
199
200 @Override
201 public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
202 LogUtil.v(
203 "DialerCall.onConferenceableCallsChanged",
204 "call %s, conferenceable calls: %d",
205 call,
206 conferenceableCalls.size());
207 update();
208 }
209
210 @Override
211 public void onConnectionEvent(android.telecom.Call call, String event, Bundle extras) {
212 LogUtil.v(
213 "DialerCall.onConnectionEvent",
214 "Call: " + call + ", Event: " + event + ", Extras: " + extras);
215 switch (event) {
216 // The Previous attempt to Merge two calls together has failed in Telecom. We must
217 // now update the UI to possibly re-enable the Merge button based on the number of
218 // currently conferenceable calls available or Connection Capabilities.
219 case android.telecom.Connection.EVENT_CALL_MERGE_FAILED:
220 update();
221 break;
222 case TelephonyManagerCompat.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE:
223 notifyWiFiToLteHandover();
224 break;
225 case TelephonyManagerCompat.EVENT_HANDOVER_TO_WIFI_FAILED:
226 notifyHandoverToWifiFailed();
227 break;
228 case TelephonyManagerCompat.EVENT_CALL_REMOTELY_HELD:
229 isRemotelyHeld = true;
230 update();
231 break;
232 case TelephonyManagerCompat.EVENT_CALL_REMOTELY_UNHELD:
233 isRemotelyHeld = false;
234 update();
235 break;
236 default:
237 break;
238 }
239 }
240 };
241 private long mTimeAddedMs;
242
243 public DialerCall(
244 Context context,
245 DialerCallDelegate dialerCallDelegate,
246 Call telecomCall,
247 LatencyReport latencyReport,
248 boolean registerCallback) {
249 Assert.isNotNull(context);
250 mContext = context;
251 mDialerCallDelegate = dialerCallDelegate;
252 mTelecomCall = telecomCall;
253 mLatencyReport = latencyReport;
254 mId = ID_PREFIX + Integer.toString(sIdCounter++);
255
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700256 // Must be after assigning mTelecomCall
257 mVideoTechManager = new VideoTechManager(this);
258
259 updateFromTelecomCall();
Eric Erfanianccca3152017-02-22 16:32:36 -0800260
261 if (registerCallback) {
262 mTelecomCall.registerCallback(mTelecomCallCallback);
263 }
264
265 mTimeAddedMs = System.currentTimeMillis();
266 parseCallSpecificAppData();
267 }
268
269 private static int translateState(int state) {
270 switch (state) {
271 case Call.STATE_NEW:
272 case Call.STATE_CONNECTING:
273 return DialerCall.State.CONNECTING;
274 case Call.STATE_SELECT_PHONE_ACCOUNT:
275 return DialerCall.State.SELECT_PHONE_ACCOUNT;
276 case Call.STATE_DIALING:
277 return DialerCall.State.DIALING;
278 case Call.STATE_PULLING_CALL:
279 return DialerCall.State.PULLING;
280 case Call.STATE_RINGING:
281 return DialerCall.State.INCOMING;
282 case Call.STATE_ACTIVE:
283 return DialerCall.State.ACTIVE;
284 case Call.STATE_HOLDING:
285 return DialerCall.State.ONHOLD;
286 case Call.STATE_DISCONNECTED:
287 return DialerCall.State.DISCONNECTED;
288 case Call.STATE_DISCONNECTING:
289 return DialerCall.State.DISCONNECTING;
290 default:
291 return DialerCall.State.INVALID;
292 }
293 }
294
295 public static boolean areSame(DialerCall call1, DialerCall call2) {
296 if (call1 == null && call2 == null) {
297 return true;
298 } else if (call1 == null || call2 == null) {
299 return false;
300 }
301
302 // otherwise compare call Ids
303 return call1.getId().equals(call2.getId());
304 }
305
306 public static boolean areSameNumber(DialerCall call1, DialerCall call2) {
307 if (call1 == null && call2 == null) {
308 return true;
309 } else if (call1 == null || call2 == null) {
310 return false;
311 }
312
313 // otherwise compare call Numbers
314 return TextUtils.equals(call1.getNumber(), call2.getNumber());
315 }
316
317 public void addListener(DialerCallListener listener) {
318 Assert.isMainThread();
319 mListeners.add(listener);
320 }
321
322 public void removeListener(DialerCallListener listener) {
323 Assert.isMainThread();
324 mListeners.remove(listener);
325 }
326
327 public void addCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
328 Assert.isMainThread();
329 mCannedTextResponsesLoadedListeners.add(listener);
330 }
331
332 public void removeCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
333 Assert.isMainThread();
334 mCannedTextResponsesLoadedListeners.remove(listener);
335 }
336
337 public void notifyWiFiToLteHandover() {
338 LogUtil.i("DialerCall.notifyWiFiToLteHandover", "");
339 for (DialerCallListener listener : mListeners) {
340 listener.onWiFiToLteHandover();
341 }
342 }
343
344 public void notifyHandoverToWifiFailed() {
345 LogUtil.i("DialerCall.notifyHandoverToWifiFailed", "");
346 for (DialerCallListener listener : mListeners) {
347 listener.onHandoverToWifiFailure();
348 }
349 }
350
351 /* package-private */ Call getTelecomCall() {
352 return mTelecomCall;
353 }
354
355 public StatusHints getStatusHints() {
356 return mTelecomCall.getDetails().getStatusHints();
357 }
358
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700359 public int getCameraDir() {
360 return mCameraDirection;
361 }
362
363 public void setCameraDir(int cameraDir) {
364 if (cameraDir == CameraDirection.CAMERA_DIRECTION_FRONT_FACING
365 || cameraDir == CameraDirection.CAMERA_DIRECTION_BACK_FACING) {
366 mCameraDirection = cameraDir;
367 } else {
368 mCameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
369 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800370 }
371
372 private void update() {
373 Trace.beginSection("Update");
374 int oldState = getState();
375 // We want to potentially register a video call callback here.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700376 updateFromTelecomCall();
Eric Erfanianccca3152017-02-22 16:32:36 -0800377 if (oldState != getState() && getState() == DialerCall.State.DISCONNECTED) {
378 for (DialerCallListener listener : mListeners) {
379 listener.onDialerCallDisconnect();
380 }
381 } else {
382 for (DialerCallListener listener : mListeners) {
383 listener.onDialerCallUpdate();
384 }
385 }
386 Trace.endSection();
387 }
388
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700389 private void updateFromTelecomCall() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800390 LogUtil.v("DialerCall.updateFromTelecomCall", mTelecomCall.toString());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700391
392 mVideoTechManager.dispatchCallStateChanged(mTelecomCall.getState());
393
Eric Erfanianccca3152017-02-22 16:32:36 -0800394 final int translatedState = translateState(mTelecomCall.getState());
395 if (mState != State.BLOCKED) {
396 setState(translatedState);
397 setDisconnectCause(mTelecomCall.getDetails().getDisconnectCause());
Eric Erfanianccca3152017-02-22 16:32:36 -0800398 }
399
400 mChildCallIds.clear();
401 final int numChildCalls = mTelecomCall.getChildren().size();
402 for (int i = 0; i < numChildCalls; i++) {
403 mChildCallIds.add(
404 mDialerCallDelegate
405 .getDialerCallFromTelecomCall(mTelecomCall.getChildren().get(i))
406 .getId());
407 }
408
409 // The number of conferenced calls can change over the course of the call, so use the
410 // maximum number of conferenced child calls as the metric for conference call usage.
411 mLogState.conferencedCalls = Math.max(numChildCalls, mLogState.conferencedCalls);
412
413 updateFromCallExtras(mTelecomCall.getDetails().getExtras());
414
415 // If the handle of the call has changed, update state for the call determining if it is an
416 // emergency call.
417 Uri newHandle = mTelecomCall.getDetails().getHandle();
418 if (!Objects.equals(mHandle, newHandle)) {
419 mHandle = newHandle;
420 updateEmergencyCallState();
421 }
422
423 // If the phone account handle of the call is set, cache capability bit indicating whether
424 // the phone account supports call subjects.
425 PhoneAccountHandle newPhoneAccountHandle = mTelecomCall.getDetails().getAccountHandle();
426 if (!Objects.equals(mPhoneAccountHandle, newPhoneAccountHandle)) {
427 mPhoneAccountHandle = newPhoneAccountHandle;
428
429 if (mPhoneAccountHandle != null) {
430 PhoneAccount phoneAccount =
431 mContext.getSystemService(TelecomManager.class).getPhoneAccount(mPhoneAccountHandle);
432 if (phoneAccount != null) {
433 mIsCallSubjectSupported =
434 phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT);
435 }
436 }
437 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800438 }
439
440 /**
441 * Tests corruption of the {@code callExtras} bundle by calling {@link
442 * Bundle#containsKey(String)}. If the bundle is corrupted a {@link IllegalArgumentException} will
443 * be thrown and caught by this function.
444 *
445 * @param callExtras the bundle to verify
446 * @return {@code true} if the bundle is corrupted, {@code false} otherwise.
447 */
448 protected boolean areCallExtrasCorrupted(Bundle callExtras) {
449 /**
450 * There's currently a bug in Telephony service (b/25613098) that could corrupt the extras
451 * bundle, resulting in a IllegalArgumentException while validating data under {@link
452 * Bundle#containsKey(String)}.
453 */
454 try {
455 callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS);
456 return false;
457 } catch (IllegalArgumentException e) {
458 LogUtil.e(
459 "DialerCall.areCallExtrasCorrupted", "callExtras is corrupted, ignoring exception", e);
460 return true;
461 }
462 }
463
464 protected void updateFromCallExtras(Bundle callExtras) {
465 if (callExtras == null || areCallExtrasCorrupted(callExtras)) {
466 /**
467 * If the bundle is corrupted, abandon information update as a work around. These are not
468 * critical for the dialer to function.
469 */
470 return;
471 }
472 // Check for a change in the child address and notify any listeners.
473 if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) {
474 String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS);
475 if (!Objects.equals(childNumber, mChildNumber)) {
476 mChildNumber = childNumber;
477 for (DialerCallListener listener : mListeners) {
478 listener.onDialerCallChildNumberChange();
479 }
480 }
481 }
482
483 // Last forwarded number comes in as an array of strings. We want to choose the
484 // last item in the array. The forwarding numbers arrive independently of when the
485 // call is originally set up, so we need to notify the the UI of the change.
486 if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) {
487 ArrayList<String> lastForwardedNumbers =
488 callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER);
489
490 if (lastForwardedNumbers != null) {
491 String lastForwardedNumber = null;
492 if (!lastForwardedNumbers.isEmpty()) {
493 lastForwardedNumber = lastForwardedNumbers.get(lastForwardedNumbers.size() - 1);
494 }
495
496 if (!Objects.equals(lastForwardedNumber, mLastForwardedNumber)) {
497 mLastForwardedNumber = lastForwardedNumber;
498 for (DialerCallListener listener : mListeners) {
499 listener.onDialerCallLastForwardedNumberChange();
500 }
501 }
502 }
503 }
504
505 // DialerCall subject is present in the extras at the start of call, so we do not need to
506 // notify any other listeners of this.
507 if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) {
508 String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT);
509 if (!Objects.equals(mCallSubject, callSubject)) {
510 mCallSubject = callSubject;
511 }
512 }
513 }
514
Eric Erfanianccca3152017-02-22 16:32:36 -0800515 public String getId() {
516 return mId;
517 }
518
519 public boolean hasShownWiFiToLteHandoverToast() {
520 return hasShownWiFiToLteHandoverToast;
521 }
522
523 public void setHasShownWiFiToLteHandoverToast() {
524 hasShownWiFiToLteHandoverToast = true;
525 }
526
527 public boolean showWifiHandoverAlertAsToast() {
528 return doNotShowDialogForHandoffToWifiFailure;
529 }
530
531 public void setDoNotShowDialogForHandoffToWifiFailure(boolean bool) {
532 doNotShowDialogForHandoffToWifiFailure = bool;
533 }
534
535 public long getTimeAddedMs() {
536 return mTimeAddedMs;
537 }
538
539 @Nullable
540 public String getNumber() {
541 return TelecomCallUtil.getNumber(mTelecomCall);
542 }
543
544 public void blockCall() {
545 mTelecomCall.reject(false, null);
546 setState(State.BLOCKED);
547 }
548
549 @Nullable
550 public Uri getHandle() {
551 return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandle();
552 }
553
554 public boolean isEmergencyCall() {
555 return mIsEmergencyCall;
556 }
557
558 public boolean isPotentialEmergencyCallback() {
559 // The property PROPERTY_EMERGENCY_CALLBACK_MODE is only set for CDMA calls when the system
560 // is actually in emergency callback mode (ie data is disabled).
561 if (hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE)) {
562 return true;
563 }
564 // We want to treat any incoming call that arrives a short time after an outgoing emergency call
565 // as a potential emergency callback.
566 if (getExtras() != null
567 && getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0)
568 > 0) {
569 long lastEmergencyCallMillis =
570 getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0);
571 if (isInEmergencyCallbackWindow(lastEmergencyCallMillis)) {
572 return true;
573 }
574 }
575 return false;
576 }
577
578 boolean isInEmergencyCallbackWindow(long timestampMillis) {
579 long emergencyCallbackWindowMillis =
580 ConfigProviderBindings.get(mContext)
581 .getLong(CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS, TimeUnit.MINUTES.toMillis(5));
582 return System.currentTimeMillis() - timestampMillis < emergencyCallbackWindowMillis;
583 }
584
585 public int getState() {
586 if (mTelecomCall != null && mTelecomCall.getParent() != null) {
587 return State.CONFERENCED;
588 } else {
589 return mState;
590 }
591 }
592
593 public void setState(int state) {
594 mState = state;
595 if (mState == State.INCOMING) {
596 mLogState.isIncoming = true;
597 } else if (mState == State.DISCONNECTED) {
598 mLogState.duration =
599 getConnectTimeMillis() == 0 ? 0 : System.currentTimeMillis() - getConnectTimeMillis();
600 }
601 }
602
603 public int getNumberPresentation() {
604 return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getHandlePresentation();
605 }
606
607 public int getCnapNamePresentation() {
608 return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getCallerDisplayNamePresentation();
609 }
610
611 @Nullable
612 public String getCnapName() {
613 return mTelecomCall == null ? null : getTelecomCall().getDetails().getCallerDisplayName();
614 }
615
616 public Bundle getIntentExtras() {
617 return mTelecomCall.getDetails().getIntentExtras();
618 }
619
620 @Nullable
621 public Bundle getExtras() {
622 return mTelecomCall == null ? null : mTelecomCall.getDetails().getExtras();
623 }
624
625 /** @return The child number for the call, or {@code null} if none specified. */
626 public String getChildNumber() {
627 return mChildNumber;
628 }
629
630 /** @return The last forwarded number for the call, or {@code null} if none specified. */
631 public String getLastForwardedNumber() {
632 return mLastForwardedNumber;
633 }
634
635 /** @return The call subject, or {@code null} if none specified. */
636 public String getCallSubject() {
637 return mCallSubject;
638 }
639
640 /**
641 * @return {@code true} if the call's phone account supports call subjects, {@code false}
642 * otherwise.
643 */
644 public boolean isCallSubjectSupported() {
645 return mIsCallSubjectSupported;
646 }
647
648 /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
649 public DisconnectCause getDisconnectCause() {
650 if (mState == State.DISCONNECTED || mState == State.IDLE) {
651 return mDisconnectCause;
652 }
653
654 return new DisconnectCause(DisconnectCause.UNKNOWN);
655 }
656
657 public void setDisconnectCause(DisconnectCause disconnectCause) {
658 mDisconnectCause = disconnectCause;
659 mLogState.disconnectCause = mDisconnectCause;
660 }
661
662 /** Returns the possible text message responses. */
663 public List<String> getCannedSmsResponses() {
664 return mTelecomCall.getCannedTextResponses();
665 }
666
667 /** Checks if the call supports the given set of capabilities supplied as a bit mask. */
668 public boolean can(int capabilities) {
669 int supportedCapabilities = mTelecomCall.getDetails().getCallCapabilities();
670
671 if ((capabilities & Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) {
672 // We allow you to merge if the capabilities allow it or if it is a call with
673 // conferenceable calls.
674 if (mTelecomCall.getConferenceableCalls().isEmpty()
675 && ((Call.Details.CAPABILITY_MERGE_CONFERENCE & supportedCapabilities) == 0)) {
676 // Cannot merge calls if there are no calls to merge with.
677 return false;
678 }
679 capabilities &= ~Call.Details.CAPABILITY_MERGE_CONFERENCE;
680 }
681 return (capabilities == (capabilities & supportedCapabilities));
682 }
683
684 public boolean hasProperty(int property) {
685 return mTelecomCall.getDetails().hasProperty(property);
686 }
687
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700688 @NonNull
Eric Erfanianccca3152017-02-22 16:32:36 -0800689 public String getUniqueCallId() {
690 return uniqueCallId;
691 }
692
693 /** Gets the time when the call first became active. */
694 public long getConnectTimeMillis() {
695 return mTelecomCall.getDetails().getConnectTimeMillis();
696 }
697
698 public boolean isConferenceCall() {
699 return hasProperty(Call.Details.PROPERTY_CONFERENCE);
700 }
701
702 @Nullable
703 public GatewayInfo getGatewayInfo() {
704 return mTelecomCall == null ? null : mTelecomCall.getDetails().getGatewayInfo();
705 }
706
707 @Nullable
708 public PhoneAccountHandle getAccountHandle() {
709 return mTelecomCall == null ? null : mTelecomCall.getDetails().getAccountHandle();
710 }
711
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700712 /** @return The {@link VideoCall} instance associated with the {@link Call}. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800713 public VideoCall getVideoCall() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700714 return mTelecomCall == null ? null : mTelecomCall.getVideoCall();
Eric Erfanianccca3152017-02-22 16:32:36 -0800715 }
716
717 public List<String> getChildCallIds() {
718 return mChildCallIds;
719 }
720
721 public String getParentId() {
722 Call parentCall = mTelecomCall.getParent();
723 if (parentCall != null) {
724 return mDialerCallDelegate.getDialerCallFromTelecomCall(parentCall).getId();
725 }
726 return null;
727 }
728
729 public int getVideoState() {
730 return mTelecomCall.getDetails().getVideoState();
731 }
732
733 public boolean isVideoCall() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700734 return getVideoTech().isTransmittingOrReceiving();
735 }
736
737 public boolean hasReceivedVideoUpgradeRequest() {
738 return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState());
739 }
740
741 public boolean hasSentVideoUpgradeRequest() {
742 return VideoUtils.hasSentVideoUpgradeRequest(getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800743 }
744
745 /**
746 * Determines if the call handle is an emergency number or not and caches the result to avoid
747 * repeated calls to isEmergencyNumber.
748 */
749 private void updateEmergencyCallState() {
750 mIsEmergencyCall = TelecomCallUtil.isEmergencyCall(mTelecomCall);
751 }
752
Eric Erfanianccca3152017-02-22 16:32:36 -0800753 public LogState getLogState() {
754 return mLogState;
755 }
756
757 /**
758 * Determines if the call is an external call.
759 *
760 * <p>An external call is one which does not exist locally for the {@link
761 * android.telecom.ConnectionService} it is associated with.
762 *
763 * <p>External calls are only supported in N and higher.
764 *
765 * @return {@code true} if the call is an external call, {@code false} otherwise.
766 */
767 public boolean isExternalCall() {
768 return VERSION.SDK_INT >= VERSION_CODES.N
769 && hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL);
770 }
771
772 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800773 * Determines if answering this call will cause an ongoing video call to be dropped.
774 *
775 * @return {@code true} if answering this call will drop an ongoing video call, {@code false}
776 * otherwise.
777 */
778 public boolean answeringDisconnectsForegroundVideoCall() {
779 Bundle extras = getExtras();
780 if (extras == null
781 || !extras.containsKey(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL)) {
782 return false;
783 }
784 return extras.getBoolean(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL);
785 }
786
787 private void parseCallSpecificAppData() {
788 if (isExternalCall()) {
789 return;
790 }
791
792 mLogState.callSpecificAppData = CallIntentParser.getCallSpecificAppData(getIntentExtras());
793 if (mLogState.callSpecificAppData == null) {
794 mLogState.callSpecificAppData = new CallSpecificAppData();
795 mLogState.callSpecificAppData.callInitiationType =
796 CallInitiationType.Type.EXTERNAL_INITIATION;
797 }
798 if (getState() == State.INCOMING) {
799 mLogState.callSpecificAppData.callInitiationType =
800 CallInitiationType.Type.INCOMING_INITIATION;
801 }
802 }
803
804 @Override
805 public String toString() {
806 if (mTelecomCall == null) {
807 // This should happen only in testing since otherwise we would never have a null
808 // Telecom call.
809 return String.valueOf(mId);
810 }
811
812 return String.format(
813 Locale.US,
814 "[%s, %s, %s, %s, children:%s, parent:%s, "
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700815 + "conferenceable:%s, videoState:%s, mSessionModificationState:%d, CameraDir:%s]",
Eric Erfanianccca3152017-02-22 16:32:36 -0800816 mId,
817 State.toString(getState()),
818 Details.capabilitiesToString(mTelecomCall.getDetails().getCallCapabilities()),
819 Details.propertiesToString(mTelecomCall.getDetails().getCallProperties()),
820 mChildCallIds,
821 getParentId(),
822 this.mTelecomCall.getConferenceableCalls(),
823 VideoProfile.videoStateToString(mTelecomCall.getDetails().getVideoState()),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700824 getVideoTech().getSessionModificationState(),
825 getCameraDir());
Eric Erfanianccca3152017-02-22 16:32:36 -0800826 }
827
828 public String toSimpleString() {
829 return super.toString();
830 }
831
832 @CallHistoryStatus
833 public int getCallHistoryStatus() {
834 return mCallHistoryStatus;
835 }
836
837 public void setCallHistoryStatus(@CallHistoryStatus int callHistoryStatus) {
838 mCallHistoryStatus = callHistoryStatus;
839 }
840
841 public boolean didShowCameraPermission() {
842 return didShowCameraPermission;
843 }
844
845 public void setDidShowCameraPermission(boolean didShow) {
846 didShowCameraPermission = didShow;
847 }
848
849 public boolean isInGlobalSpamList() {
850 return isInGlobalSpamList;
851 }
852
853 public void setIsInGlobalSpamList(boolean inSpamList) {
854 isInGlobalSpamList = inSpamList;
855 }
856
857 public boolean isInUserSpamList() {
858 return isInUserSpamList;
859 }
860
861 public void setIsInUserSpamList(boolean inSpamList) {
862 isInUserSpamList = inSpamList;
863 }
864
865 public boolean isInUserWhiteList() {
866 return isInUserWhiteList;
867 }
868
869 public void setIsInUserWhiteList(boolean inWhiteList) {
870 isInUserWhiteList = inWhiteList;
871 }
872
873 public boolean isSpam() {
874 return mIsSpam;
875 }
876
877 public void setSpam(boolean isSpam) {
878 mIsSpam = isSpam;
879 }
880
881 public boolean isBlocked() {
882 return mIsBlocked;
883 }
884
885 public void setBlockedStatus(boolean isBlocked) {
886 mIsBlocked = isBlocked;
887 }
888
889 public boolean isRemotelyHeld() {
890 return isRemotelyHeld;
891 }
892
893 public boolean isIncoming() {
894 return mLogState.isIncoming;
895 }
896
897 public LatencyReport getLatencyReport() {
898 return mLatencyReport;
899 }
900
901 public void unregisterCallback() {
902 mTelecomCall.unregisterCallback(mTelecomCallCallback);
903 }
904
Eric Erfanianccca3152017-02-22 16:32:36 -0800905 public void phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault) {
906 LogUtil.i(
907 "DialerCall.phoneAccountSelected",
908 "accountHandle: %s, setDefault: %b",
909 accountHandle,
910 setDefault);
911 mTelecomCall.phoneAccountSelected(accountHandle, setDefault);
912 }
913
914 public void disconnect() {
915 LogUtil.i("DialerCall.disconnect", "");
916 setState(DialerCall.State.DISCONNECTING);
917 for (DialerCallListener listener : mListeners) {
918 listener.onDialerCallUpdate();
919 }
920 mTelecomCall.disconnect();
921 }
922
923 public void hold() {
924 LogUtil.i("DialerCall.hold", "");
925 mTelecomCall.hold();
926 }
927
928 public void unhold() {
929 LogUtil.i("DialerCall.unhold", "");
930 mTelecomCall.unhold();
931 }
932
933 public void splitFromConference() {
934 LogUtil.i("DialerCall.splitFromConference", "");
935 mTelecomCall.splitFromConference();
936 }
937
938 public void answer(int videoState) {
939 LogUtil.i("DialerCall.answer", "videoState: " + videoState);
940 mTelecomCall.answer(videoState);
941 }
942
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700943 public void answer() {
944 answer(mTelecomCall.getDetails().getVideoState());
945 }
946
Eric Erfanianccca3152017-02-22 16:32:36 -0800947 public void reject(boolean rejectWithMessage, String message) {
948 LogUtil.i("DialerCall.reject", "");
949 mTelecomCall.reject(rejectWithMessage, message);
950 }
951
952 /** Return the string label to represent the call provider */
953 public String getCallProviderLabel() {
954 if (callProviderLabel == null) {
955 PhoneAccount account = getPhoneAccount();
956 if (account != null && !TextUtils.isEmpty(account.getLabel())) {
957 List<PhoneAccountHandle> accounts =
958 mContext.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts();
959 if (accounts != null && accounts.size() > 1) {
960 callProviderLabel = account.getLabel().toString();
961 }
962 }
963 if (callProviderLabel == null) {
964 callProviderLabel = "";
965 }
966 }
967 return callProviderLabel;
968 }
969
970 private PhoneAccount getPhoneAccount() {
971 PhoneAccountHandle accountHandle = getAccountHandle();
972 if (accountHandle == null) {
973 return null;
974 }
975 return mContext.getSystemService(TelecomManager.class).getPhoneAccount(accountHandle);
976 }
977
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700978 public VideoTech getVideoTech() {
979 return mVideoTechManager.getVideoTech();
980 }
981
Eric Erfanianccca3152017-02-22 16:32:36 -0800982 public String getCallbackNumber() {
983 if (callbackNumber == null) {
984 // Show the emergency callback number if either:
985 // 1. This is an emergency call.
986 // 2. The phone is in Emergency Callback Mode, which means we should show the callback
987 // number.
988 boolean showCallbackNumber = hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE);
989
990 if (isEmergencyCall() || showCallbackNumber) {
991 callbackNumber = getSubscriptionNumber();
992 } else {
993 StatusHints statusHints = getTelecomCall().getDetails().getStatusHints();
994 if (statusHints != null) {
995 Bundle extras = statusHints.getExtras();
996 if (extras != null) {
997 callbackNumber = extras.getString(TelecomManager.EXTRA_CALL_BACK_NUMBER);
998 }
999 }
1000 }
1001
1002 String simNumber =
1003 mContext.getSystemService(TelecomManager.class).getLine1Number(getAccountHandle());
1004 if (!showCallbackNumber && PhoneNumberUtils.compare(callbackNumber, simNumber)) {
1005 LogUtil.v(
1006 "DialerCall.getCallbackNumber",
1007 "numbers are the same (and callback number is not being forced to show);"
1008 + " not showing the callback number");
1009 callbackNumber = "";
1010 }
1011 if (callbackNumber == null) {
1012 callbackNumber = "";
1013 }
1014 }
1015 return callbackNumber;
1016 }
1017
1018 private String getSubscriptionNumber() {
1019 // If it's an emergency call, and they're not populating the callback number,
1020 // then try to fall back to the phone sub info (to hopefully get the SIM's
1021 // number directly from the telephony layer).
1022 PhoneAccountHandle accountHandle = getAccountHandle();
1023 if (accountHandle != null) {
1024 PhoneAccount account =
1025 mContext.getSystemService(TelecomManager.class).getPhoneAccount(accountHandle);
1026 if (account != null) {
1027 return getNumberFromHandle(account.getSubscriptionAddress());
1028 }
1029 }
1030 return null;
1031 }
1032
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001033 @Override
1034 public void onVideoTechStateChanged() {
1035 update();
1036 }
1037
1038 @Override
1039 public void onSessionModificationStateChanged() {
1040 for (DialerCallListener listener : mListeners) {
1041 listener.onDialerCallSessionModificationStateChange();
1042 }
1043 }
1044
1045 @Override
1046 public void onCameraDimensionsChanged(int width, int height) {
1047 InCallVideoCallCallbackNotifier.getInstance().cameraDimensionsChanged(this, width, height);
1048 }
1049
1050 @Override
1051 public void onPeerDimensionsChanged(int width, int height) {
1052 InCallVideoCallCallbackNotifier.getInstance().peerDimensionsChanged(this, width, height);
1053 }
1054
1055 @Override
1056 public void onVideoUpgradeRequestReceived() {
1057 LogUtil.enterBlock("DialerCall.onVideoUpgradeRequestReceived");
1058
1059 for (DialerCallListener listener : mListeners) {
1060 listener.onDialerCallUpgradeToVideo();
1061 }
1062
1063 update();
1064 }
1065
Eric Erfanianccca3152017-02-22 16:32:36 -08001066 /**
1067 * Specifies whether a number is in the call history or not. {@link #CALL_HISTORY_STATUS_UNKNOWN}
1068 * means there is no result.
1069 */
1070 @IntDef({
1071 CALL_HISTORY_STATUS_UNKNOWN,
1072 CALL_HISTORY_STATUS_PRESENT,
1073 CALL_HISTORY_STATUS_NOT_PRESENT
1074 })
1075 @Retention(RetentionPolicy.SOURCE)
1076 public @interface CallHistoryStatus {}
1077
1078 /* Defines different states of this call */
1079 public static class State {
1080
1081 public static final int INVALID = 0;
1082 public static final int NEW = 1; /* The call is new. */
1083 public static final int IDLE = 2; /* The call is idle. Nothing active */
1084 public static final int ACTIVE = 3; /* There is an active call */
1085 public static final int INCOMING = 4; /* A normal incoming phone call */
1086 public static final int CALL_WAITING = 5; /* Incoming call while another is active */
1087 public static final int DIALING = 6; /* An outgoing call during dial phase */
1088 public static final int REDIALING = 7; /* Subsequent dialing attempt after a failure */
1089 public static final int ONHOLD = 8; /* An active phone call placed on hold */
1090 public static final int DISCONNECTING = 9; /* A call is being ended. */
1091 public static final int DISCONNECTED = 10; /* State after a call disconnects */
1092 public static final int CONFERENCED = 11; /* DialerCall part of a conference call */
1093 public static final int SELECT_PHONE_ACCOUNT = 12; /* Waiting for account selection */
1094 public static final int CONNECTING = 13; /* Waiting for Telecom broadcast to finish */
1095 public static final int BLOCKED = 14; /* The number was found on the block list */
1096 public static final int PULLING = 15; /* An external call being pulled to the device */
1097
1098 public static boolean isConnectingOrConnected(int state) {
1099 switch (state) {
1100 case ACTIVE:
1101 case INCOMING:
1102 case CALL_WAITING:
1103 case CONNECTING:
1104 case DIALING:
1105 case PULLING:
1106 case REDIALING:
1107 case ONHOLD:
1108 case CONFERENCED:
1109 return true;
1110 default:
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001111 return false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001112 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001113 }
1114
1115 public static boolean isDialing(int state) {
1116 return state == DIALING || state == PULLING || state == REDIALING;
1117 }
1118
1119 public static String toString(int state) {
1120 switch (state) {
1121 case INVALID:
1122 return "INVALID";
1123 case NEW:
1124 return "NEW";
1125 case IDLE:
1126 return "IDLE";
1127 case ACTIVE:
1128 return "ACTIVE";
1129 case INCOMING:
1130 return "INCOMING";
1131 case CALL_WAITING:
1132 return "CALL_WAITING";
1133 case DIALING:
1134 return "DIALING";
1135 case PULLING:
1136 return "PULLING";
1137 case REDIALING:
1138 return "REDIALING";
1139 case ONHOLD:
1140 return "ONHOLD";
1141 case DISCONNECTING:
1142 return "DISCONNECTING";
1143 case DISCONNECTED:
1144 return "DISCONNECTED";
1145 case CONFERENCED:
1146 return "CONFERENCED";
1147 case SELECT_PHONE_ACCOUNT:
1148 return "SELECT_PHONE_ACCOUNT";
1149 case CONNECTING:
1150 return "CONNECTING";
1151 case BLOCKED:
1152 return "BLOCKED";
1153 default:
1154 return "UNKNOWN";
1155 }
1156 }
1157 }
1158
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001159 /** Camera direction constants */
1160 public static class CameraDirection {
Eric Erfanianccca3152017-02-22 16:32:36 -08001161 public static final int CAMERA_DIRECTION_UNKNOWN = -1;
1162 public static final int CAMERA_DIRECTION_FRONT_FACING = CameraCharacteristics.LENS_FACING_FRONT;
1163 public static final int CAMERA_DIRECTION_BACK_FACING = CameraCharacteristics.LENS_FACING_BACK;
Eric Erfanianccca3152017-02-22 16:32:36 -08001164 }
1165
1166 /**
1167 * Tracks any state variables that is useful for logging. There is some amount of overlap with
1168 * existing call member variables, but this duplication helps to ensure that none of these logging
1169 * variables will interface with/and affect call logic.
1170 */
1171 public static class LogState {
1172
1173 public DisconnectCause disconnectCause;
1174 public boolean isIncoming = false;
1175 public int contactLookupResult = ContactLookupResult.Type.UNKNOWN_LOOKUP_RESULT_TYPE;
1176 public CallSpecificAppData callSpecificAppData;
1177 // If this was a conference call, the total number of calls involved in the conference.
1178 public int conferencedCalls = 0;
1179 public long duration = 0;
1180 public boolean isLogged = false;
1181
1182 private static String lookupToString(int lookupType) {
1183 switch (lookupType) {
1184 case ContactLookupResult.Type.LOCAL_CONTACT:
1185 return "Local";
1186 case ContactLookupResult.Type.LOCAL_CACHE:
1187 return "Cache";
1188 case ContactLookupResult.Type.REMOTE:
1189 return "Remote";
1190 case ContactLookupResult.Type.EMERGENCY:
1191 return "Emergency";
1192 case ContactLookupResult.Type.VOICEMAIL:
1193 return "Voicemail";
1194 default:
1195 return "Not found";
1196 }
1197 }
1198
1199 private static String initiationToString(CallSpecificAppData callSpecificAppData) {
1200 if (callSpecificAppData == null) {
1201 return "null";
1202 }
1203 switch (callSpecificAppData.callInitiationType) {
1204 case CallInitiationType.Type.INCOMING_INITIATION:
1205 return "Incoming";
1206 case CallInitiationType.Type.DIALPAD:
1207 return "Dialpad";
1208 case CallInitiationType.Type.SPEED_DIAL:
1209 return "Speed Dial";
1210 case CallInitiationType.Type.REMOTE_DIRECTORY:
1211 return "Remote Directory";
1212 case CallInitiationType.Type.SMART_DIAL:
1213 return "Smart Dial";
1214 case CallInitiationType.Type.REGULAR_SEARCH:
1215 return "Regular Search";
1216 case CallInitiationType.Type.CALL_LOG:
1217 return "DialerCall Log";
1218 case CallInitiationType.Type.CALL_LOG_FILTER:
1219 return "DialerCall Log Filter";
1220 case CallInitiationType.Type.VOICEMAIL_LOG:
1221 return "Voicemail Log";
1222 case CallInitiationType.Type.CALL_DETAILS:
1223 return "DialerCall Details";
1224 case CallInitiationType.Type.QUICK_CONTACTS:
1225 return "Quick Contacts";
1226 case CallInitiationType.Type.EXTERNAL_INITIATION:
1227 return "External";
1228 case CallInitiationType.Type.LAUNCHER_SHORTCUT:
1229 return "Launcher Shortcut";
1230 default:
1231 return "Unknown: " + callSpecificAppData.callInitiationType;
1232 }
1233 }
1234
1235 @Override
1236 public String toString() {
1237 return String.format(
1238 Locale.US,
1239 "["
1240 + "%s, " // DisconnectCause toString already describes the object type
1241 + "isIncoming: %s, "
1242 + "contactLookup: %s, "
1243 + "callInitiation: %s, "
1244 + "duration: %s"
1245 + "]",
1246 disconnectCause,
1247 isIncoming,
1248 lookupToString(contactLookupResult),
1249 initiationToString(callSpecificAppData),
1250 duration);
1251 }
1252 }
1253
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001254 private static class VideoTechManager {
1255 private final EmptyVideoTech emptyVideoTech = new EmptyVideoTech();
1256 private final VideoTech[] videoTechs;
1257 private VideoTech savedTech;
1258
1259 VideoTechManager(DialerCall call) {
1260 String phoneNumber = call.getNumber();
1261
1262 // Insert order here determines the priority of that video tech option
1263 videoTechs =
1264 new VideoTech[] {
1265 new ImsVideoTech(call, call.mTelecomCall),
1266 new RcsVideoShare(
1267 EnrichedCallComponent.get(call.mContext).getEnrichedCallManager(),
1268 call,
1269 phoneNumber != null ? phoneNumber : "")
1270 };
1271 }
1272
1273 VideoTech getVideoTech() {
1274 if (savedTech != null) {
1275 return savedTech;
1276 }
1277
1278 for (VideoTech tech : videoTechs) {
1279 if (tech.isAvailable()) {
1280 // Remember the first VideoTech that becomes available and always use it
1281 savedTech = tech;
1282 return savedTech;
1283 }
1284 }
1285
1286 return emptyVideoTech;
1287 }
1288
1289 void dispatchCallStateChanged(int newState) {
1290 for (VideoTech videoTech : videoTechs) {
1291 videoTech.onCallStateChanged(newState);
1292 }
1293 }
1294 }
1295
Eric Erfanianccca3152017-02-22 16:32:36 -08001296 /** Called when canned text responses have been loaded. */
1297 public interface CannedTextResponsesLoadedListener {
1298 void onCannedTextResponsesLoaded(DialerCall call);
1299 }
1300}