blob: 1a998096a3831952017cc04cbbc5b6995c773e8d [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;
Eric Erfanianccca3152017-02-22 16:32:36 -080044import com.android.contacts.common.compat.telecom.TelecomManagerCompat;
Eric Erfanian8369df02017-05-03 10:27:13 -070045import com.android.dialer.callintent.CallInitiationType;
Eric Erfanianccca3152017-02-22 16:32:36 -080046import com.android.dialer.callintent.CallIntentParser;
Eric Erfanian8369df02017-05-03 10:27:13 -070047import com.android.dialer.callintent.CallSpecificAppData;
Eric Erfanianccca3152017-02-22 16:32:36 -080048import com.android.dialer.common.Assert;
Eric Erfanianccca3152017-02-22 16:32:36 -080049import com.android.dialer.common.LogUtil;
Eric Erfanian2ca43182017-08-31 06:57:16 -070050import com.android.dialer.compat.telephony.TelephonyManagerCompat;
51import com.android.dialer.configprovider.ConfigProviderBindings;
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 Erfanian2ca43182017-08-31 06:57:16 -070054import com.android.dialer.enrichedcall.EnrichedCallManager;
55import com.android.dialer.enrichedcall.EnrichedCallManager.CapabilitiesListener;
56import com.android.dialer.enrichedcall.EnrichedCallManager.Filter;
57import com.android.dialer.enrichedcall.EnrichedCallManager.StateChangedListener;
Eric Erfanian8369df02017-05-03 10:27:13 -070058import com.android.dialer.enrichedcall.Session;
Eric Erfaniand8046e52017-04-06 09:41:50 -070059import com.android.dialer.lightbringer.LightbringerComponent;
Eric Erfanian8369df02017-05-03 10:27:13 -070060import com.android.dialer.logging.ContactLookupResult;
61import com.android.dialer.logging.DialerImpression;
62import com.android.dialer.logging.Logger;
Eric Erfanianc857f902017-05-15 14:05:33 -070063import com.android.dialer.theme.R;
Eric Erfanian8369df02017-05-03 10:27:13 -070064import com.android.incallui.audiomode.AudioModeProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080065import com.android.incallui.latencyreport.LatencyReport;
66import com.android.incallui.util.TelecomCallUtil;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070067import com.android.incallui.videotech.VideoTech;
68import com.android.incallui.videotech.VideoTech.VideoTechListener;
69import com.android.incallui.videotech.empty.EmptyVideoTech;
70import com.android.incallui.videotech.ims.ImsVideoTech;
Eric Erfaniand8046e52017-04-06 09:41:50 -070071import com.android.incallui.videotech.lightbringer.LightbringerTech;
Eric Erfanian90508232017-03-24 09:31:16 -070072import com.android.incallui.videotech.utils.VideoUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080073import java.lang.annotation.Retention;
74import java.lang.annotation.RetentionPolicy;
75import java.util.ArrayList;
76import java.util.List;
77import java.util.Locale;
78import java.util.Objects;
79import java.util.UUID;
80import java.util.concurrent.CopyOnWriteArrayList;
81import java.util.concurrent.TimeUnit;
82
83/** Describes a single call and its state. */
Eric Erfanian2ca43182017-08-31 06:57:16 -070084public class DialerCall implements VideoTechListener, StateChangedListener, CapabilitiesListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080085
86 public static final int CALL_HISTORY_STATUS_UNKNOWN = 0;
87 public static final int CALL_HISTORY_STATUS_PRESENT = 1;
88 public static final int CALL_HISTORY_STATUS_NOT_PRESENT = 2;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070089
90 // Hard coded property for {@code Call}. Upstreamed change from Motorola.
91 // TODO(b/35359461): Move it to Telecom in framework.
92 public static final int PROPERTY_CODEC_KNOWN = 0x04000000;
93
Eric Erfanianccca3152017-02-22 16:32:36 -080094 private static final String ID_PREFIX = "DialerCall_";
95 private static final String CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS =
96 "emergency_callback_window_millis";
97 private static int sIdCounter = 0;
98
99 /**
Eric Erfanianc857f902017-05-15 14:05:33 -0700100 * A counter used to append to restricted/private/hidden calls so that users can identify them in
101 * a conversation. This value is reset in {@link CallList#onCallRemoved(Context, Call)} when there
102 * are no live calls.
103 */
104 private static int sHiddenCounter;
105
106 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800107 * The unique call ID for every call. This will help us to identify each call and allow us the
108 * ability to stitch impressions to calls if needed.
109 */
110 private final String uniqueCallId = UUID.randomUUID().toString();
111
112 private final Call mTelecomCall;
113 private final LatencyReport mLatencyReport;
114 private final String mId;
Eric Erfanianc857f902017-05-15 14:05:33 -0700115 private final int mHiddenId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800116 private final List<String> mChildCallIds = new ArrayList<>();
Eric Erfanianccca3152017-02-22 16:32:36 -0800117 private final LogState mLogState = new LogState();
118 private final Context mContext;
119 private final DialerCallDelegate mDialerCallDelegate;
120 private final List<DialerCallListener> mListeners = new CopyOnWriteArrayList<>();
121 private final List<CannedTextResponsesLoadedListener> mCannedTextResponsesLoadedListeners =
122 new CopyOnWriteArrayList<>();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700123 private final VideoTechManager mVideoTechManager;
Eric Erfanianccca3152017-02-22 16:32:36 -0800124
125 private boolean mIsEmergencyCall;
126 private Uri mHandle;
127 private int mState = State.INVALID;
128 private DisconnectCause mDisconnectCause;
129
130 private boolean hasShownWiFiToLteHandoverToast;
131 private boolean doNotShowDialogForHandoffToWifiFailure;
132
Eric Erfanianccca3152017-02-22 16:32:36 -0800133 private String mChildNumber;
134 private String mLastForwardedNumber;
135 private String mCallSubject;
136 private PhoneAccountHandle mPhoneAccountHandle;
137 @CallHistoryStatus private int mCallHistoryStatus = CALL_HISTORY_STATUS_UNKNOWN;
138 private boolean mIsSpam;
139 private boolean mIsBlocked;
140 private boolean isInUserSpamList;
141 private boolean isInUserWhiteList;
142 private boolean isInGlobalSpamList;
143 private boolean didShowCameraPermission;
144 private String callProviderLabel;
145 private String callbackNumber;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700146 private int mCameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
Eric Erfanian8369df02017-05-03 10:27:13 -0700147 private EnrichedCallCapabilities mEnrichedCallCapabilities;
148 private Session mEnrichedCallSession;
Eric Erfanianccca3152017-02-22 16:32:36 -0800149
Eric Erfanian2ca43182017-08-31 06:57:16 -0700150 private int answerAndReleaseButtonDisplayedTimes = 0;
151 private boolean releasedByAnsweringSecondCall = false;
152 // Times when a second call is received but AnswerAndRelease button is not shown
153 // since it's not supported.
154 private int secondCallWithoutAnswerAndReleasedButtonTimes = 0;
155 private VideoTech videoTech;
156
Eric Erfanianccca3152017-02-22 16:32:36 -0800157 public static String getNumberFromHandle(Uri handle) {
158 return handle == null ? "" : handle.getSchemeSpecificPart();
159 }
160
161 /**
162 * Whether the call is put on hold by remote party. This is different than the {@link
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700163 * State#ONHOLD} state which indicates that the call is being held locally on the device.
Eric Erfanianccca3152017-02-22 16:32:36 -0800164 */
165 private boolean isRemotelyHeld;
166
Eric Erfanian2ca43182017-08-31 06:57:16 -0700167 /** Indicates whether this call is currently in the process of being merged into a conference. */
168 private boolean isMergeInProcess;
169
Eric Erfanianccca3152017-02-22 16:32:36 -0800170 /**
171 * Indicates whether the phone account associated with this call supports specifying a call
172 * subject.
173 */
174 private boolean mIsCallSubjectSupported;
175
176 private final Call.Callback mTelecomCallCallback =
177 new Call.Callback() {
178 @Override
179 public void onStateChanged(Call call, int newState) {
180 LogUtil.v("TelecomCallCallback.onStateChanged", "call=" + call + " newState=" + newState);
181 update();
182 }
183
184 @Override
185 public void onParentChanged(Call call, Call newParent) {
186 LogUtil.v(
187 "TelecomCallCallback.onParentChanged", "call=" + call + " newParent=" + newParent);
188 update();
189 }
190
191 @Override
192 public void onChildrenChanged(Call call, List<Call> children) {
193 update();
194 }
195
196 @Override
197 public void onDetailsChanged(Call call, Call.Details details) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700198 LogUtil.v(
199 "TelecomCallCallback.onDetailsChanged", " call=" + call + " details=" + details);
Eric Erfanianccca3152017-02-22 16:32:36 -0800200 update();
201 }
202
203 @Override
204 public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {
205 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700206 "TelecomCallCallback.onCannedTextResponsesLoaded",
Eric Erfanianccca3152017-02-22 16:32:36 -0800207 "call=" + call + " cannedTextResponses=" + cannedTextResponses);
208 for (CannedTextResponsesLoadedListener listener : mCannedTextResponsesLoadedListeners) {
209 listener.onCannedTextResponsesLoaded(DialerCall.this);
210 }
211 }
212
213 @Override
214 public void onPostDialWait(Call call, String remainingPostDialSequence) {
215 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700216 "TelecomCallCallback.onPostDialWait",
Eric Erfanianccca3152017-02-22 16:32:36 -0800217 "call=" + call + " remainingPostDialSequence=" + remainingPostDialSequence);
218 update();
219 }
220
221 @Override
222 public void onVideoCallChanged(Call call, VideoCall videoCall) {
223 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700224 "TelecomCallCallback.onVideoCallChanged", "call=" + call + " videoCall=" + videoCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800225 update();
226 }
227
228 @Override
229 public void onCallDestroyed(Call call) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700230 LogUtil.v("TelecomCallCallback.onCallDestroyed", "call=" + call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700231 unregisterCallback();
Eric Erfanianccca3152017-02-22 16:32:36 -0800232 }
233
234 @Override
235 public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
236 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700237 "TelecomCallCallback.onConferenceableCallsChanged",
Eric Erfanianccca3152017-02-22 16:32:36 -0800238 "call %s, conferenceable calls: %d",
239 call,
240 conferenceableCalls.size());
241 update();
242 }
243
244 @Override
245 public void onConnectionEvent(android.telecom.Call call, String event, Bundle extras) {
246 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700247 "TelecomCallCallback.onConnectionEvent",
Eric Erfanianccca3152017-02-22 16:32:36 -0800248 "Call: " + call + ", Event: " + event + ", Extras: " + extras);
249 switch (event) {
250 // The Previous attempt to Merge two calls together has failed in Telecom. We must
251 // now update the UI to possibly re-enable the Merge button based on the number of
252 // currently conferenceable calls available or Connection Capabilities.
253 case android.telecom.Connection.EVENT_CALL_MERGE_FAILED:
254 update();
255 break;
256 case TelephonyManagerCompat.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE:
257 notifyWiFiToLteHandover();
258 break;
259 case TelephonyManagerCompat.EVENT_HANDOVER_TO_WIFI_FAILED:
260 notifyHandoverToWifiFailed();
261 break;
262 case TelephonyManagerCompat.EVENT_CALL_REMOTELY_HELD:
263 isRemotelyHeld = true;
264 update();
265 break;
266 case TelephonyManagerCompat.EVENT_CALL_REMOTELY_UNHELD:
267 isRemotelyHeld = false;
268 update();
269 break;
Eric Erfanianc857f902017-05-15 14:05:33 -0700270 case TelephonyManagerCompat.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC:
271 notifyInternationalCallOnWifi();
272 break;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700273 case TelephonyManagerCompat.EVENT_MERGE_START:
274 LogUtil.i("DialerCall.onConnectionEvent", "merge start");
275 isMergeInProcess = true;
276 break;
277 case TelephonyManagerCompat.EVENT_MERGE_COMPLETE:
278 LogUtil.i("DialerCall.onConnectionEvent", "merge complete");
279 isMergeInProcess = false;
280 break;
Eric Erfanianccca3152017-02-22 16:32:36 -0800281 default:
282 break;
283 }
284 }
285 };
Eric Erfanianc857f902017-05-15 14:05:33 -0700286
Eric Erfanianccca3152017-02-22 16:32:36 -0800287 private long mTimeAddedMs;
288
289 public DialerCall(
290 Context context,
291 DialerCallDelegate dialerCallDelegate,
292 Call telecomCall,
293 LatencyReport latencyReport,
294 boolean registerCallback) {
295 Assert.isNotNull(context);
296 mContext = context;
297 mDialerCallDelegate = dialerCallDelegate;
298 mTelecomCall = telecomCall;
299 mLatencyReport = latencyReport;
300 mId = ID_PREFIX + Integer.toString(sIdCounter++);
301
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700302 // Must be after assigning mTelecomCall
303 mVideoTechManager = new VideoTechManager(this);
304
305 updateFromTelecomCall();
Eric Erfanianc857f902017-05-15 14:05:33 -0700306 if (isHiddenNumber() && TextUtils.isEmpty(getNumber())) {
307 mHiddenId = ++sHiddenCounter;
308 } else {
309 mHiddenId = 0;
310 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800311
312 if (registerCallback) {
313 mTelecomCall.registerCallback(mTelecomCallCallback);
314 }
315
316 mTimeAddedMs = System.currentTimeMillis();
317 parseCallSpecificAppData();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700318
319 updateEnrichedCallSession();
Eric Erfanianccca3152017-02-22 16:32:36 -0800320 }
321
322 private static int translateState(int state) {
323 switch (state) {
324 case Call.STATE_NEW:
325 case Call.STATE_CONNECTING:
326 return DialerCall.State.CONNECTING;
327 case Call.STATE_SELECT_PHONE_ACCOUNT:
328 return DialerCall.State.SELECT_PHONE_ACCOUNT;
329 case Call.STATE_DIALING:
330 return DialerCall.State.DIALING;
331 case Call.STATE_PULLING_CALL:
332 return DialerCall.State.PULLING;
333 case Call.STATE_RINGING:
334 return DialerCall.State.INCOMING;
335 case Call.STATE_ACTIVE:
336 return DialerCall.State.ACTIVE;
337 case Call.STATE_HOLDING:
338 return DialerCall.State.ONHOLD;
339 case Call.STATE_DISCONNECTED:
340 return DialerCall.State.DISCONNECTED;
341 case Call.STATE_DISCONNECTING:
342 return DialerCall.State.DISCONNECTING;
343 default:
344 return DialerCall.State.INVALID;
345 }
346 }
347
348 public static boolean areSame(DialerCall call1, DialerCall call2) {
349 if (call1 == null && call2 == null) {
350 return true;
351 } else if (call1 == null || call2 == null) {
352 return false;
353 }
354
355 // otherwise compare call Ids
356 return call1.getId().equals(call2.getId());
357 }
358
359 public static boolean areSameNumber(DialerCall call1, DialerCall call2) {
360 if (call1 == null && call2 == null) {
361 return true;
362 } else if (call1 == null || call2 == null) {
363 return false;
364 }
365
366 // otherwise compare call Numbers
367 return TextUtils.equals(call1.getNumber(), call2.getNumber());
368 }
369
370 public void addListener(DialerCallListener listener) {
371 Assert.isMainThread();
372 mListeners.add(listener);
373 }
374
375 public void removeListener(DialerCallListener listener) {
376 Assert.isMainThread();
377 mListeners.remove(listener);
378 }
379
380 public void addCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
381 Assert.isMainThread();
382 mCannedTextResponsesLoadedListeners.add(listener);
383 }
384
385 public void removeCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
386 Assert.isMainThread();
387 mCannedTextResponsesLoadedListeners.remove(listener);
388 }
389
390 public void notifyWiFiToLteHandover() {
391 LogUtil.i("DialerCall.notifyWiFiToLteHandover", "");
392 for (DialerCallListener listener : mListeners) {
393 listener.onWiFiToLteHandover();
394 }
395 }
396
397 public void notifyHandoverToWifiFailed() {
398 LogUtil.i("DialerCall.notifyHandoverToWifiFailed", "");
399 for (DialerCallListener listener : mListeners) {
400 listener.onHandoverToWifiFailure();
401 }
402 }
403
Eric Erfanianc857f902017-05-15 14:05:33 -0700404 public void notifyInternationalCallOnWifi() {
405 LogUtil.enterBlock("DialerCall.notifyInternationalCallOnWifi");
406 for (DialerCallListener dialerCallListener : mListeners) {
407 dialerCallListener.onInternationalCallOnWifi();
408 }
409 }
410
Eric Erfanianccca3152017-02-22 16:32:36 -0800411 /* package-private */ Call getTelecomCall() {
412 return mTelecomCall;
413 }
414
415 public StatusHints getStatusHints() {
416 return mTelecomCall.getDetails().getStatusHints();
417 }
418
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700419 public int getCameraDir() {
420 return mCameraDirection;
421 }
422
423 public void setCameraDir(int cameraDir) {
424 if (cameraDir == CameraDirection.CAMERA_DIRECTION_FRONT_FACING
425 || cameraDir == CameraDirection.CAMERA_DIRECTION_BACK_FACING) {
426 mCameraDirection = cameraDir;
427 } else {
428 mCameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
429 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800430 }
431
Eric Erfanian2ca43182017-08-31 06:57:16 -0700432 public boolean wasParentCall() {
433 return mLogState.conferencedCalls != 0;
434 }
435
Eric Erfanianccca3152017-02-22 16:32:36 -0800436 private void update() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700437 Trace.beginSection("DialerCall.update");
Eric Erfanianccca3152017-02-22 16:32:36 -0800438 int oldState = getState();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700439 // Clear any cache here that could potentially change on update.
440 videoTech = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800441 // We want to potentially register a video call callback here.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700442 updateFromTelecomCall();
Eric Erfanianccca3152017-02-22 16:32:36 -0800443 if (oldState != getState() && getState() == DialerCall.State.DISCONNECTED) {
444 for (DialerCallListener listener : mListeners) {
445 listener.onDialerCallDisconnect();
446 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700447 EnrichedCallComponent.get(mContext)
448 .getEnrichedCallManager()
449 .unregisterCapabilitiesListener(this);
450 EnrichedCallComponent.get(mContext)
451 .getEnrichedCallManager()
452 .unregisterStateChangedListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800453 } else {
454 for (DialerCallListener listener : mListeners) {
455 listener.onDialerCallUpdate();
456 }
457 }
458 Trace.endSection();
459 }
460
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700461 private void updateFromTelecomCall() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700462 Trace.beginSection("DialerCall.updateFromTelecomCall");
Eric Erfanianccca3152017-02-22 16:32:36 -0800463 LogUtil.v("DialerCall.updateFromTelecomCall", mTelecomCall.toString());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700464
465 mVideoTechManager.dispatchCallStateChanged(mTelecomCall.getState());
466
Eric Erfanianccca3152017-02-22 16:32:36 -0800467 final int translatedState = translateState(mTelecomCall.getState());
468 if (mState != State.BLOCKED) {
469 setState(translatedState);
470 setDisconnectCause(mTelecomCall.getDetails().getDisconnectCause());
Eric Erfanianccca3152017-02-22 16:32:36 -0800471 }
472
473 mChildCallIds.clear();
474 final int numChildCalls = mTelecomCall.getChildren().size();
475 for (int i = 0; i < numChildCalls; i++) {
476 mChildCallIds.add(
477 mDialerCallDelegate
478 .getDialerCallFromTelecomCall(mTelecomCall.getChildren().get(i))
479 .getId());
480 }
481
482 // The number of conferenced calls can change over the course of the call, so use the
483 // maximum number of conferenced child calls as the metric for conference call usage.
484 mLogState.conferencedCalls = Math.max(numChildCalls, mLogState.conferencedCalls);
485
486 updateFromCallExtras(mTelecomCall.getDetails().getExtras());
487
488 // If the handle of the call has changed, update state for the call determining if it is an
489 // emergency call.
490 Uri newHandle = mTelecomCall.getDetails().getHandle();
491 if (!Objects.equals(mHandle, newHandle)) {
492 mHandle = newHandle;
493 updateEmergencyCallState();
494 }
495
496 // If the phone account handle of the call is set, cache capability bit indicating whether
497 // the phone account supports call subjects.
498 PhoneAccountHandle newPhoneAccountHandle = mTelecomCall.getDetails().getAccountHandle();
499 if (!Objects.equals(mPhoneAccountHandle, newPhoneAccountHandle)) {
500 mPhoneAccountHandle = newPhoneAccountHandle;
501
502 if (mPhoneAccountHandle != null) {
503 PhoneAccount phoneAccount =
504 mContext.getSystemService(TelecomManager.class).getPhoneAccount(mPhoneAccountHandle);
505 if (phoneAccount != null) {
506 mIsCallSubjectSupported =
507 phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT);
508 }
509 }
510 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700511 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800512 }
513
514 /**
515 * Tests corruption of the {@code callExtras} bundle by calling {@link
516 * Bundle#containsKey(String)}. If the bundle is corrupted a {@link IllegalArgumentException} will
517 * be thrown and caught by this function.
518 *
519 * @param callExtras the bundle to verify
520 * @return {@code true} if the bundle is corrupted, {@code false} otherwise.
521 */
522 protected boolean areCallExtrasCorrupted(Bundle callExtras) {
523 /**
524 * There's currently a bug in Telephony service (b/25613098) that could corrupt the extras
525 * bundle, resulting in a IllegalArgumentException while validating data under {@link
526 * Bundle#containsKey(String)}.
527 */
528 try {
529 callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS);
530 return false;
531 } catch (IllegalArgumentException e) {
532 LogUtil.e(
533 "DialerCall.areCallExtrasCorrupted", "callExtras is corrupted, ignoring exception", e);
534 return true;
535 }
536 }
537
538 protected void updateFromCallExtras(Bundle callExtras) {
539 if (callExtras == null || areCallExtrasCorrupted(callExtras)) {
540 /**
541 * If the bundle is corrupted, abandon information update as a work around. These are not
542 * critical for the dialer to function.
543 */
544 return;
545 }
546 // Check for a change in the child address and notify any listeners.
547 if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) {
548 String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS);
549 if (!Objects.equals(childNumber, mChildNumber)) {
550 mChildNumber = childNumber;
551 for (DialerCallListener listener : mListeners) {
552 listener.onDialerCallChildNumberChange();
553 }
554 }
555 }
556
557 // Last forwarded number comes in as an array of strings. We want to choose the
558 // last item in the array. The forwarding numbers arrive independently of when the
559 // call is originally set up, so we need to notify the the UI of the change.
560 if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) {
561 ArrayList<String> lastForwardedNumbers =
562 callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER);
563
564 if (lastForwardedNumbers != null) {
565 String lastForwardedNumber = null;
566 if (!lastForwardedNumbers.isEmpty()) {
567 lastForwardedNumber = lastForwardedNumbers.get(lastForwardedNumbers.size() - 1);
568 }
569
570 if (!Objects.equals(lastForwardedNumber, mLastForwardedNumber)) {
571 mLastForwardedNumber = lastForwardedNumber;
572 for (DialerCallListener listener : mListeners) {
573 listener.onDialerCallLastForwardedNumberChange();
574 }
575 }
576 }
577 }
578
579 // DialerCall subject is present in the extras at the start of call, so we do not need to
580 // notify any other listeners of this.
581 if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) {
582 String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT);
583 if (!Objects.equals(mCallSubject, callSubject)) {
584 mCallSubject = callSubject;
585 }
586 }
587 }
588
Eric Erfanianccca3152017-02-22 16:32:36 -0800589 public String getId() {
590 return mId;
591 }
592
Eric Erfanianc857f902017-05-15 14:05:33 -0700593 /**
594 * @return name appended with a number if the number is restricted/unknown and the user has
595 * received more than one restricted/unknown call.
596 */
597 @Nullable
598 public String updateNameIfRestricted(@Nullable String name) {
599 if (name != null && isHiddenNumber() && mHiddenId != 0 && sHiddenCounter > 1) {
600 return mContext.getString(R.string.unknown_counter, name, mHiddenId);
601 }
602 return name;
603 }
604
605 public static void clearRestrictedCount() {
606 sHiddenCounter = 0;
607 }
608
609 private boolean isHiddenNumber() {
610 return getNumberPresentation() == TelecomManager.PRESENTATION_RESTRICTED
611 || getNumberPresentation() == TelecomManager.PRESENTATION_UNKNOWN;
612 }
613
Eric Erfanianccca3152017-02-22 16:32:36 -0800614 public boolean hasShownWiFiToLteHandoverToast() {
615 return hasShownWiFiToLteHandoverToast;
616 }
617
618 public void setHasShownWiFiToLteHandoverToast() {
619 hasShownWiFiToLteHandoverToast = true;
620 }
621
622 public boolean showWifiHandoverAlertAsToast() {
623 return doNotShowDialogForHandoffToWifiFailure;
624 }
625
626 public void setDoNotShowDialogForHandoffToWifiFailure(boolean bool) {
627 doNotShowDialogForHandoffToWifiFailure = bool;
628 }
629
630 public long getTimeAddedMs() {
631 return mTimeAddedMs;
632 }
633
634 @Nullable
635 public String getNumber() {
636 return TelecomCallUtil.getNumber(mTelecomCall);
637 }
638
639 public void blockCall() {
640 mTelecomCall.reject(false, null);
641 setState(State.BLOCKED);
642 }
643
644 @Nullable
645 public Uri getHandle() {
646 return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandle();
647 }
648
649 public boolean isEmergencyCall() {
650 return mIsEmergencyCall;
651 }
652
653 public boolean isPotentialEmergencyCallback() {
654 // The property PROPERTY_EMERGENCY_CALLBACK_MODE is only set for CDMA calls when the system
655 // is actually in emergency callback mode (ie data is disabled).
656 if (hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE)) {
657 return true;
658 }
659 // We want to treat any incoming call that arrives a short time after an outgoing emergency call
660 // as a potential emergency callback.
661 if (getExtras() != null
662 && getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0)
663 > 0) {
664 long lastEmergencyCallMillis =
665 getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0);
666 if (isInEmergencyCallbackWindow(lastEmergencyCallMillis)) {
667 return true;
668 }
669 }
670 return false;
671 }
672
673 boolean isInEmergencyCallbackWindow(long timestampMillis) {
674 long emergencyCallbackWindowMillis =
675 ConfigProviderBindings.get(mContext)
676 .getLong(CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS, TimeUnit.MINUTES.toMillis(5));
677 return System.currentTimeMillis() - timestampMillis < emergencyCallbackWindowMillis;
678 }
679
680 public int getState() {
681 if (mTelecomCall != null && mTelecomCall.getParent() != null) {
682 return State.CONFERENCED;
683 } else {
684 return mState;
685 }
686 }
687
688 public void setState(int state) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700689 if (state == State.INCOMING) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800690 mLogState.isIncoming = true;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700691 } else if (state == State.DISCONNECTED) {
692 long newDuration =
Eric Erfanianccca3152017-02-22 16:32:36 -0800693 getConnectTimeMillis() == 0 ? 0 : System.currentTimeMillis() - getConnectTimeMillis();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700694 if (mState != state) {
695 mLogState.duration = newDuration;
696 } else {
697 LogUtil.i(
698 "DialerCall.setState",
699 "ignoring state transition from DISCONNECTED to DISCONNECTED."
700 + " Duration would have changed from %s to %s",
701 mLogState.duration,
702 newDuration);
703 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800704 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700705 mState = state;
Eric Erfanianccca3152017-02-22 16:32:36 -0800706 }
707
708 public int getNumberPresentation() {
709 return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getHandlePresentation();
710 }
711
712 public int getCnapNamePresentation() {
713 return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getCallerDisplayNamePresentation();
714 }
715
716 @Nullable
717 public String getCnapName() {
718 return mTelecomCall == null ? null : getTelecomCall().getDetails().getCallerDisplayName();
719 }
720
721 public Bundle getIntentExtras() {
722 return mTelecomCall.getDetails().getIntentExtras();
723 }
724
725 @Nullable
726 public Bundle getExtras() {
727 return mTelecomCall == null ? null : mTelecomCall.getDetails().getExtras();
728 }
729
730 /** @return The child number for the call, or {@code null} if none specified. */
731 public String getChildNumber() {
732 return mChildNumber;
733 }
734
735 /** @return The last forwarded number for the call, or {@code null} if none specified. */
736 public String getLastForwardedNumber() {
737 return mLastForwardedNumber;
738 }
739
740 /** @return The call subject, or {@code null} if none specified. */
741 public String getCallSubject() {
742 return mCallSubject;
743 }
744
745 /**
746 * @return {@code true} if the call's phone account supports call subjects, {@code false}
747 * otherwise.
748 */
749 public boolean isCallSubjectSupported() {
750 return mIsCallSubjectSupported;
751 }
752
753 /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
754 public DisconnectCause getDisconnectCause() {
755 if (mState == State.DISCONNECTED || mState == State.IDLE) {
756 return mDisconnectCause;
757 }
758
759 return new DisconnectCause(DisconnectCause.UNKNOWN);
760 }
761
762 public void setDisconnectCause(DisconnectCause disconnectCause) {
763 mDisconnectCause = disconnectCause;
764 mLogState.disconnectCause = mDisconnectCause;
765 }
766
767 /** Returns the possible text message responses. */
768 public List<String> getCannedSmsResponses() {
769 return mTelecomCall.getCannedTextResponses();
770 }
771
772 /** Checks if the call supports the given set of capabilities supplied as a bit mask. */
773 public boolean can(int capabilities) {
774 int supportedCapabilities = mTelecomCall.getDetails().getCallCapabilities();
775
776 if ((capabilities & Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) {
777 // We allow you to merge if the capabilities allow it or if it is a call with
778 // conferenceable calls.
779 if (mTelecomCall.getConferenceableCalls().isEmpty()
780 && ((Call.Details.CAPABILITY_MERGE_CONFERENCE & supportedCapabilities) == 0)) {
781 // Cannot merge calls if there are no calls to merge with.
782 return false;
783 }
784 capabilities &= ~Call.Details.CAPABILITY_MERGE_CONFERENCE;
785 }
786 return (capabilities == (capabilities & supportedCapabilities));
787 }
788
789 public boolean hasProperty(int property) {
790 return mTelecomCall.getDetails().hasProperty(property);
791 }
792
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700793 @NonNull
Eric Erfanianccca3152017-02-22 16:32:36 -0800794 public String getUniqueCallId() {
795 return uniqueCallId;
796 }
797
798 /** Gets the time when the call first became active. */
799 public long getConnectTimeMillis() {
800 return mTelecomCall.getDetails().getConnectTimeMillis();
801 }
802
803 public boolean isConferenceCall() {
804 return hasProperty(Call.Details.PROPERTY_CONFERENCE);
805 }
806
807 @Nullable
808 public GatewayInfo getGatewayInfo() {
809 return mTelecomCall == null ? null : mTelecomCall.getDetails().getGatewayInfo();
810 }
811
812 @Nullable
813 public PhoneAccountHandle getAccountHandle() {
814 return mTelecomCall == null ? null : mTelecomCall.getDetails().getAccountHandle();
815 }
816
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700817 /** @return The {@link VideoCall} instance associated with the {@link Call}. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800818 public VideoCall getVideoCall() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700819 return mTelecomCall == null ? null : mTelecomCall.getVideoCall();
Eric Erfanianccca3152017-02-22 16:32:36 -0800820 }
821
822 public List<String> getChildCallIds() {
823 return mChildCallIds;
824 }
825
826 public String getParentId() {
827 Call parentCall = mTelecomCall.getParent();
828 if (parentCall != null) {
829 return mDialerCallDelegate.getDialerCallFromTelecomCall(parentCall).getId();
830 }
831 return null;
832 }
833
834 public int getVideoState() {
835 return mTelecomCall.getDetails().getVideoState();
836 }
837
838 public boolean isVideoCall() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700839 return getVideoTech().isTransmittingOrReceiving();
840 }
841
842 public boolean hasReceivedVideoUpgradeRequest() {
843 return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState());
844 }
845
846 public boolean hasSentVideoUpgradeRequest() {
847 return VideoUtils.hasSentVideoUpgradeRequest(getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800848 }
849
850 /**
851 * Determines if the call handle is an emergency number or not and caches the result to avoid
852 * repeated calls to isEmergencyNumber.
853 */
854 private void updateEmergencyCallState() {
855 mIsEmergencyCall = TelecomCallUtil.isEmergencyCall(mTelecomCall);
856 }
857
Eric Erfanianccca3152017-02-22 16:32:36 -0800858 public LogState getLogState() {
859 return mLogState;
860 }
861
862 /**
863 * Determines if the call is an external call.
864 *
865 * <p>An external call is one which does not exist locally for the {@link
866 * android.telecom.ConnectionService} it is associated with.
867 *
868 * <p>External calls are only supported in N and higher.
869 *
870 * @return {@code true} if the call is an external call, {@code false} otherwise.
871 */
872 public boolean isExternalCall() {
873 return VERSION.SDK_INT >= VERSION_CODES.N
874 && hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL);
875 }
876
877 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800878 * Determines if answering this call will cause an ongoing video call to be dropped.
879 *
880 * @return {@code true} if answering this call will drop an ongoing video call, {@code false}
881 * otherwise.
882 */
883 public boolean answeringDisconnectsForegroundVideoCall() {
884 Bundle extras = getExtras();
885 if (extras == null
886 || !extras.containsKey(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL)) {
887 return false;
888 }
889 return extras.getBoolean(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL);
890 }
891
892 private void parseCallSpecificAppData() {
893 if (isExternalCall()) {
894 return;
895 }
896
897 mLogState.callSpecificAppData = CallIntentParser.getCallSpecificAppData(getIntentExtras());
898 if (mLogState.callSpecificAppData == null) {
Eric Erfanian8369df02017-05-03 10:27:13 -0700899
900 mLogState.callSpecificAppData =
901 CallSpecificAppData.newBuilder()
902 .setCallInitiationType(CallInitiationType.Type.EXTERNAL_INITIATION)
903 .build();
Eric Erfanianccca3152017-02-22 16:32:36 -0800904 }
905 if (getState() == State.INCOMING) {
Eric Erfanian8369df02017-05-03 10:27:13 -0700906 mLogState.callSpecificAppData =
907 mLogState
908 .callSpecificAppData
909 .toBuilder()
910 .setCallInitiationType(CallInitiationType.Type.INCOMING_INITIATION)
911 .build();
Eric Erfanianccca3152017-02-22 16:32:36 -0800912 }
913 }
914
915 @Override
916 public String toString() {
917 if (mTelecomCall == null) {
918 // This should happen only in testing since otherwise we would never have a null
919 // Telecom call.
920 return String.valueOf(mId);
921 }
922
923 return String.format(
924 Locale.US,
925 "[%s, %s, %s, %s, children:%s, parent:%s, "
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700926 + "conferenceable:%s, videoState:%s, mSessionModificationState:%d, CameraDir:%s]",
Eric Erfanianccca3152017-02-22 16:32:36 -0800927 mId,
928 State.toString(getState()),
929 Details.capabilitiesToString(mTelecomCall.getDetails().getCallCapabilities()),
930 Details.propertiesToString(mTelecomCall.getDetails().getCallProperties()),
931 mChildCallIds,
932 getParentId(),
933 this.mTelecomCall.getConferenceableCalls(),
934 VideoProfile.videoStateToString(mTelecomCall.getDetails().getVideoState()),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700935 getVideoTech().getSessionModificationState(),
936 getCameraDir());
Eric Erfanianccca3152017-02-22 16:32:36 -0800937 }
938
939 public String toSimpleString() {
940 return super.toString();
941 }
942
943 @CallHistoryStatus
944 public int getCallHistoryStatus() {
945 return mCallHistoryStatus;
946 }
947
948 public void setCallHistoryStatus(@CallHistoryStatus int callHistoryStatus) {
949 mCallHistoryStatus = callHistoryStatus;
950 }
951
952 public boolean didShowCameraPermission() {
953 return didShowCameraPermission;
954 }
955
956 public void setDidShowCameraPermission(boolean didShow) {
957 didShowCameraPermission = didShow;
958 }
959
960 public boolean isInGlobalSpamList() {
961 return isInGlobalSpamList;
962 }
963
964 public void setIsInGlobalSpamList(boolean inSpamList) {
965 isInGlobalSpamList = inSpamList;
966 }
967
968 public boolean isInUserSpamList() {
969 return isInUserSpamList;
970 }
971
972 public void setIsInUserSpamList(boolean inSpamList) {
973 isInUserSpamList = inSpamList;
974 }
975
976 public boolean isInUserWhiteList() {
977 return isInUserWhiteList;
978 }
979
980 public void setIsInUserWhiteList(boolean inWhiteList) {
981 isInUserWhiteList = inWhiteList;
982 }
983
984 public boolean isSpam() {
985 return mIsSpam;
986 }
987
988 public void setSpam(boolean isSpam) {
989 mIsSpam = isSpam;
990 }
991
992 public boolean isBlocked() {
993 return mIsBlocked;
994 }
995
996 public void setBlockedStatus(boolean isBlocked) {
997 mIsBlocked = isBlocked;
998 }
999
1000 public boolean isRemotelyHeld() {
1001 return isRemotelyHeld;
1002 }
1003
Eric Erfanian2ca43182017-08-31 06:57:16 -07001004 public boolean isMergeInProcess() {
1005 return isMergeInProcess;
1006 }
1007
Eric Erfanianccca3152017-02-22 16:32:36 -08001008 public boolean isIncoming() {
1009 return mLogState.isIncoming;
1010 }
1011
Eric Erfanian2ca43182017-08-31 06:57:16 -07001012 public boolean isAssistedDialed() {
1013 if (getIntentExtras() != null) {
1014 return getIntentExtras().getBoolean(TelephonyManagerCompat.IS_ASSISTED_DIALED, false);
1015 }
1016 return false;
1017 }
1018
Eric Erfanianccca3152017-02-22 16:32:36 -08001019 public LatencyReport getLatencyReport() {
1020 return mLatencyReport;
1021 }
1022
Eric Erfanian2ca43182017-08-31 06:57:16 -07001023 public int getAnswerAndReleaseButtonDisplayedTimes() {
1024 return answerAndReleaseButtonDisplayedTimes;
1025 }
1026
1027 public void increaseAnswerAndReleaseButtonDisplayedTimes() {
1028 answerAndReleaseButtonDisplayedTimes++;
1029 }
1030
1031 public boolean getReleasedByAnsweringSecondCall() {
1032 return releasedByAnsweringSecondCall;
1033 }
1034
1035 public void setReleasedByAnsweringSecondCall(boolean releasedByAnsweringSecondCall) {
1036 this.releasedByAnsweringSecondCall = releasedByAnsweringSecondCall;
1037 }
1038
1039 public int getSecondCallWithoutAnswerAndReleasedButtonTimes() {
1040 return secondCallWithoutAnswerAndReleasedButtonTimes;
1041 }
1042
1043 public void increaseSecondCallWithoutAnswerAndReleasedButtonTimes() {
1044 secondCallWithoutAnswerAndReleasedButtonTimes++;
1045 }
1046
Eric Erfanian8369df02017-05-03 10:27:13 -07001047 @Nullable
1048 public EnrichedCallCapabilities getEnrichedCallCapabilities() {
1049 return mEnrichedCallCapabilities;
1050 }
1051
1052 public void setEnrichedCallCapabilities(
1053 @Nullable EnrichedCallCapabilities mEnrichedCallCapabilities) {
1054 this.mEnrichedCallCapabilities = mEnrichedCallCapabilities;
1055 }
1056
1057 @Nullable
1058 public Session getEnrichedCallSession() {
1059 return mEnrichedCallSession;
1060 }
1061
1062 public void setEnrichedCallSession(@Nullable Session mEnrichedCallSession) {
1063 this.mEnrichedCallSession = mEnrichedCallSession;
1064 }
1065
Eric Erfanianccca3152017-02-22 16:32:36 -08001066 public void unregisterCallback() {
1067 mTelecomCall.unregisterCallback(mTelecomCallCallback);
1068 }
1069
Eric Erfanianccca3152017-02-22 16:32:36 -08001070 public void phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault) {
1071 LogUtil.i(
1072 "DialerCall.phoneAccountSelected",
1073 "accountHandle: %s, setDefault: %b",
1074 accountHandle,
1075 setDefault);
1076 mTelecomCall.phoneAccountSelected(accountHandle, setDefault);
1077 }
1078
1079 public void disconnect() {
1080 LogUtil.i("DialerCall.disconnect", "");
1081 setState(DialerCall.State.DISCONNECTING);
1082 for (DialerCallListener listener : mListeners) {
1083 listener.onDialerCallUpdate();
1084 }
1085 mTelecomCall.disconnect();
1086 }
1087
1088 public void hold() {
1089 LogUtil.i("DialerCall.hold", "");
1090 mTelecomCall.hold();
1091 }
1092
1093 public void unhold() {
1094 LogUtil.i("DialerCall.unhold", "");
1095 mTelecomCall.unhold();
1096 }
1097
1098 public void splitFromConference() {
1099 LogUtil.i("DialerCall.splitFromConference", "");
1100 mTelecomCall.splitFromConference();
1101 }
1102
1103 public void answer(int videoState) {
1104 LogUtil.i("DialerCall.answer", "videoState: " + videoState);
1105 mTelecomCall.answer(videoState);
1106 }
1107
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001108 public void answer() {
1109 answer(mTelecomCall.getDetails().getVideoState());
1110 }
1111
Eric Erfanianccca3152017-02-22 16:32:36 -08001112 public void reject(boolean rejectWithMessage, String message) {
1113 LogUtil.i("DialerCall.reject", "");
1114 mTelecomCall.reject(rejectWithMessage, message);
1115 }
1116
1117 /** Return the string label to represent the call provider */
1118 public String getCallProviderLabel() {
1119 if (callProviderLabel == null) {
1120 PhoneAccount account = getPhoneAccount();
1121 if (account != null && !TextUtils.isEmpty(account.getLabel())) {
1122 List<PhoneAccountHandle> accounts =
1123 mContext.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts();
1124 if (accounts != null && accounts.size() > 1) {
1125 callProviderLabel = account.getLabel().toString();
1126 }
1127 }
1128 if (callProviderLabel == null) {
1129 callProviderLabel = "";
1130 }
1131 }
1132 return callProviderLabel;
1133 }
1134
1135 private PhoneAccount getPhoneAccount() {
1136 PhoneAccountHandle accountHandle = getAccountHandle();
1137 if (accountHandle == null) {
1138 return null;
1139 }
1140 return mContext.getSystemService(TelecomManager.class).getPhoneAccount(accountHandle);
1141 }
1142
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001143 public VideoTech getVideoTech() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001144 if (videoTech == null) {
1145 videoTech = mVideoTechManager.getVideoTech();
1146 }
1147 return videoTech;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001148 }
1149
Eric Erfanianccca3152017-02-22 16:32:36 -08001150 public String getCallbackNumber() {
1151 if (callbackNumber == null) {
1152 // Show the emergency callback number if either:
1153 // 1. This is an emergency call.
1154 // 2. The phone is in Emergency Callback Mode, which means we should show the callback
1155 // number.
1156 boolean showCallbackNumber = hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE);
1157
1158 if (isEmergencyCall() || showCallbackNumber) {
1159 callbackNumber = getSubscriptionNumber();
1160 } else {
1161 StatusHints statusHints = getTelecomCall().getDetails().getStatusHints();
1162 if (statusHints != null) {
1163 Bundle extras = statusHints.getExtras();
1164 if (extras != null) {
1165 callbackNumber = extras.getString(TelecomManager.EXTRA_CALL_BACK_NUMBER);
1166 }
1167 }
1168 }
1169
1170 String simNumber =
1171 mContext.getSystemService(TelecomManager.class).getLine1Number(getAccountHandle());
1172 if (!showCallbackNumber && PhoneNumberUtils.compare(callbackNumber, simNumber)) {
1173 LogUtil.v(
1174 "DialerCall.getCallbackNumber",
1175 "numbers are the same (and callback number is not being forced to show);"
1176 + " not showing the callback number");
1177 callbackNumber = "";
1178 }
1179 if (callbackNumber == null) {
1180 callbackNumber = "";
1181 }
1182 }
1183 return callbackNumber;
1184 }
1185
1186 private String getSubscriptionNumber() {
1187 // If it's an emergency call, and they're not populating the callback number,
1188 // then try to fall back to the phone sub info (to hopefully get the SIM's
1189 // number directly from the telephony layer).
1190 PhoneAccountHandle accountHandle = getAccountHandle();
1191 if (accountHandle != null) {
1192 PhoneAccount account =
1193 mContext.getSystemService(TelecomManager.class).getPhoneAccount(accountHandle);
1194 if (account != null) {
1195 return getNumberFromHandle(account.getSubscriptionAddress());
1196 }
1197 }
1198 return null;
1199 }
1200
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001201 @Override
1202 public void onVideoTechStateChanged() {
1203 update();
1204 }
1205
1206 @Override
1207 public void onSessionModificationStateChanged() {
1208 for (DialerCallListener listener : mListeners) {
1209 listener.onDialerCallSessionModificationStateChange();
1210 }
1211 }
1212
1213 @Override
1214 public void onCameraDimensionsChanged(int width, int height) {
1215 InCallVideoCallCallbackNotifier.getInstance().cameraDimensionsChanged(this, width, height);
1216 }
1217
1218 @Override
1219 public void onPeerDimensionsChanged(int width, int height) {
1220 InCallVideoCallCallbackNotifier.getInstance().peerDimensionsChanged(this, width, height);
1221 }
1222
1223 @Override
1224 public void onVideoUpgradeRequestReceived() {
1225 LogUtil.enterBlock("DialerCall.onVideoUpgradeRequestReceived");
1226
1227 for (DialerCallListener listener : mListeners) {
1228 listener.onDialerCallUpgradeToVideo();
1229 }
1230
1231 update();
Eric Erfanian8369df02017-05-03 10:27:13 -07001232
1233 Logger.get(mContext)
1234 .logCallImpression(
1235 DialerImpression.Type.VIDEO_CALL_REQUEST_RECEIVED, getUniqueCallId(), getTimeAddedMs());
1236 }
1237
1238 @Override
1239 public void onUpgradedToVideo(boolean switchToSpeaker) {
1240 LogUtil.enterBlock("DialerCall.onUpgradedToVideo");
1241
1242 if (!switchToSpeaker) {
1243 return;
1244 }
1245
1246 CallAudioState audioState = AudioModeProvider.getInstance().getAudioState();
1247
1248 if (0 != (CallAudioState.ROUTE_BLUETOOTH & audioState.getSupportedRouteMask())) {
1249 LogUtil.e(
1250 "DialerCall.onUpgradedToVideo",
1251 "toggling speakerphone not allowed when bluetooth supported.");
1252 return;
1253 }
1254
1255 if (audioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
1256 return;
1257 }
1258
1259 TelecomAdapter.getInstance().setAudioRoute(CallAudioState.ROUTE_SPEAKER);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001260 }
1261
Eric Erfanian2ca43182017-08-31 06:57:16 -07001262 @Override
1263 public void onCapabilitiesUpdated() {
1264 if (getNumber() == null) {
1265 return;
1266 }
1267 EnrichedCallCapabilities capabilities =
1268 EnrichedCallComponent.get(mContext).getEnrichedCallManager().getCapabilities(getNumber());
1269 if (capabilities != null) {
1270 setEnrichedCallCapabilities(capabilities);
1271 update();
1272 }
1273 }
1274
1275 @Override
1276 public void onEnrichedCallStateChanged() {
1277 updateEnrichedCallSession();
1278 }
1279
1280 @Override
1281 public void onImpressionLoggingNeeded(DialerImpression.Type impressionType) {
1282 Logger.get(mContext).logCallImpression(impressionType, getUniqueCallId(), getTimeAddedMs());
1283 }
1284
1285 private void updateEnrichedCallSession() {
1286 if (getNumber() == null) {
1287 return;
1288 }
1289 if (getEnrichedCallSession() != null) {
1290 // State changes to existing sessions are currently handled by the UI components (which have
1291 // their own listeners). Someday instead we could remove those and just call update() here and
1292 // have the usual onDialerCallUpdate update the UI.
1293 dispatchOnEnrichedCallSessionUpdate();
1294 return;
1295 }
1296
1297 EnrichedCallManager manager = EnrichedCallComponent.get(mContext).getEnrichedCallManager();
1298
1299 Filter filter =
1300 isIncoming()
1301 ? manager.createIncomingCallComposerFilter()
1302 : manager.createOutgoingCallComposerFilter();
1303
1304 Session session = manager.getSession(getUniqueCallId(), getNumber(), filter);
1305 if (session == null) {
1306 return;
1307 }
1308
1309 session.setUniqueDialerCallId(getUniqueCallId());
1310 setEnrichedCallSession(session);
1311
1312 LogUtil.i(
1313 "DialerCall.updateEnrichedCallSession",
1314 "setting session %d's dialer id to %s",
1315 session.getSessionId(),
1316 getUniqueCallId());
1317
1318 dispatchOnEnrichedCallSessionUpdate();
1319 }
1320
1321 private void dispatchOnEnrichedCallSessionUpdate() {
1322 for (DialerCallListener listener : mListeners) {
1323 listener.onEnrichedCallSessionUpdate();
1324 }
1325 }
1326
1327 void onRemovedFromCallList() {
1328 // Ensure we clean up when this call is removed.
1329 mVideoTechManager.dispatchRemovedFromCallList();
1330 }
1331
Eric Erfanianccca3152017-02-22 16:32:36 -08001332 /**
1333 * Specifies whether a number is in the call history or not. {@link #CALL_HISTORY_STATUS_UNKNOWN}
1334 * means there is no result.
1335 */
1336 @IntDef({
1337 CALL_HISTORY_STATUS_UNKNOWN,
1338 CALL_HISTORY_STATUS_PRESENT,
1339 CALL_HISTORY_STATUS_NOT_PRESENT
1340 })
1341 @Retention(RetentionPolicy.SOURCE)
1342 public @interface CallHistoryStatus {}
1343
1344 /* Defines different states of this call */
1345 public static class State {
1346
1347 public static final int INVALID = 0;
1348 public static final int NEW = 1; /* The call is new. */
1349 public static final int IDLE = 2; /* The call is idle. Nothing active */
1350 public static final int ACTIVE = 3; /* There is an active call */
1351 public static final int INCOMING = 4; /* A normal incoming phone call */
1352 public static final int CALL_WAITING = 5; /* Incoming call while another is active */
1353 public static final int DIALING = 6; /* An outgoing call during dial phase */
1354 public static final int REDIALING = 7; /* Subsequent dialing attempt after a failure */
1355 public static final int ONHOLD = 8; /* An active phone call placed on hold */
1356 public static final int DISCONNECTING = 9; /* A call is being ended. */
1357 public static final int DISCONNECTED = 10; /* State after a call disconnects */
1358 public static final int CONFERENCED = 11; /* DialerCall part of a conference call */
1359 public static final int SELECT_PHONE_ACCOUNT = 12; /* Waiting for account selection */
1360 public static final int CONNECTING = 13; /* Waiting for Telecom broadcast to finish */
1361 public static final int BLOCKED = 14; /* The number was found on the block list */
1362 public static final int PULLING = 15; /* An external call being pulled to the device */
Eric Erfanian2ca43182017-08-31 06:57:16 -07001363 public static final int CALL_PENDING = 16; /* A call is pending on a long process to finish */
Eric Erfanianccca3152017-02-22 16:32:36 -08001364
1365 public static boolean isConnectingOrConnected(int state) {
1366 switch (state) {
1367 case ACTIVE:
1368 case INCOMING:
1369 case CALL_WAITING:
1370 case CONNECTING:
1371 case DIALING:
1372 case PULLING:
1373 case REDIALING:
1374 case ONHOLD:
1375 case CONFERENCED:
1376 return true;
1377 default:
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001378 return false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001379 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001380 }
1381
1382 public static boolean isDialing(int state) {
1383 return state == DIALING || state == PULLING || state == REDIALING;
1384 }
1385
1386 public static String toString(int state) {
1387 switch (state) {
1388 case INVALID:
1389 return "INVALID";
1390 case NEW:
1391 return "NEW";
1392 case IDLE:
1393 return "IDLE";
1394 case ACTIVE:
1395 return "ACTIVE";
1396 case INCOMING:
1397 return "INCOMING";
1398 case CALL_WAITING:
1399 return "CALL_WAITING";
1400 case DIALING:
1401 return "DIALING";
1402 case PULLING:
1403 return "PULLING";
1404 case REDIALING:
1405 return "REDIALING";
1406 case ONHOLD:
1407 return "ONHOLD";
1408 case DISCONNECTING:
1409 return "DISCONNECTING";
1410 case DISCONNECTED:
1411 return "DISCONNECTED";
1412 case CONFERENCED:
1413 return "CONFERENCED";
1414 case SELECT_PHONE_ACCOUNT:
1415 return "SELECT_PHONE_ACCOUNT";
1416 case CONNECTING:
1417 return "CONNECTING";
1418 case BLOCKED:
1419 return "BLOCKED";
1420 default:
1421 return "UNKNOWN";
1422 }
1423 }
1424 }
1425
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001426 /** Camera direction constants */
1427 public static class CameraDirection {
Eric Erfanianccca3152017-02-22 16:32:36 -08001428 public static final int CAMERA_DIRECTION_UNKNOWN = -1;
1429 public static final int CAMERA_DIRECTION_FRONT_FACING = CameraCharacteristics.LENS_FACING_FRONT;
1430 public static final int CAMERA_DIRECTION_BACK_FACING = CameraCharacteristics.LENS_FACING_BACK;
Eric Erfanianccca3152017-02-22 16:32:36 -08001431 }
1432
1433 /**
1434 * Tracks any state variables that is useful for logging. There is some amount of overlap with
1435 * existing call member variables, but this duplication helps to ensure that none of these logging
1436 * variables will interface with/and affect call logic.
1437 */
1438 public static class LogState {
1439
1440 public DisconnectCause disconnectCause;
1441 public boolean isIncoming = false;
Eric Erfanian8369df02017-05-03 10:27:13 -07001442 public ContactLookupResult.Type contactLookupResult =
1443 ContactLookupResult.Type.UNKNOWN_LOOKUP_RESULT_TYPE;
Eric Erfanianccca3152017-02-22 16:32:36 -08001444 public CallSpecificAppData callSpecificAppData;
1445 // If this was a conference call, the total number of calls involved in the conference.
1446 public int conferencedCalls = 0;
1447 public long duration = 0;
1448 public boolean isLogged = false;
1449
Eric Erfanian8369df02017-05-03 10:27:13 -07001450 private static String lookupToString(ContactLookupResult.Type lookupType) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001451 switch (lookupType) {
Eric Erfanian8369df02017-05-03 10:27:13 -07001452 case LOCAL_CONTACT:
Eric Erfanianccca3152017-02-22 16:32:36 -08001453 return "Local";
Eric Erfanian8369df02017-05-03 10:27:13 -07001454 case LOCAL_CACHE:
Eric Erfanianccca3152017-02-22 16:32:36 -08001455 return "Cache";
Eric Erfanian8369df02017-05-03 10:27:13 -07001456 case REMOTE:
Eric Erfanianccca3152017-02-22 16:32:36 -08001457 return "Remote";
Eric Erfanian8369df02017-05-03 10:27:13 -07001458 case EMERGENCY:
Eric Erfanianccca3152017-02-22 16:32:36 -08001459 return "Emergency";
Eric Erfanian8369df02017-05-03 10:27:13 -07001460 case VOICEMAIL:
Eric Erfanianccca3152017-02-22 16:32:36 -08001461 return "Voicemail";
1462 default:
1463 return "Not found";
1464 }
1465 }
1466
1467 private static String initiationToString(CallSpecificAppData callSpecificAppData) {
1468 if (callSpecificAppData == null) {
1469 return "null";
1470 }
Eric Erfanian8369df02017-05-03 10:27:13 -07001471 switch (callSpecificAppData.getCallInitiationType()) {
1472 case INCOMING_INITIATION:
Eric Erfanianccca3152017-02-22 16:32:36 -08001473 return "Incoming";
Eric Erfanian8369df02017-05-03 10:27:13 -07001474 case DIALPAD:
Eric Erfanianccca3152017-02-22 16:32:36 -08001475 return "Dialpad";
Eric Erfanian8369df02017-05-03 10:27:13 -07001476 case SPEED_DIAL:
Eric Erfanianccca3152017-02-22 16:32:36 -08001477 return "Speed Dial";
Eric Erfanian8369df02017-05-03 10:27:13 -07001478 case REMOTE_DIRECTORY:
Eric Erfanianccca3152017-02-22 16:32:36 -08001479 return "Remote Directory";
Eric Erfanian8369df02017-05-03 10:27:13 -07001480 case SMART_DIAL:
Eric Erfanianccca3152017-02-22 16:32:36 -08001481 return "Smart Dial";
Eric Erfanian8369df02017-05-03 10:27:13 -07001482 case REGULAR_SEARCH:
Eric Erfanianccca3152017-02-22 16:32:36 -08001483 return "Regular Search";
Eric Erfanian8369df02017-05-03 10:27:13 -07001484 case CALL_LOG:
Eric Erfanianccca3152017-02-22 16:32:36 -08001485 return "DialerCall Log";
Eric Erfanian8369df02017-05-03 10:27:13 -07001486 case CALL_LOG_FILTER:
Eric Erfanianccca3152017-02-22 16:32:36 -08001487 return "DialerCall Log Filter";
Eric Erfanian8369df02017-05-03 10:27:13 -07001488 case VOICEMAIL_LOG:
Eric Erfanianccca3152017-02-22 16:32:36 -08001489 return "Voicemail Log";
Eric Erfanian8369df02017-05-03 10:27:13 -07001490 case CALL_DETAILS:
Eric Erfanianccca3152017-02-22 16:32:36 -08001491 return "DialerCall Details";
Eric Erfanian8369df02017-05-03 10:27:13 -07001492 case QUICK_CONTACTS:
Eric Erfanianccca3152017-02-22 16:32:36 -08001493 return "Quick Contacts";
Eric Erfanian8369df02017-05-03 10:27:13 -07001494 case EXTERNAL_INITIATION:
Eric Erfanianccca3152017-02-22 16:32:36 -08001495 return "External";
Eric Erfanian8369df02017-05-03 10:27:13 -07001496 case LAUNCHER_SHORTCUT:
Eric Erfanianccca3152017-02-22 16:32:36 -08001497 return "Launcher Shortcut";
1498 default:
Eric Erfanian8369df02017-05-03 10:27:13 -07001499 return "Unknown: " + callSpecificAppData.getCallInitiationType();
Eric Erfanianccca3152017-02-22 16:32:36 -08001500 }
1501 }
1502
1503 @Override
1504 public String toString() {
1505 return String.format(
1506 Locale.US,
1507 "["
1508 + "%s, " // DisconnectCause toString already describes the object type
1509 + "isIncoming: %s, "
1510 + "contactLookup: %s, "
1511 + "callInitiation: %s, "
1512 + "duration: %s"
1513 + "]",
1514 disconnectCause,
1515 isIncoming,
1516 lookupToString(contactLookupResult),
1517 initiationToString(callSpecificAppData),
1518 duration);
1519 }
1520 }
1521
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001522 private static class VideoTechManager {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001523 private final Context context;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001524 private final EmptyVideoTech emptyVideoTech = new EmptyVideoTech();
Eric Erfanian90508232017-03-24 09:31:16 -07001525 private final List<VideoTech> videoTechs;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001526 private VideoTech savedTech;
1527
1528 VideoTechManager(DialerCall call) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001529 this.context = call.mContext;
1530
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001531 String phoneNumber = call.getNumber();
Eric Erfaniand8046e52017-04-06 09:41:50 -07001532 phoneNumber = phoneNumber != null ? phoneNumber : "";
Eric Erfanian2ca43182017-08-31 06:57:16 -07001533 phoneNumber = phoneNumber.replaceAll("[^+0-9]", "");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001534
1535 // Insert order here determines the priority of that video tech option
Eric Erfanian8369df02017-05-03 10:27:13 -07001536 videoTechs = new ArrayList<>();
1537 videoTechs.add(new ImsVideoTech(Logger.get(call.mContext), call, call.mTelecomCall));
Eric Erfanian90508232017-03-24 09:31:16 -07001538
1539 VideoTech rcsVideoTech =
1540 EnrichedCallComponent.get(call.mContext)
1541 .getRcsVideoShareFactory()
1542 .newRcsVideoShare(
1543 EnrichedCallComponent.get(call.mContext).getEnrichedCallManager(),
1544 call,
Eric Erfaniand8046e52017-04-06 09:41:50 -07001545 phoneNumber);
Eric Erfanian90508232017-03-24 09:31:16 -07001546 if (rcsVideoTech != null) {
1547 videoTechs.add(rcsVideoTech);
1548 }
Eric Erfaniand8046e52017-04-06 09:41:50 -07001549
1550 videoTechs.add(
1551 new LightbringerTech(
Eric Erfanian2ca43182017-08-31 06:57:16 -07001552 LightbringerComponent.get(call.mContext).getLightbringer(),
1553 call,
1554 call.mTelecomCall,
1555 phoneNumber));
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001556 }
1557
1558 VideoTech getVideoTech() {
1559 if (savedTech != null) {
1560 return savedTech;
1561 }
1562
1563 for (VideoTech tech : videoTechs) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001564 if (tech.isAvailable(context)) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001565 // Remember the first VideoTech that becomes available and always use it
1566 savedTech = tech;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001567 savedTech.becomePrimary();
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001568 return savedTech;
1569 }
1570 }
1571
1572 return emptyVideoTech;
1573 }
1574
1575 void dispatchCallStateChanged(int newState) {
1576 for (VideoTech videoTech : videoTechs) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001577 videoTech.onCallStateChanged(context, newState);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001578 }
1579 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001580
1581 void dispatchRemovedFromCallList() {
1582 for (VideoTech videoTech : videoTechs) {
1583 videoTech.onRemovedFromCallList();
1584 }
1585 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001586 }
1587
Eric Erfanianccca3152017-02-22 16:32:36 -08001588 /** Called when canned text responses have been loaded. */
1589 public interface CannedTextResponsesLoadedListener {
1590 void onCannedTextResponsesLoaded(DialerCall call);
1591 }
1592}