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