blob: acedf41f1dac96650d86c5cb438f3fc9fad9ef7d [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;
Eric Erfanianc857f902017-05-15 14:05:33 -070059import com.android.dialer.theme.R;
Eric Erfanian8369df02017-05-03 10:27:13 -070060import com.android.incallui.audiomode.AudioModeProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080061import com.android.incallui.latencyreport.LatencyReport;
62import com.android.incallui.util.TelecomCallUtil;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070063import com.android.incallui.videotech.VideoTech;
64import com.android.incallui.videotech.VideoTech.VideoTechListener;
65import com.android.incallui.videotech.empty.EmptyVideoTech;
66import com.android.incallui.videotech.ims.ImsVideoTech;
Eric Erfaniand8046e52017-04-06 09:41:50 -070067import com.android.incallui.videotech.lightbringer.LightbringerTech;
Eric Erfanian90508232017-03-24 09:31:16 -070068import com.android.incallui.videotech.utils.VideoUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080069import java.lang.annotation.Retention;
70import java.lang.annotation.RetentionPolicy;
71import java.util.ArrayList;
72import java.util.List;
73import java.util.Locale;
74import java.util.Objects;
75import java.util.UUID;
76import java.util.concurrent.CopyOnWriteArrayList;
77import java.util.concurrent.TimeUnit;
78
79/** Describes a single call and its state. */
Eric Erfaniand5e47f62017-03-15 14:41:07 -070080public class DialerCall implements VideoTechListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080081
82 public static final int CALL_HISTORY_STATUS_UNKNOWN = 0;
83 public static final int CALL_HISTORY_STATUS_PRESENT = 1;
84 public static final int CALL_HISTORY_STATUS_NOT_PRESENT = 2;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070085
86 // Hard coded property for {@code Call}. Upstreamed change from Motorola.
87 // TODO(b/35359461): Move it to Telecom in framework.
88 public static final int PROPERTY_CODEC_KNOWN = 0x04000000;
89
Eric Erfanianccca3152017-02-22 16:32:36 -080090 private static final String ID_PREFIX = "DialerCall_";
91 private static final String CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS =
92 "emergency_callback_window_millis";
93 private static int sIdCounter = 0;
94
95 /**
Eric Erfanianc857f902017-05-15 14:05:33 -070096 * A counter used to append to restricted/private/hidden calls so that users can identify them in
97 * a conversation. This value is reset in {@link CallList#onCallRemoved(Context, Call)} when there
98 * are no live calls.
99 */
100 private static int sHiddenCounter;
101
102 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800103 * The unique call ID for every call. This will help us to identify each call and allow us the
104 * ability to stitch impressions to calls if needed.
105 */
106 private final String uniqueCallId = UUID.randomUUID().toString();
107
108 private final Call mTelecomCall;
109 private final LatencyReport mLatencyReport;
110 private final String mId;
Eric Erfanianc857f902017-05-15 14:05:33 -0700111 private final int mHiddenId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800112 private final List<String> mChildCallIds = new ArrayList<>();
Eric Erfanianccca3152017-02-22 16:32:36 -0800113 private final LogState mLogState = new LogState();
114 private final Context mContext;
115 private final DialerCallDelegate mDialerCallDelegate;
116 private final List<DialerCallListener> mListeners = new CopyOnWriteArrayList<>();
117 private final List<CannedTextResponsesLoadedListener> mCannedTextResponsesLoadedListeners =
118 new CopyOnWriteArrayList<>();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700119 private final VideoTechManager mVideoTechManager;
Eric Erfanianccca3152017-02-22 16:32:36 -0800120
121 private boolean mIsEmergencyCall;
122 private Uri mHandle;
123 private int mState = State.INVALID;
124 private DisconnectCause mDisconnectCause;
125
126 private boolean hasShownWiFiToLteHandoverToast;
127 private boolean doNotShowDialogForHandoffToWifiFailure;
128
Eric Erfanianccca3152017-02-22 16:32:36 -0800129 private String mChildNumber;
130 private String mLastForwardedNumber;
131 private String mCallSubject;
132 private PhoneAccountHandle mPhoneAccountHandle;
133 @CallHistoryStatus private int mCallHistoryStatus = CALL_HISTORY_STATUS_UNKNOWN;
134 private boolean mIsSpam;
135 private boolean mIsBlocked;
136 private boolean isInUserSpamList;
137 private boolean isInUserWhiteList;
138 private boolean isInGlobalSpamList;
139 private boolean didShowCameraPermission;
140 private String callProviderLabel;
141 private String callbackNumber;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700142 private int mCameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
Eric Erfanian8369df02017-05-03 10:27:13 -0700143 private EnrichedCallCapabilities mEnrichedCallCapabilities;
144 private Session mEnrichedCallSession;
Eric Erfanianccca3152017-02-22 16:32:36 -0800145
146 public static String getNumberFromHandle(Uri handle) {
147 return handle == null ? "" : handle.getSchemeSpecificPart();
148 }
149
150 /**
151 * Whether the call is put on hold by remote party. This is different than the {@link
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700152 * State#ONHOLD} state which indicates that the call is being held locally on the device.
Eric Erfanianccca3152017-02-22 16:32:36 -0800153 */
154 private boolean isRemotelyHeld;
155
156 /**
157 * Indicates whether the phone account associated with this call supports specifying a call
158 * subject.
159 */
160 private boolean mIsCallSubjectSupported;
161
162 private final Call.Callback mTelecomCallCallback =
163 new Call.Callback() {
164 @Override
165 public void onStateChanged(Call call, int newState) {
166 LogUtil.v("TelecomCallCallback.onStateChanged", "call=" + call + " newState=" + newState);
167 update();
168 }
169
170 @Override
171 public void onParentChanged(Call call, Call newParent) {
172 LogUtil.v(
173 "TelecomCallCallback.onParentChanged", "call=" + call + " newParent=" + newParent);
174 update();
175 }
176
177 @Override
178 public void onChildrenChanged(Call call, List<Call> children) {
179 update();
180 }
181
182 @Override
183 public void onDetailsChanged(Call call, Call.Details details) {
184 LogUtil.v("TelecomCallCallback.onStateChanged", " call=" + call + " details=" + details);
185 update();
186 }
187
188 @Override
189 public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {
190 LogUtil.v(
191 "TelecomCallCallback.onStateChanged",
192 "call=" + call + " cannedTextResponses=" + cannedTextResponses);
193 for (CannedTextResponsesLoadedListener listener : mCannedTextResponsesLoadedListeners) {
194 listener.onCannedTextResponsesLoaded(DialerCall.this);
195 }
196 }
197
198 @Override
199 public void onPostDialWait(Call call, String remainingPostDialSequence) {
200 LogUtil.v(
201 "TelecomCallCallback.onStateChanged",
202 "call=" + call + " remainingPostDialSequence=" + remainingPostDialSequence);
203 update();
204 }
205
206 @Override
207 public void onVideoCallChanged(Call call, VideoCall videoCall) {
208 LogUtil.v(
209 "TelecomCallCallback.onStateChanged", "call=" + call + " videoCall=" + videoCall);
210 update();
211 }
212
213 @Override
214 public void onCallDestroyed(Call call) {
215 LogUtil.v("TelecomCallCallback.onStateChanged", "call=" + call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700216 unregisterCallback();
Eric Erfanianccca3152017-02-22 16:32:36 -0800217 }
218
219 @Override
220 public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
221 LogUtil.v(
222 "DialerCall.onConferenceableCallsChanged",
223 "call %s, conferenceable calls: %d",
224 call,
225 conferenceableCalls.size());
226 update();
227 }
228
229 @Override
230 public void onConnectionEvent(android.telecom.Call call, String event, Bundle extras) {
231 LogUtil.v(
232 "DialerCall.onConnectionEvent",
233 "Call: " + call + ", Event: " + event + ", Extras: " + extras);
234 switch (event) {
235 // The Previous attempt to Merge two calls together has failed in Telecom. We must
236 // now update the UI to possibly re-enable the Merge button based on the number of
237 // currently conferenceable calls available or Connection Capabilities.
238 case android.telecom.Connection.EVENT_CALL_MERGE_FAILED:
239 update();
240 break;
241 case TelephonyManagerCompat.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE:
242 notifyWiFiToLteHandover();
243 break;
244 case TelephonyManagerCompat.EVENT_HANDOVER_TO_WIFI_FAILED:
245 notifyHandoverToWifiFailed();
246 break;
247 case TelephonyManagerCompat.EVENT_CALL_REMOTELY_HELD:
248 isRemotelyHeld = true;
249 update();
250 break;
251 case TelephonyManagerCompat.EVENT_CALL_REMOTELY_UNHELD:
252 isRemotelyHeld = false;
253 update();
254 break;
Eric Erfanianc857f902017-05-15 14:05:33 -0700255 case TelephonyManagerCompat.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC:
256 notifyInternationalCallOnWifi();
257 break;
Eric Erfanianccca3152017-02-22 16:32:36 -0800258 default:
259 break;
260 }
261 }
262 };
Eric Erfanianc857f902017-05-15 14:05:33 -0700263
Eric Erfanianccca3152017-02-22 16:32:36 -0800264 private long mTimeAddedMs;
265
266 public DialerCall(
267 Context context,
268 DialerCallDelegate dialerCallDelegate,
269 Call telecomCall,
270 LatencyReport latencyReport,
271 boolean registerCallback) {
272 Assert.isNotNull(context);
273 mContext = context;
274 mDialerCallDelegate = dialerCallDelegate;
275 mTelecomCall = telecomCall;
276 mLatencyReport = latencyReport;
277 mId = ID_PREFIX + Integer.toString(sIdCounter++);
278
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700279 // Must be after assigning mTelecomCall
280 mVideoTechManager = new VideoTechManager(this);
281
282 updateFromTelecomCall();
Eric Erfanianc857f902017-05-15 14:05:33 -0700283 if (isHiddenNumber() && TextUtils.isEmpty(getNumber())) {
284 mHiddenId = ++sHiddenCounter;
285 } else {
286 mHiddenId = 0;
287 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800288
289 if (registerCallback) {
290 mTelecomCall.registerCallback(mTelecomCallCallback);
291 }
292
293 mTimeAddedMs = System.currentTimeMillis();
294 parseCallSpecificAppData();
295 }
296
297 private static int translateState(int state) {
298 switch (state) {
299 case Call.STATE_NEW:
300 case Call.STATE_CONNECTING:
301 return DialerCall.State.CONNECTING;
302 case Call.STATE_SELECT_PHONE_ACCOUNT:
303 return DialerCall.State.SELECT_PHONE_ACCOUNT;
304 case Call.STATE_DIALING:
305 return DialerCall.State.DIALING;
306 case Call.STATE_PULLING_CALL:
307 return DialerCall.State.PULLING;
308 case Call.STATE_RINGING:
309 return DialerCall.State.INCOMING;
310 case Call.STATE_ACTIVE:
311 return DialerCall.State.ACTIVE;
312 case Call.STATE_HOLDING:
313 return DialerCall.State.ONHOLD;
314 case Call.STATE_DISCONNECTED:
315 return DialerCall.State.DISCONNECTED;
316 case Call.STATE_DISCONNECTING:
317 return DialerCall.State.DISCONNECTING;
318 default:
319 return DialerCall.State.INVALID;
320 }
321 }
322
323 public static boolean areSame(DialerCall call1, DialerCall call2) {
324 if (call1 == null && call2 == null) {
325 return true;
326 } else if (call1 == null || call2 == null) {
327 return false;
328 }
329
330 // otherwise compare call Ids
331 return call1.getId().equals(call2.getId());
332 }
333
334 public static boolean areSameNumber(DialerCall call1, DialerCall call2) {
335 if (call1 == null && call2 == null) {
336 return true;
337 } else if (call1 == null || call2 == null) {
338 return false;
339 }
340
341 // otherwise compare call Numbers
342 return TextUtils.equals(call1.getNumber(), call2.getNumber());
343 }
344
345 public void addListener(DialerCallListener listener) {
346 Assert.isMainThread();
347 mListeners.add(listener);
348 }
349
350 public void removeListener(DialerCallListener listener) {
351 Assert.isMainThread();
352 mListeners.remove(listener);
353 }
354
355 public void addCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
356 Assert.isMainThread();
357 mCannedTextResponsesLoadedListeners.add(listener);
358 }
359
360 public void removeCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
361 Assert.isMainThread();
362 mCannedTextResponsesLoadedListeners.remove(listener);
363 }
364
365 public void notifyWiFiToLteHandover() {
366 LogUtil.i("DialerCall.notifyWiFiToLteHandover", "");
367 for (DialerCallListener listener : mListeners) {
368 listener.onWiFiToLteHandover();
369 }
370 }
371
372 public void notifyHandoverToWifiFailed() {
373 LogUtil.i("DialerCall.notifyHandoverToWifiFailed", "");
374 for (DialerCallListener listener : mListeners) {
375 listener.onHandoverToWifiFailure();
376 }
377 }
378
Eric Erfanianc857f902017-05-15 14:05:33 -0700379 public void notifyInternationalCallOnWifi() {
380 LogUtil.enterBlock("DialerCall.notifyInternationalCallOnWifi");
381 for (DialerCallListener dialerCallListener : mListeners) {
382 dialerCallListener.onInternationalCallOnWifi();
383 }
384 }
385
Eric Erfanianccca3152017-02-22 16:32:36 -0800386 /* package-private */ Call getTelecomCall() {
387 return mTelecomCall;
388 }
389
390 public StatusHints getStatusHints() {
391 return mTelecomCall.getDetails().getStatusHints();
392 }
393
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700394 public int getCameraDir() {
395 return mCameraDirection;
396 }
397
398 public void setCameraDir(int cameraDir) {
399 if (cameraDir == CameraDirection.CAMERA_DIRECTION_FRONT_FACING
400 || cameraDir == CameraDirection.CAMERA_DIRECTION_BACK_FACING) {
401 mCameraDirection = cameraDir;
402 } else {
403 mCameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
404 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800405 }
406
407 private void update() {
408 Trace.beginSection("Update");
409 int oldState = getState();
410 // We want to potentially register a video call callback here.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700411 updateFromTelecomCall();
Eric Erfanianccca3152017-02-22 16:32:36 -0800412 if (oldState != getState() && getState() == DialerCall.State.DISCONNECTED) {
413 for (DialerCallListener listener : mListeners) {
414 listener.onDialerCallDisconnect();
415 }
416 } else {
417 for (DialerCallListener listener : mListeners) {
418 listener.onDialerCallUpdate();
419 }
420 }
421 Trace.endSection();
422 }
423
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700424 private void updateFromTelecomCall() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800425 LogUtil.v("DialerCall.updateFromTelecomCall", mTelecomCall.toString());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700426
427 mVideoTechManager.dispatchCallStateChanged(mTelecomCall.getState());
428
Eric Erfanianccca3152017-02-22 16:32:36 -0800429 final int translatedState = translateState(mTelecomCall.getState());
430 if (mState != State.BLOCKED) {
431 setState(translatedState);
432 setDisconnectCause(mTelecomCall.getDetails().getDisconnectCause());
Eric Erfanianccca3152017-02-22 16:32:36 -0800433 }
434
435 mChildCallIds.clear();
436 final int numChildCalls = mTelecomCall.getChildren().size();
437 for (int i = 0; i < numChildCalls; i++) {
438 mChildCallIds.add(
439 mDialerCallDelegate
440 .getDialerCallFromTelecomCall(mTelecomCall.getChildren().get(i))
441 .getId());
442 }
443
444 // The number of conferenced calls can change over the course of the call, so use the
445 // maximum number of conferenced child calls as the metric for conference call usage.
446 mLogState.conferencedCalls = Math.max(numChildCalls, mLogState.conferencedCalls);
447
448 updateFromCallExtras(mTelecomCall.getDetails().getExtras());
449
450 // If the handle of the call has changed, update state for the call determining if it is an
451 // emergency call.
452 Uri newHandle = mTelecomCall.getDetails().getHandle();
453 if (!Objects.equals(mHandle, newHandle)) {
454 mHandle = newHandle;
455 updateEmergencyCallState();
456 }
457
458 // If the phone account handle of the call is set, cache capability bit indicating whether
459 // the phone account supports call subjects.
460 PhoneAccountHandle newPhoneAccountHandle = mTelecomCall.getDetails().getAccountHandle();
461 if (!Objects.equals(mPhoneAccountHandle, newPhoneAccountHandle)) {
462 mPhoneAccountHandle = newPhoneAccountHandle;
463
464 if (mPhoneAccountHandle != null) {
465 PhoneAccount phoneAccount =
466 mContext.getSystemService(TelecomManager.class).getPhoneAccount(mPhoneAccountHandle);
467 if (phoneAccount != null) {
468 mIsCallSubjectSupported =
469 phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT);
470 }
471 }
472 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800473 }
474
475 /**
476 * Tests corruption of the {@code callExtras} bundle by calling {@link
477 * Bundle#containsKey(String)}. If the bundle is corrupted a {@link IllegalArgumentException} will
478 * be thrown and caught by this function.
479 *
480 * @param callExtras the bundle to verify
481 * @return {@code true} if the bundle is corrupted, {@code false} otherwise.
482 */
483 protected boolean areCallExtrasCorrupted(Bundle callExtras) {
484 /**
485 * There's currently a bug in Telephony service (b/25613098) that could corrupt the extras
486 * bundle, resulting in a IllegalArgumentException while validating data under {@link
487 * Bundle#containsKey(String)}.
488 */
489 try {
490 callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS);
491 return false;
492 } catch (IllegalArgumentException e) {
493 LogUtil.e(
494 "DialerCall.areCallExtrasCorrupted", "callExtras is corrupted, ignoring exception", e);
495 return true;
496 }
497 }
498
499 protected void updateFromCallExtras(Bundle callExtras) {
500 if (callExtras == null || areCallExtrasCorrupted(callExtras)) {
501 /**
502 * If the bundle is corrupted, abandon information update as a work around. These are not
503 * critical for the dialer to function.
504 */
505 return;
506 }
507 // Check for a change in the child address and notify any listeners.
508 if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) {
509 String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS);
510 if (!Objects.equals(childNumber, mChildNumber)) {
511 mChildNumber = childNumber;
512 for (DialerCallListener listener : mListeners) {
513 listener.onDialerCallChildNumberChange();
514 }
515 }
516 }
517
518 // Last forwarded number comes in as an array of strings. We want to choose the
519 // last item in the array. The forwarding numbers arrive independently of when the
520 // call is originally set up, so we need to notify the the UI of the change.
521 if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) {
522 ArrayList<String> lastForwardedNumbers =
523 callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER);
524
525 if (lastForwardedNumbers != null) {
526 String lastForwardedNumber = null;
527 if (!lastForwardedNumbers.isEmpty()) {
528 lastForwardedNumber = lastForwardedNumbers.get(lastForwardedNumbers.size() - 1);
529 }
530
531 if (!Objects.equals(lastForwardedNumber, mLastForwardedNumber)) {
532 mLastForwardedNumber = lastForwardedNumber;
533 for (DialerCallListener listener : mListeners) {
534 listener.onDialerCallLastForwardedNumberChange();
535 }
536 }
537 }
538 }
539
540 // DialerCall subject is present in the extras at the start of call, so we do not need to
541 // notify any other listeners of this.
542 if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) {
543 String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT);
544 if (!Objects.equals(mCallSubject, callSubject)) {
545 mCallSubject = callSubject;
546 }
547 }
548 }
549
Eric Erfanianccca3152017-02-22 16:32:36 -0800550 public String getId() {
551 return mId;
552 }
553
Eric Erfanianc857f902017-05-15 14:05:33 -0700554 /**
555 * @return name appended with a number if the number is restricted/unknown and the user has
556 * received more than one restricted/unknown call.
557 */
558 @Nullable
559 public String updateNameIfRestricted(@Nullable String name) {
560 if (name != null && isHiddenNumber() && mHiddenId != 0 && sHiddenCounter > 1) {
561 return mContext.getString(R.string.unknown_counter, name, mHiddenId);
562 }
563 return name;
564 }
565
566 public static void clearRestrictedCount() {
567 sHiddenCounter = 0;
568 }
569
570 private boolean isHiddenNumber() {
571 return getNumberPresentation() == TelecomManager.PRESENTATION_RESTRICTED
572 || getNumberPresentation() == TelecomManager.PRESENTATION_UNKNOWN;
573 }
574
Eric Erfanianccca3152017-02-22 16:32:36 -0800575 public boolean hasShownWiFiToLteHandoverToast() {
576 return hasShownWiFiToLteHandoverToast;
577 }
578
579 public void setHasShownWiFiToLteHandoverToast() {
580 hasShownWiFiToLteHandoverToast = true;
581 }
582
583 public boolean showWifiHandoverAlertAsToast() {
584 return doNotShowDialogForHandoffToWifiFailure;
585 }
586
587 public void setDoNotShowDialogForHandoffToWifiFailure(boolean bool) {
588 doNotShowDialogForHandoffToWifiFailure = bool;
589 }
590
591 public long getTimeAddedMs() {
592 return mTimeAddedMs;
593 }
594
595 @Nullable
596 public String getNumber() {
597 return TelecomCallUtil.getNumber(mTelecomCall);
598 }
599
600 public void blockCall() {
601 mTelecomCall.reject(false, null);
602 setState(State.BLOCKED);
603 }
604
605 @Nullable
606 public Uri getHandle() {
607 return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandle();
608 }
609
610 public boolean isEmergencyCall() {
611 return mIsEmergencyCall;
612 }
613
614 public boolean isPotentialEmergencyCallback() {
615 // The property PROPERTY_EMERGENCY_CALLBACK_MODE is only set for CDMA calls when the system
616 // is actually in emergency callback mode (ie data is disabled).
617 if (hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE)) {
618 return true;
619 }
620 // We want to treat any incoming call that arrives a short time after an outgoing emergency call
621 // as a potential emergency callback.
622 if (getExtras() != null
623 && getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0)
624 > 0) {
625 long lastEmergencyCallMillis =
626 getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0);
627 if (isInEmergencyCallbackWindow(lastEmergencyCallMillis)) {
628 return true;
629 }
630 }
631 return false;
632 }
633
634 boolean isInEmergencyCallbackWindow(long timestampMillis) {
635 long emergencyCallbackWindowMillis =
636 ConfigProviderBindings.get(mContext)
637 .getLong(CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS, TimeUnit.MINUTES.toMillis(5));
638 return System.currentTimeMillis() - timestampMillis < emergencyCallbackWindowMillis;
639 }
640
641 public int getState() {
642 if (mTelecomCall != null && mTelecomCall.getParent() != null) {
643 return State.CONFERENCED;
644 } else {
645 return mState;
646 }
647 }
648
649 public void setState(int state) {
650 mState = state;
651 if (mState == State.INCOMING) {
652 mLogState.isIncoming = true;
653 } else if (mState == State.DISCONNECTED) {
654 mLogState.duration =
655 getConnectTimeMillis() == 0 ? 0 : System.currentTimeMillis() - getConnectTimeMillis();
656 }
657 }
658
659 public int getNumberPresentation() {
660 return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getHandlePresentation();
661 }
662
663 public int getCnapNamePresentation() {
664 return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getCallerDisplayNamePresentation();
665 }
666
667 @Nullable
668 public String getCnapName() {
669 return mTelecomCall == null ? null : getTelecomCall().getDetails().getCallerDisplayName();
670 }
671
672 public Bundle getIntentExtras() {
673 return mTelecomCall.getDetails().getIntentExtras();
674 }
675
676 @Nullable
677 public Bundle getExtras() {
678 return mTelecomCall == null ? null : mTelecomCall.getDetails().getExtras();
679 }
680
681 /** @return The child number for the call, or {@code null} if none specified. */
682 public String getChildNumber() {
683 return mChildNumber;
684 }
685
686 /** @return The last forwarded number for the call, or {@code null} if none specified. */
687 public String getLastForwardedNumber() {
688 return mLastForwardedNumber;
689 }
690
691 /** @return The call subject, or {@code null} if none specified. */
692 public String getCallSubject() {
693 return mCallSubject;
694 }
695
696 /**
697 * @return {@code true} if the call's phone account supports call subjects, {@code false}
698 * otherwise.
699 */
700 public boolean isCallSubjectSupported() {
701 return mIsCallSubjectSupported;
702 }
703
704 /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
705 public DisconnectCause getDisconnectCause() {
706 if (mState == State.DISCONNECTED || mState == State.IDLE) {
707 return mDisconnectCause;
708 }
709
710 return new DisconnectCause(DisconnectCause.UNKNOWN);
711 }
712
713 public void setDisconnectCause(DisconnectCause disconnectCause) {
714 mDisconnectCause = disconnectCause;
715 mLogState.disconnectCause = mDisconnectCause;
716 }
717
718 /** Returns the possible text message responses. */
719 public List<String> getCannedSmsResponses() {
720 return mTelecomCall.getCannedTextResponses();
721 }
722
723 /** Checks if the call supports the given set of capabilities supplied as a bit mask. */
724 public boolean can(int capabilities) {
725 int supportedCapabilities = mTelecomCall.getDetails().getCallCapabilities();
726
727 if ((capabilities & Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) {
728 // We allow you to merge if the capabilities allow it or if it is a call with
729 // conferenceable calls.
730 if (mTelecomCall.getConferenceableCalls().isEmpty()
731 && ((Call.Details.CAPABILITY_MERGE_CONFERENCE & supportedCapabilities) == 0)) {
732 // Cannot merge calls if there are no calls to merge with.
733 return false;
734 }
735 capabilities &= ~Call.Details.CAPABILITY_MERGE_CONFERENCE;
736 }
737 return (capabilities == (capabilities & supportedCapabilities));
738 }
739
740 public boolean hasProperty(int property) {
741 return mTelecomCall.getDetails().hasProperty(property);
742 }
743
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700744 @NonNull
Eric Erfanianccca3152017-02-22 16:32:36 -0800745 public String getUniqueCallId() {
746 return uniqueCallId;
747 }
748
749 /** Gets the time when the call first became active. */
750 public long getConnectTimeMillis() {
751 return mTelecomCall.getDetails().getConnectTimeMillis();
752 }
753
754 public boolean isConferenceCall() {
755 return hasProperty(Call.Details.PROPERTY_CONFERENCE);
756 }
757
758 @Nullable
759 public GatewayInfo getGatewayInfo() {
760 return mTelecomCall == null ? null : mTelecomCall.getDetails().getGatewayInfo();
761 }
762
763 @Nullable
764 public PhoneAccountHandle getAccountHandle() {
765 return mTelecomCall == null ? null : mTelecomCall.getDetails().getAccountHandle();
766 }
767
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700768 /** @return The {@link VideoCall} instance associated with the {@link Call}. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800769 public VideoCall getVideoCall() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700770 return mTelecomCall == null ? null : mTelecomCall.getVideoCall();
Eric Erfanianccca3152017-02-22 16:32:36 -0800771 }
772
773 public List<String> getChildCallIds() {
774 return mChildCallIds;
775 }
776
777 public String getParentId() {
778 Call parentCall = mTelecomCall.getParent();
779 if (parentCall != null) {
780 return mDialerCallDelegate.getDialerCallFromTelecomCall(parentCall).getId();
781 }
782 return null;
783 }
784
785 public int getVideoState() {
786 return mTelecomCall.getDetails().getVideoState();
787 }
788
789 public boolean isVideoCall() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700790 return getVideoTech().isTransmittingOrReceiving();
791 }
792
793 public boolean hasReceivedVideoUpgradeRequest() {
794 return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState());
795 }
796
797 public boolean hasSentVideoUpgradeRequest() {
798 return VideoUtils.hasSentVideoUpgradeRequest(getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800799 }
800
801 /**
802 * Determines if the call handle is an emergency number or not and caches the result to avoid
803 * repeated calls to isEmergencyNumber.
804 */
805 private void updateEmergencyCallState() {
806 mIsEmergencyCall = TelecomCallUtil.isEmergencyCall(mTelecomCall);
807 }
808
Eric Erfanianccca3152017-02-22 16:32:36 -0800809 public LogState getLogState() {
810 return mLogState;
811 }
812
813 /**
814 * Determines if the call is an external call.
815 *
816 * <p>An external call is one which does not exist locally for the {@link
817 * android.telecom.ConnectionService} it is associated with.
818 *
819 * <p>External calls are only supported in N and higher.
820 *
821 * @return {@code true} if the call is an external call, {@code false} otherwise.
822 */
823 public boolean isExternalCall() {
824 return VERSION.SDK_INT >= VERSION_CODES.N
825 && hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL);
826 }
827
828 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800829 * Determines if answering this call will cause an ongoing video call to be dropped.
830 *
831 * @return {@code true} if answering this call will drop an ongoing video call, {@code false}
832 * otherwise.
833 */
834 public boolean answeringDisconnectsForegroundVideoCall() {
835 Bundle extras = getExtras();
836 if (extras == null
837 || !extras.containsKey(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL)) {
838 return false;
839 }
840 return extras.getBoolean(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL);
841 }
842
843 private void parseCallSpecificAppData() {
844 if (isExternalCall()) {
845 return;
846 }
847
848 mLogState.callSpecificAppData = CallIntentParser.getCallSpecificAppData(getIntentExtras());
849 if (mLogState.callSpecificAppData == null) {
Eric Erfanian8369df02017-05-03 10:27:13 -0700850
851 mLogState.callSpecificAppData =
852 CallSpecificAppData.newBuilder()
853 .setCallInitiationType(CallInitiationType.Type.EXTERNAL_INITIATION)
854 .build();
Eric Erfanianccca3152017-02-22 16:32:36 -0800855 }
856 if (getState() == State.INCOMING) {
Eric Erfanian8369df02017-05-03 10:27:13 -0700857 mLogState.callSpecificAppData =
858 mLogState
859 .callSpecificAppData
860 .toBuilder()
861 .setCallInitiationType(CallInitiationType.Type.INCOMING_INITIATION)
862 .build();
Eric Erfanianccca3152017-02-22 16:32:36 -0800863 }
864 }
865
866 @Override
867 public String toString() {
868 if (mTelecomCall == null) {
869 // This should happen only in testing since otherwise we would never have a null
870 // Telecom call.
871 return String.valueOf(mId);
872 }
873
874 return String.format(
875 Locale.US,
876 "[%s, %s, %s, %s, children:%s, parent:%s, "
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700877 + "conferenceable:%s, videoState:%s, mSessionModificationState:%d, CameraDir:%s]",
Eric Erfanianccca3152017-02-22 16:32:36 -0800878 mId,
879 State.toString(getState()),
880 Details.capabilitiesToString(mTelecomCall.getDetails().getCallCapabilities()),
881 Details.propertiesToString(mTelecomCall.getDetails().getCallProperties()),
882 mChildCallIds,
883 getParentId(),
884 this.mTelecomCall.getConferenceableCalls(),
885 VideoProfile.videoStateToString(mTelecomCall.getDetails().getVideoState()),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700886 getVideoTech().getSessionModificationState(),
887 getCameraDir());
Eric Erfanianccca3152017-02-22 16:32:36 -0800888 }
889
890 public String toSimpleString() {
891 return super.toString();
892 }
893
894 @CallHistoryStatus
895 public int getCallHistoryStatus() {
896 return mCallHistoryStatus;
897 }
898
899 public void setCallHistoryStatus(@CallHistoryStatus int callHistoryStatus) {
900 mCallHistoryStatus = callHistoryStatus;
901 }
902
903 public boolean didShowCameraPermission() {
904 return didShowCameraPermission;
905 }
906
907 public void setDidShowCameraPermission(boolean didShow) {
908 didShowCameraPermission = didShow;
909 }
910
911 public boolean isInGlobalSpamList() {
912 return isInGlobalSpamList;
913 }
914
915 public void setIsInGlobalSpamList(boolean inSpamList) {
916 isInGlobalSpamList = inSpamList;
917 }
918
919 public boolean isInUserSpamList() {
920 return isInUserSpamList;
921 }
922
923 public void setIsInUserSpamList(boolean inSpamList) {
924 isInUserSpamList = inSpamList;
925 }
926
927 public boolean isInUserWhiteList() {
928 return isInUserWhiteList;
929 }
930
931 public void setIsInUserWhiteList(boolean inWhiteList) {
932 isInUserWhiteList = inWhiteList;
933 }
934
935 public boolean isSpam() {
936 return mIsSpam;
937 }
938
939 public void setSpam(boolean isSpam) {
940 mIsSpam = isSpam;
941 }
942
943 public boolean isBlocked() {
944 return mIsBlocked;
945 }
946
947 public void setBlockedStatus(boolean isBlocked) {
948 mIsBlocked = isBlocked;
949 }
950
951 public boolean isRemotelyHeld() {
952 return isRemotelyHeld;
953 }
954
955 public boolean isIncoming() {
956 return mLogState.isIncoming;
957 }
958
959 public LatencyReport getLatencyReport() {
960 return mLatencyReport;
961 }
962
Eric Erfanian8369df02017-05-03 10:27:13 -0700963 @Nullable
964 public EnrichedCallCapabilities getEnrichedCallCapabilities() {
965 return mEnrichedCallCapabilities;
966 }
967
968 public void setEnrichedCallCapabilities(
969 @Nullable EnrichedCallCapabilities mEnrichedCallCapabilities) {
970 this.mEnrichedCallCapabilities = mEnrichedCallCapabilities;
971 }
972
973 @Nullable
974 public Session getEnrichedCallSession() {
975 return mEnrichedCallSession;
976 }
977
978 public void setEnrichedCallSession(@Nullable Session mEnrichedCallSession) {
979 this.mEnrichedCallSession = mEnrichedCallSession;
980 }
981
Eric Erfanianccca3152017-02-22 16:32:36 -0800982 public void unregisterCallback() {
983 mTelecomCall.unregisterCallback(mTelecomCallCallback);
984 }
985
Eric Erfanianccca3152017-02-22 16:32:36 -0800986 public void phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault) {
987 LogUtil.i(
988 "DialerCall.phoneAccountSelected",
989 "accountHandle: %s, setDefault: %b",
990 accountHandle,
991 setDefault);
992 mTelecomCall.phoneAccountSelected(accountHandle, setDefault);
993 }
994
995 public void disconnect() {
996 LogUtil.i("DialerCall.disconnect", "");
997 setState(DialerCall.State.DISCONNECTING);
998 for (DialerCallListener listener : mListeners) {
999 listener.onDialerCallUpdate();
1000 }
1001 mTelecomCall.disconnect();
1002 }
1003
1004 public void hold() {
1005 LogUtil.i("DialerCall.hold", "");
1006 mTelecomCall.hold();
1007 }
1008
1009 public void unhold() {
1010 LogUtil.i("DialerCall.unhold", "");
1011 mTelecomCall.unhold();
1012 }
1013
1014 public void splitFromConference() {
1015 LogUtil.i("DialerCall.splitFromConference", "");
1016 mTelecomCall.splitFromConference();
1017 }
1018
1019 public void answer(int videoState) {
1020 LogUtil.i("DialerCall.answer", "videoState: " + videoState);
1021 mTelecomCall.answer(videoState);
1022 }
1023
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001024 public void answer() {
1025 answer(mTelecomCall.getDetails().getVideoState());
1026 }
1027
Eric Erfanianccca3152017-02-22 16:32:36 -08001028 public void reject(boolean rejectWithMessage, String message) {
1029 LogUtil.i("DialerCall.reject", "");
1030 mTelecomCall.reject(rejectWithMessage, message);
1031 }
1032
1033 /** Return the string label to represent the call provider */
1034 public String getCallProviderLabel() {
1035 if (callProviderLabel == null) {
1036 PhoneAccount account = getPhoneAccount();
1037 if (account != null && !TextUtils.isEmpty(account.getLabel())) {
1038 List<PhoneAccountHandle> accounts =
1039 mContext.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts();
1040 if (accounts != null && accounts.size() > 1) {
1041 callProviderLabel = account.getLabel().toString();
1042 }
1043 }
1044 if (callProviderLabel == null) {
1045 callProviderLabel = "";
1046 }
1047 }
1048 return callProviderLabel;
1049 }
1050
1051 private PhoneAccount getPhoneAccount() {
1052 PhoneAccountHandle accountHandle = getAccountHandle();
1053 if (accountHandle == null) {
1054 return null;
1055 }
1056 return mContext.getSystemService(TelecomManager.class).getPhoneAccount(accountHandle);
1057 }
1058
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001059 public VideoTech getVideoTech() {
1060 return mVideoTechManager.getVideoTech();
1061 }
1062
Eric Erfanianccca3152017-02-22 16:32:36 -08001063 public String getCallbackNumber() {
1064 if (callbackNumber == null) {
1065 // Show the emergency callback number if either:
1066 // 1. This is an emergency call.
1067 // 2. The phone is in Emergency Callback Mode, which means we should show the callback
1068 // number.
1069 boolean showCallbackNumber = hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE);
1070
1071 if (isEmergencyCall() || showCallbackNumber) {
1072 callbackNumber = getSubscriptionNumber();
1073 } else {
1074 StatusHints statusHints = getTelecomCall().getDetails().getStatusHints();
1075 if (statusHints != null) {
1076 Bundle extras = statusHints.getExtras();
1077 if (extras != null) {
1078 callbackNumber = extras.getString(TelecomManager.EXTRA_CALL_BACK_NUMBER);
1079 }
1080 }
1081 }
1082
1083 String simNumber =
1084 mContext.getSystemService(TelecomManager.class).getLine1Number(getAccountHandle());
1085 if (!showCallbackNumber && PhoneNumberUtils.compare(callbackNumber, simNumber)) {
1086 LogUtil.v(
1087 "DialerCall.getCallbackNumber",
1088 "numbers are the same (and callback number is not being forced to show);"
1089 + " not showing the callback number");
1090 callbackNumber = "";
1091 }
1092 if (callbackNumber == null) {
1093 callbackNumber = "";
1094 }
1095 }
1096 return callbackNumber;
1097 }
1098
1099 private String getSubscriptionNumber() {
1100 // If it's an emergency call, and they're not populating the callback number,
1101 // then try to fall back to the phone sub info (to hopefully get the SIM's
1102 // number directly from the telephony layer).
1103 PhoneAccountHandle accountHandle = getAccountHandle();
1104 if (accountHandle != null) {
1105 PhoneAccount account =
1106 mContext.getSystemService(TelecomManager.class).getPhoneAccount(accountHandle);
1107 if (account != null) {
1108 return getNumberFromHandle(account.getSubscriptionAddress());
1109 }
1110 }
1111 return null;
1112 }
1113
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001114 @Override
1115 public void onVideoTechStateChanged() {
1116 update();
1117 }
1118
1119 @Override
1120 public void onSessionModificationStateChanged() {
1121 for (DialerCallListener listener : mListeners) {
1122 listener.onDialerCallSessionModificationStateChange();
1123 }
1124 }
1125
1126 @Override
1127 public void onCameraDimensionsChanged(int width, int height) {
1128 InCallVideoCallCallbackNotifier.getInstance().cameraDimensionsChanged(this, width, height);
1129 }
1130
1131 @Override
1132 public void onPeerDimensionsChanged(int width, int height) {
1133 InCallVideoCallCallbackNotifier.getInstance().peerDimensionsChanged(this, width, height);
1134 }
1135
1136 @Override
1137 public void onVideoUpgradeRequestReceived() {
1138 LogUtil.enterBlock("DialerCall.onVideoUpgradeRequestReceived");
1139
1140 for (DialerCallListener listener : mListeners) {
1141 listener.onDialerCallUpgradeToVideo();
1142 }
1143
1144 update();
Eric Erfanian8369df02017-05-03 10:27:13 -07001145
1146 Logger.get(mContext)
1147 .logCallImpression(
1148 DialerImpression.Type.VIDEO_CALL_REQUEST_RECEIVED, getUniqueCallId(), getTimeAddedMs());
1149 }
1150
1151 @Override
1152 public void onUpgradedToVideo(boolean switchToSpeaker) {
1153 LogUtil.enterBlock("DialerCall.onUpgradedToVideo");
1154
1155 if (!switchToSpeaker) {
1156 return;
1157 }
1158
1159 CallAudioState audioState = AudioModeProvider.getInstance().getAudioState();
1160
1161 if (0 != (CallAudioState.ROUTE_BLUETOOTH & audioState.getSupportedRouteMask())) {
1162 LogUtil.e(
1163 "DialerCall.onUpgradedToVideo",
1164 "toggling speakerphone not allowed when bluetooth supported.");
1165 return;
1166 }
1167
1168 if (audioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
1169 return;
1170 }
1171
1172 TelecomAdapter.getInstance().setAudioRoute(CallAudioState.ROUTE_SPEAKER);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001173 }
1174
Eric Erfanianccca3152017-02-22 16:32:36 -08001175 /**
1176 * Specifies whether a number is in the call history or not. {@link #CALL_HISTORY_STATUS_UNKNOWN}
1177 * means there is no result.
1178 */
1179 @IntDef({
1180 CALL_HISTORY_STATUS_UNKNOWN,
1181 CALL_HISTORY_STATUS_PRESENT,
1182 CALL_HISTORY_STATUS_NOT_PRESENT
1183 })
1184 @Retention(RetentionPolicy.SOURCE)
1185 public @interface CallHistoryStatus {}
1186
1187 /* Defines different states of this call */
1188 public static class State {
1189
1190 public static final int INVALID = 0;
1191 public static final int NEW = 1; /* The call is new. */
1192 public static final int IDLE = 2; /* The call is idle. Nothing active */
1193 public static final int ACTIVE = 3; /* There is an active call */
1194 public static final int INCOMING = 4; /* A normal incoming phone call */
1195 public static final int CALL_WAITING = 5; /* Incoming call while another is active */
1196 public static final int DIALING = 6; /* An outgoing call during dial phase */
1197 public static final int REDIALING = 7; /* Subsequent dialing attempt after a failure */
1198 public static final int ONHOLD = 8; /* An active phone call placed on hold */
1199 public static final int DISCONNECTING = 9; /* A call is being ended. */
1200 public static final int DISCONNECTED = 10; /* State after a call disconnects */
1201 public static final int CONFERENCED = 11; /* DialerCall part of a conference call */
1202 public static final int SELECT_PHONE_ACCOUNT = 12; /* Waiting for account selection */
1203 public static final int CONNECTING = 13; /* Waiting for Telecom broadcast to finish */
1204 public static final int BLOCKED = 14; /* The number was found on the block list */
1205 public static final int PULLING = 15; /* An external call being pulled to the device */
1206
1207 public static boolean isConnectingOrConnected(int state) {
1208 switch (state) {
1209 case ACTIVE:
1210 case INCOMING:
1211 case CALL_WAITING:
1212 case CONNECTING:
1213 case DIALING:
1214 case PULLING:
1215 case REDIALING:
1216 case ONHOLD:
1217 case CONFERENCED:
1218 return true;
1219 default:
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001220 return false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001221 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001222 }
1223
1224 public static boolean isDialing(int state) {
1225 return state == DIALING || state == PULLING || state == REDIALING;
1226 }
1227
1228 public static String toString(int state) {
1229 switch (state) {
1230 case INVALID:
1231 return "INVALID";
1232 case NEW:
1233 return "NEW";
1234 case IDLE:
1235 return "IDLE";
1236 case ACTIVE:
1237 return "ACTIVE";
1238 case INCOMING:
1239 return "INCOMING";
1240 case CALL_WAITING:
1241 return "CALL_WAITING";
1242 case DIALING:
1243 return "DIALING";
1244 case PULLING:
1245 return "PULLING";
1246 case REDIALING:
1247 return "REDIALING";
1248 case ONHOLD:
1249 return "ONHOLD";
1250 case DISCONNECTING:
1251 return "DISCONNECTING";
1252 case DISCONNECTED:
1253 return "DISCONNECTED";
1254 case CONFERENCED:
1255 return "CONFERENCED";
1256 case SELECT_PHONE_ACCOUNT:
1257 return "SELECT_PHONE_ACCOUNT";
1258 case CONNECTING:
1259 return "CONNECTING";
1260 case BLOCKED:
1261 return "BLOCKED";
1262 default:
1263 return "UNKNOWN";
1264 }
1265 }
1266 }
1267
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001268 /** Camera direction constants */
1269 public static class CameraDirection {
Eric Erfanianccca3152017-02-22 16:32:36 -08001270 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;
Eric Erfanianccca3152017-02-22 16:32:36 -08001273 }
1274
1275 /**
1276 * Tracks any state variables that is useful for logging. There is some amount of overlap with
1277 * existing call member variables, but this duplication helps to ensure that none of these logging
1278 * variables will interface with/and affect call logic.
1279 */
1280 public static class LogState {
1281
1282 public DisconnectCause disconnectCause;
1283 public boolean isIncoming = false;
Eric Erfanian8369df02017-05-03 10:27:13 -07001284 public ContactLookupResult.Type contactLookupResult =
1285 ContactLookupResult.Type.UNKNOWN_LOOKUP_RESULT_TYPE;
Eric Erfanianccca3152017-02-22 16:32:36 -08001286 public CallSpecificAppData callSpecificAppData;
1287 // If this was a conference call, the total number of calls involved in the conference.
1288 public int conferencedCalls = 0;
1289 public long duration = 0;
1290 public boolean isLogged = false;
1291
Eric Erfanian8369df02017-05-03 10:27:13 -07001292 private static String lookupToString(ContactLookupResult.Type lookupType) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001293 switch (lookupType) {
Eric Erfanian8369df02017-05-03 10:27:13 -07001294 case LOCAL_CONTACT:
Eric Erfanianccca3152017-02-22 16:32:36 -08001295 return "Local";
Eric Erfanian8369df02017-05-03 10:27:13 -07001296 case LOCAL_CACHE:
Eric Erfanianccca3152017-02-22 16:32:36 -08001297 return "Cache";
Eric Erfanian8369df02017-05-03 10:27:13 -07001298 case REMOTE:
Eric Erfanianccca3152017-02-22 16:32:36 -08001299 return "Remote";
Eric Erfanian8369df02017-05-03 10:27:13 -07001300 case EMERGENCY:
Eric Erfanianccca3152017-02-22 16:32:36 -08001301 return "Emergency";
Eric Erfanian8369df02017-05-03 10:27:13 -07001302 case VOICEMAIL:
Eric Erfanianccca3152017-02-22 16:32:36 -08001303 return "Voicemail";
1304 default:
1305 return "Not found";
1306 }
1307 }
1308
1309 private static String initiationToString(CallSpecificAppData callSpecificAppData) {
1310 if (callSpecificAppData == null) {
1311 return "null";
1312 }
Eric Erfanian8369df02017-05-03 10:27:13 -07001313 switch (callSpecificAppData.getCallInitiationType()) {
1314 case INCOMING_INITIATION:
Eric Erfanianccca3152017-02-22 16:32:36 -08001315 return "Incoming";
Eric Erfanian8369df02017-05-03 10:27:13 -07001316 case DIALPAD:
Eric Erfanianccca3152017-02-22 16:32:36 -08001317 return "Dialpad";
Eric Erfanian8369df02017-05-03 10:27:13 -07001318 case SPEED_DIAL:
Eric Erfanianccca3152017-02-22 16:32:36 -08001319 return "Speed Dial";
Eric Erfanian8369df02017-05-03 10:27:13 -07001320 case REMOTE_DIRECTORY:
Eric Erfanianccca3152017-02-22 16:32:36 -08001321 return "Remote Directory";
Eric Erfanian8369df02017-05-03 10:27:13 -07001322 case SMART_DIAL:
Eric Erfanianccca3152017-02-22 16:32:36 -08001323 return "Smart Dial";
Eric Erfanian8369df02017-05-03 10:27:13 -07001324 case REGULAR_SEARCH:
Eric Erfanianccca3152017-02-22 16:32:36 -08001325 return "Regular Search";
Eric Erfanian8369df02017-05-03 10:27:13 -07001326 case CALL_LOG:
Eric Erfanianccca3152017-02-22 16:32:36 -08001327 return "DialerCall Log";
Eric Erfanian8369df02017-05-03 10:27:13 -07001328 case CALL_LOG_FILTER:
Eric Erfanianccca3152017-02-22 16:32:36 -08001329 return "DialerCall Log Filter";
Eric Erfanian8369df02017-05-03 10:27:13 -07001330 case VOICEMAIL_LOG:
Eric Erfanianccca3152017-02-22 16:32:36 -08001331 return "Voicemail Log";
Eric Erfanian8369df02017-05-03 10:27:13 -07001332 case CALL_DETAILS:
Eric Erfanianccca3152017-02-22 16:32:36 -08001333 return "DialerCall Details";
Eric Erfanian8369df02017-05-03 10:27:13 -07001334 case QUICK_CONTACTS:
Eric Erfanianccca3152017-02-22 16:32:36 -08001335 return "Quick Contacts";
Eric Erfanian8369df02017-05-03 10:27:13 -07001336 case EXTERNAL_INITIATION:
Eric Erfanianccca3152017-02-22 16:32:36 -08001337 return "External";
Eric Erfanian8369df02017-05-03 10:27:13 -07001338 case LAUNCHER_SHORTCUT:
Eric Erfanianccca3152017-02-22 16:32:36 -08001339 return "Launcher Shortcut";
1340 default:
Eric Erfanian8369df02017-05-03 10:27:13 -07001341 return "Unknown: " + callSpecificAppData.getCallInitiationType();
Eric Erfanianccca3152017-02-22 16:32:36 -08001342 }
1343 }
1344
1345 @Override
1346 public String toString() {
1347 return String.format(
1348 Locale.US,
1349 "["
1350 + "%s, " // DisconnectCause toString already describes the object type
1351 + "isIncoming: %s, "
1352 + "contactLookup: %s, "
1353 + "callInitiation: %s, "
1354 + "duration: %s"
1355 + "]",
1356 disconnectCause,
1357 isIncoming,
1358 lookupToString(contactLookupResult),
1359 initiationToString(callSpecificAppData),
1360 duration);
1361 }
1362 }
1363
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001364 private static class VideoTechManager {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001365 private final Context context;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001366 private final EmptyVideoTech emptyVideoTech = new EmptyVideoTech();
Eric Erfanian90508232017-03-24 09:31:16 -07001367 private final List<VideoTech> videoTechs;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001368 private VideoTech savedTech;
1369
1370 VideoTechManager(DialerCall call) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001371 this.context = call.mContext;
1372
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001373 String phoneNumber = call.getNumber();
Eric Erfaniand8046e52017-04-06 09:41:50 -07001374 phoneNumber = phoneNumber != null ? phoneNumber : "";
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001375
1376 // Insert order here determines the priority of that video tech option
Eric Erfanian8369df02017-05-03 10:27:13 -07001377 videoTechs = new ArrayList<>();
1378 videoTechs.add(new ImsVideoTech(Logger.get(call.mContext), call, call.mTelecomCall));
Eric Erfanian90508232017-03-24 09:31:16 -07001379
1380 VideoTech rcsVideoTech =
1381 EnrichedCallComponent.get(call.mContext)
1382 .getRcsVideoShareFactory()
1383 .newRcsVideoShare(
1384 EnrichedCallComponent.get(call.mContext).getEnrichedCallManager(),
1385 call,
Eric Erfaniand8046e52017-04-06 09:41:50 -07001386 phoneNumber);
Eric Erfanian90508232017-03-24 09:31:16 -07001387 if (rcsVideoTech != null) {
1388 videoTechs.add(rcsVideoTech);
1389 }
Eric Erfaniand8046e52017-04-06 09:41:50 -07001390
1391 videoTechs.add(
1392 new LightbringerTech(
1393 LightbringerComponent.get(call.mContext).getLightbringer(), call, phoneNumber));
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001394 }
1395
1396 VideoTech getVideoTech() {
1397 if (savedTech != null) {
1398 return savedTech;
1399 }
1400
1401 for (VideoTech tech : videoTechs) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001402 if (tech.isAvailable(context)) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001403 // Remember the first VideoTech that becomes available and always use it
1404 savedTech = tech;
1405 return savedTech;
1406 }
1407 }
1408
1409 return emptyVideoTech;
1410 }
1411
1412 void dispatchCallStateChanged(int newState) {
1413 for (VideoTech videoTech : videoTechs) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001414 videoTech.onCallStateChanged(context, newState);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001415 }
1416 }
1417 }
1418
Eric Erfanianccca3152017-02-22 16:32:36 -08001419 /** Called when canned text responses have been loaded. */
1420 public interface CannedTextResponsesLoadedListener {
1421 void onCannedTextResponsesLoaded(DialerCall call);
1422 }
1423}