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