blob: 2511bb8ca0bed555cd3a0e9d7c7cc6f6451991b1 [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;
twyendde01c52017-09-22 10:07:31 -070029import android.support.annotation.VisibleForTesting;
Eric Erfanianccca3152017-02-22 16:32:36 -080030import android.telecom.Call;
31import android.telecom.Call.Details;
Eric Erfanian8369df02017-05-03 10:27:13 -070032import android.telecom.CallAudioState;
Eric Erfanianccca3152017-02-22 16:32:36 -080033import android.telecom.Connection;
34import android.telecom.DisconnectCause;
35import android.telecom.GatewayInfo;
36import android.telecom.InCallService.VideoCall;
37import android.telecom.PhoneAccount;
38import android.telecom.PhoneAccountHandle;
39import android.telecom.StatusHints;
40import android.telecom.TelecomManager;
41import android.telecom.VideoProfile;
Eric Erfanianccca3152017-02-22 16:32:36 -080042import 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;
twyendde01c52017-09-22 10:07:31 -070061import com.android.dialer.logging.ContactLookupResult.Type;
Eric Erfanian8369df02017-05-03 10:27:13 -070062import com.android.dialer.logging.DialerImpression;
63import com.android.dialer.logging.Logger;
Eric Erfanianc857f902017-05-15 14:05:33 -070064import com.android.dialer.theme.R;
Eric Erfanian8369df02017-05-03 10:27:13 -070065import com.android.incallui.audiomode.AudioModeProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080066import com.android.incallui.latencyreport.LatencyReport;
67import com.android.incallui.util.TelecomCallUtil;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070068import com.android.incallui.videotech.VideoTech;
69import com.android.incallui.videotech.VideoTech.VideoTechListener;
70import com.android.incallui.videotech.empty.EmptyVideoTech;
71import com.android.incallui.videotech.ims.ImsVideoTech;
Eric Erfaniand8046e52017-04-06 09:41:50 -070072import com.android.incallui.videotech.lightbringer.LightbringerTech;
Eric Erfanian90508232017-03-24 09:31:16 -070073import com.android.incallui.videotech.utils.VideoUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080074import java.lang.annotation.Retention;
75import java.lang.annotation.RetentionPolicy;
76import java.util.ArrayList;
77import java.util.List;
78import java.util.Locale;
79import java.util.Objects;
80import java.util.UUID;
81import java.util.concurrent.CopyOnWriteArrayList;
82import java.util.concurrent.TimeUnit;
83
84/** Describes a single call and its state. */
Eric Erfanian2ca43182017-08-31 06:57:16 -070085public class DialerCall implements VideoTechListener, StateChangedListener, CapabilitiesListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080086
87 public static final int CALL_HISTORY_STATUS_UNKNOWN = 0;
88 public static final int CALL_HISTORY_STATUS_PRESENT = 1;
89 public static final int CALL_HISTORY_STATUS_NOT_PRESENT = 2;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070090
91 // Hard coded property for {@code Call}. Upstreamed change from Motorola.
92 // TODO(b/35359461): Move it to Telecom in framework.
93 public static final int PROPERTY_CODEC_KNOWN = 0x04000000;
94
Eric Erfanianccca3152017-02-22 16:32:36 -080095 private static final String ID_PREFIX = "DialerCall_";
96 private static final String CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS =
97 "emergency_callback_window_millis";
98 private static int sIdCounter = 0;
99
100 /**
Eric Erfanianc857f902017-05-15 14:05:33 -0700101 * A counter used to append to restricted/private/hidden calls so that users can identify them in
102 * a conversation. This value is reset in {@link CallList#onCallRemoved(Context, Call)} when there
103 * are no live calls.
104 */
105 private static int sHiddenCounter;
106
107 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800108 * The unique call ID for every call. This will help us to identify each call and allow us the
109 * ability to stitch impressions to calls if needed.
110 */
111 private final String uniqueCallId = UUID.randomUUID().toString();
112
113 private final Call mTelecomCall;
114 private final LatencyReport mLatencyReport;
115 private final String mId;
Eric Erfanianc857f902017-05-15 14:05:33 -0700116 private final int mHiddenId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800117 private final List<String> mChildCallIds = new ArrayList<>();
Eric Erfanianccca3152017-02-22 16:32:36 -0800118 private final LogState mLogState = new LogState();
119 private final Context mContext;
120 private final DialerCallDelegate mDialerCallDelegate;
121 private final List<DialerCallListener> mListeners = new CopyOnWriteArrayList<>();
122 private final List<CannedTextResponsesLoadedListener> mCannedTextResponsesLoadedListeners =
123 new CopyOnWriteArrayList<>();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700124 private final VideoTechManager mVideoTechManager;
Eric Erfanianccca3152017-02-22 16:32:36 -0800125
126 private boolean mIsEmergencyCall;
127 private Uri mHandle;
128 private int mState = State.INVALID;
129 private DisconnectCause mDisconnectCause;
130
131 private boolean hasShownWiFiToLteHandoverToast;
132 private boolean doNotShowDialogForHandoffToWifiFailure;
133
Eric Erfanianccca3152017-02-22 16:32:36 -0800134 private String mChildNumber;
135 private String mLastForwardedNumber;
136 private String mCallSubject;
137 private PhoneAccountHandle mPhoneAccountHandle;
138 @CallHistoryStatus private int mCallHistoryStatus = CALL_HISTORY_STATUS_UNKNOWN;
139 private boolean mIsSpam;
140 private boolean mIsBlocked;
141 private boolean isInUserSpamList;
142 private boolean isInUserWhiteList;
143 private boolean isInGlobalSpamList;
144 private boolean didShowCameraPermission;
145 private String callProviderLabel;
146 private String callbackNumber;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700147 private int mCameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
Eric Erfanian8369df02017-05-03 10:27:13 -0700148 private EnrichedCallCapabilities mEnrichedCallCapabilities;
149 private Session mEnrichedCallSession;
Eric Erfanianccca3152017-02-22 16:32:36 -0800150
Eric Erfanian2ca43182017-08-31 06:57:16 -0700151 private int answerAndReleaseButtonDisplayedTimes = 0;
152 private boolean releasedByAnsweringSecondCall = false;
153 // Times when a second call is received but AnswerAndRelease button is not shown
154 // since it's not supported.
155 private int secondCallWithoutAnswerAndReleasedButtonTimes = 0;
roldenburg7bb96232017-10-09 10:32:05 -0700156 private VideoTech videoTech;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700157
wangqi4d705e52017-09-28 12:23:35 -0700158 private com.android.dialer.logging.VideoTech.Type selectedAvailableVideoTechType =
159 com.android.dialer.logging.VideoTech.Type.NONE;
yueg457b3972017-09-18 15:11:47 -0700160
Eric Erfanianccca3152017-02-22 16:32:36 -0800161 public static String getNumberFromHandle(Uri handle) {
162 return handle == null ? "" : handle.getSchemeSpecificPart();
163 }
164
165 /**
166 * Whether the call is put on hold by remote party. This is different than the {@link
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700167 * State#ONHOLD} state which indicates that the call is being held locally on the device.
Eric Erfanianccca3152017-02-22 16:32:36 -0800168 */
169 private boolean isRemotelyHeld;
170
Eric Erfanian2ca43182017-08-31 06:57:16 -0700171 /** Indicates whether this call is currently in the process of being merged into a conference. */
172 private boolean isMergeInProcess;
173
Eric Erfanianccca3152017-02-22 16:32:36 -0800174 /**
175 * Indicates whether the phone account associated with this call supports specifying a call
176 * subject.
177 */
178 private boolean mIsCallSubjectSupported;
179
180 private final Call.Callback mTelecomCallCallback =
181 new Call.Callback() {
182 @Override
183 public void onStateChanged(Call call, int newState) {
184 LogUtil.v("TelecomCallCallback.onStateChanged", "call=" + call + " newState=" + newState);
185 update();
186 }
187
188 @Override
189 public void onParentChanged(Call call, Call newParent) {
190 LogUtil.v(
191 "TelecomCallCallback.onParentChanged", "call=" + call + " newParent=" + newParent);
192 update();
193 }
194
195 @Override
196 public void onChildrenChanged(Call call, List<Call> children) {
197 update();
198 }
199
200 @Override
201 public void onDetailsChanged(Call call, Call.Details details) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700202 LogUtil.v(
203 "TelecomCallCallback.onDetailsChanged", " call=" + call + " details=" + details);
Eric Erfanianccca3152017-02-22 16:32:36 -0800204 update();
205 }
206
207 @Override
208 public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {
209 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700210 "TelecomCallCallback.onCannedTextResponsesLoaded",
Eric Erfanianccca3152017-02-22 16:32:36 -0800211 "call=" + call + " cannedTextResponses=" + cannedTextResponses);
212 for (CannedTextResponsesLoadedListener listener : mCannedTextResponsesLoadedListeners) {
213 listener.onCannedTextResponsesLoaded(DialerCall.this);
214 }
215 }
216
217 @Override
218 public void onPostDialWait(Call call, String remainingPostDialSequence) {
219 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700220 "TelecomCallCallback.onPostDialWait",
Eric Erfanianccca3152017-02-22 16:32:36 -0800221 "call=" + call + " remainingPostDialSequence=" + remainingPostDialSequence);
222 update();
223 }
224
225 @Override
226 public void onVideoCallChanged(Call call, VideoCall videoCall) {
227 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700228 "TelecomCallCallback.onVideoCallChanged", "call=" + call + " videoCall=" + videoCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800229 update();
230 }
231
232 @Override
233 public void onCallDestroyed(Call call) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700234 LogUtil.v("TelecomCallCallback.onCallDestroyed", "call=" + call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700235 unregisterCallback();
Eric Erfanianccca3152017-02-22 16:32:36 -0800236 }
237
238 @Override
239 public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
240 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700241 "TelecomCallCallback.onConferenceableCallsChanged",
Eric Erfanianccca3152017-02-22 16:32:36 -0800242 "call %s, conferenceable calls: %d",
243 call,
244 conferenceableCalls.size());
245 update();
246 }
247
248 @Override
249 public void onConnectionEvent(android.telecom.Call call, String event, Bundle extras) {
250 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700251 "TelecomCallCallback.onConnectionEvent",
Eric Erfanianccca3152017-02-22 16:32:36 -0800252 "Call: " + call + ", Event: " + event + ", Extras: " + extras);
253 switch (event) {
254 // The Previous attempt to Merge two calls together has failed in Telecom. We must
255 // now update the UI to possibly re-enable the Merge button based on the number of
256 // currently conferenceable calls available or Connection Capabilities.
257 case android.telecom.Connection.EVENT_CALL_MERGE_FAILED:
258 update();
259 break;
260 case TelephonyManagerCompat.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE:
261 notifyWiFiToLteHandover();
262 break;
263 case TelephonyManagerCompat.EVENT_HANDOVER_TO_WIFI_FAILED:
264 notifyHandoverToWifiFailed();
265 break;
266 case TelephonyManagerCompat.EVENT_CALL_REMOTELY_HELD:
267 isRemotelyHeld = true;
268 update();
269 break;
270 case TelephonyManagerCompat.EVENT_CALL_REMOTELY_UNHELD:
271 isRemotelyHeld = false;
272 update();
273 break;
Eric Erfanianc857f902017-05-15 14:05:33 -0700274 case TelephonyManagerCompat.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC:
275 notifyInternationalCallOnWifi();
276 break;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700277 case TelephonyManagerCompat.EVENT_MERGE_START:
278 LogUtil.i("DialerCall.onConnectionEvent", "merge start");
279 isMergeInProcess = true;
280 break;
281 case TelephonyManagerCompat.EVENT_MERGE_COMPLETE:
282 LogUtil.i("DialerCall.onConnectionEvent", "merge complete");
283 isMergeInProcess = false;
284 break;
Eric Erfanianccca3152017-02-22 16:32:36 -0800285 default:
286 break;
287 }
288 }
289 };
Eric Erfanianc857f902017-05-15 14:05:33 -0700290
Eric Erfanianccca3152017-02-22 16:32:36 -0800291 private long mTimeAddedMs;
292
293 public DialerCall(
294 Context context,
295 DialerCallDelegate dialerCallDelegate,
296 Call telecomCall,
297 LatencyReport latencyReport,
298 boolean registerCallback) {
299 Assert.isNotNull(context);
300 mContext = context;
301 mDialerCallDelegate = dialerCallDelegate;
302 mTelecomCall = telecomCall;
303 mLatencyReport = latencyReport;
304 mId = ID_PREFIX + Integer.toString(sIdCounter++);
305
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700306 // Must be after assigning mTelecomCall
307 mVideoTechManager = new VideoTechManager(this);
308
309 updateFromTelecomCall();
Eric Erfanianc857f902017-05-15 14:05:33 -0700310 if (isHiddenNumber() && TextUtils.isEmpty(getNumber())) {
311 mHiddenId = ++sHiddenCounter;
312 } else {
313 mHiddenId = 0;
314 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800315
316 if (registerCallback) {
317 mTelecomCall.registerCallback(mTelecomCallCallback);
318 }
319
320 mTimeAddedMs = System.currentTimeMillis();
321 parseCallSpecificAppData();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700322
323 updateEnrichedCallSession();
Eric Erfanianccca3152017-02-22 16:32:36 -0800324 }
325
twyendde01c52017-09-22 10:07:31 -0700326 /** Test only constructor to avoid initializing dependencies. */
327 @VisibleForTesting
328 DialerCall(Context context) {
329 mContext = context;
330 mTelecomCall = null;
331 mLatencyReport = null;
332 mId = null;
333 mHiddenId = 0;
334 mDialerCallDelegate = null;
335 mVideoTechManager = null;
336 }
337
Eric Erfanianccca3152017-02-22 16:32:36 -0800338 private static int translateState(int state) {
339 switch (state) {
340 case Call.STATE_NEW:
341 case Call.STATE_CONNECTING:
342 return DialerCall.State.CONNECTING;
343 case Call.STATE_SELECT_PHONE_ACCOUNT:
344 return DialerCall.State.SELECT_PHONE_ACCOUNT;
345 case Call.STATE_DIALING:
346 return DialerCall.State.DIALING;
347 case Call.STATE_PULLING_CALL:
348 return DialerCall.State.PULLING;
349 case Call.STATE_RINGING:
350 return DialerCall.State.INCOMING;
351 case Call.STATE_ACTIVE:
352 return DialerCall.State.ACTIVE;
353 case Call.STATE_HOLDING:
354 return DialerCall.State.ONHOLD;
355 case Call.STATE_DISCONNECTED:
356 return DialerCall.State.DISCONNECTED;
357 case Call.STATE_DISCONNECTING:
358 return DialerCall.State.DISCONNECTING;
359 default:
360 return DialerCall.State.INVALID;
361 }
362 }
363
364 public static boolean areSame(DialerCall call1, DialerCall call2) {
365 if (call1 == null && call2 == null) {
366 return true;
367 } else if (call1 == null || call2 == null) {
368 return false;
369 }
370
371 // otherwise compare call Ids
372 return call1.getId().equals(call2.getId());
373 }
374
375 public static boolean areSameNumber(DialerCall call1, DialerCall call2) {
376 if (call1 == null && call2 == null) {
377 return true;
378 } else if (call1 == null || call2 == null) {
379 return false;
380 }
381
382 // otherwise compare call Numbers
383 return TextUtils.equals(call1.getNumber(), call2.getNumber());
384 }
385
386 public void addListener(DialerCallListener listener) {
387 Assert.isMainThread();
388 mListeners.add(listener);
389 }
390
391 public void removeListener(DialerCallListener listener) {
392 Assert.isMainThread();
393 mListeners.remove(listener);
394 }
395
396 public void addCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
397 Assert.isMainThread();
398 mCannedTextResponsesLoadedListeners.add(listener);
399 }
400
401 public void removeCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
402 Assert.isMainThread();
403 mCannedTextResponsesLoadedListeners.remove(listener);
404 }
405
406 public void notifyWiFiToLteHandover() {
407 LogUtil.i("DialerCall.notifyWiFiToLteHandover", "");
408 for (DialerCallListener listener : mListeners) {
409 listener.onWiFiToLteHandover();
410 }
411 }
412
413 public void notifyHandoverToWifiFailed() {
414 LogUtil.i("DialerCall.notifyHandoverToWifiFailed", "");
415 for (DialerCallListener listener : mListeners) {
416 listener.onHandoverToWifiFailure();
417 }
418 }
419
Eric Erfanianc857f902017-05-15 14:05:33 -0700420 public void notifyInternationalCallOnWifi() {
421 LogUtil.enterBlock("DialerCall.notifyInternationalCallOnWifi");
422 for (DialerCallListener dialerCallListener : mListeners) {
423 dialerCallListener.onInternationalCallOnWifi();
424 }
425 }
426
Eric Erfanianccca3152017-02-22 16:32:36 -0800427 /* package-private */ Call getTelecomCall() {
428 return mTelecomCall;
429 }
430
431 public StatusHints getStatusHints() {
432 return mTelecomCall.getDetails().getStatusHints();
433 }
434
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700435 public int getCameraDir() {
436 return mCameraDirection;
437 }
438
439 public void setCameraDir(int cameraDir) {
440 if (cameraDir == CameraDirection.CAMERA_DIRECTION_FRONT_FACING
441 || cameraDir == CameraDirection.CAMERA_DIRECTION_BACK_FACING) {
442 mCameraDirection = cameraDir;
443 } else {
444 mCameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
445 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800446 }
447
Eric Erfanian2ca43182017-08-31 06:57:16 -0700448 public boolean wasParentCall() {
449 return mLogState.conferencedCalls != 0;
450 }
451
Eric Erfanianccca3152017-02-22 16:32:36 -0800452 private void update() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700453 Trace.beginSection("DialerCall.update");
Eric Erfanianccca3152017-02-22 16:32:36 -0800454 int oldState = getState();
roldenburg7bb96232017-10-09 10:32:05 -0700455 // Clear any cache here that could potentially change on update.
456 videoTech = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800457 // We want to potentially register a video call callback here.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700458 updateFromTelecomCall();
Eric Erfanianccca3152017-02-22 16:32:36 -0800459 if (oldState != getState() && getState() == DialerCall.State.DISCONNECTED) {
460 for (DialerCallListener listener : mListeners) {
461 listener.onDialerCallDisconnect();
462 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700463 EnrichedCallComponent.get(mContext)
464 .getEnrichedCallManager()
465 .unregisterCapabilitiesListener(this);
466 EnrichedCallComponent.get(mContext)
467 .getEnrichedCallManager()
468 .unregisterStateChangedListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800469 } else {
470 for (DialerCallListener listener : mListeners) {
471 listener.onDialerCallUpdate();
472 }
473 }
474 Trace.endSection();
475 }
476
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700477 private void updateFromTelecomCall() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700478 Trace.beginSection("DialerCall.updateFromTelecomCall");
Eric Erfanianccca3152017-02-22 16:32:36 -0800479 LogUtil.v("DialerCall.updateFromTelecomCall", mTelecomCall.toString());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700480
481 mVideoTechManager.dispatchCallStateChanged(mTelecomCall.getState());
482
Eric Erfanianccca3152017-02-22 16:32:36 -0800483 final int translatedState = translateState(mTelecomCall.getState());
484 if (mState != State.BLOCKED) {
485 setState(translatedState);
486 setDisconnectCause(mTelecomCall.getDetails().getDisconnectCause());
Eric Erfanianccca3152017-02-22 16:32:36 -0800487 }
488
489 mChildCallIds.clear();
490 final int numChildCalls = mTelecomCall.getChildren().size();
491 for (int i = 0; i < numChildCalls; i++) {
492 mChildCallIds.add(
493 mDialerCallDelegate
494 .getDialerCallFromTelecomCall(mTelecomCall.getChildren().get(i))
495 .getId());
496 }
497
498 // The number of conferenced calls can change over the course of the call, so use the
499 // maximum number of conferenced child calls as the metric for conference call usage.
500 mLogState.conferencedCalls = Math.max(numChildCalls, mLogState.conferencedCalls);
501
502 updateFromCallExtras(mTelecomCall.getDetails().getExtras());
503
504 // If the handle of the call has changed, update state for the call determining if it is an
505 // emergency call.
506 Uri newHandle = mTelecomCall.getDetails().getHandle();
507 if (!Objects.equals(mHandle, newHandle)) {
508 mHandle = newHandle;
509 updateEmergencyCallState();
510 }
511
512 // If the phone account handle of the call is set, cache capability bit indicating whether
513 // the phone account supports call subjects.
514 PhoneAccountHandle newPhoneAccountHandle = mTelecomCall.getDetails().getAccountHandle();
515 if (!Objects.equals(mPhoneAccountHandle, newPhoneAccountHandle)) {
516 mPhoneAccountHandle = newPhoneAccountHandle;
517
518 if (mPhoneAccountHandle != null) {
519 PhoneAccount phoneAccount =
520 mContext.getSystemService(TelecomManager.class).getPhoneAccount(mPhoneAccountHandle);
521 if (phoneAccount != null) {
522 mIsCallSubjectSupported =
523 phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT);
524 }
525 }
526 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700527 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800528 }
529
530 /**
531 * Tests corruption of the {@code callExtras} bundle by calling {@link
532 * Bundle#containsKey(String)}. If the bundle is corrupted a {@link IllegalArgumentException} will
533 * be thrown and caught by this function.
534 *
535 * @param callExtras the bundle to verify
536 * @return {@code true} if the bundle is corrupted, {@code false} otherwise.
537 */
538 protected boolean areCallExtrasCorrupted(Bundle callExtras) {
539 /**
540 * There's currently a bug in Telephony service (b/25613098) that could corrupt the extras
541 * bundle, resulting in a IllegalArgumentException while validating data under {@link
542 * Bundle#containsKey(String)}.
543 */
544 try {
545 callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS);
546 return false;
547 } catch (IllegalArgumentException e) {
548 LogUtil.e(
549 "DialerCall.areCallExtrasCorrupted", "callExtras is corrupted, ignoring exception", e);
550 return true;
551 }
552 }
553
554 protected void updateFromCallExtras(Bundle callExtras) {
555 if (callExtras == null || areCallExtrasCorrupted(callExtras)) {
556 /**
557 * If the bundle is corrupted, abandon information update as a work around. These are not
558 * critical for the dialer to function.
559 */
560 return;
561 }
562 // Check for a change in the child address and notify any listeners.
563 if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) {
564 String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS);
565 if (!Objects.equals(childNumber, mChildNumber)) {
566 mChildNumber = childNumber;
567 for (DialerCallListener listener : mListeners) {
568 listener.onDialerCallChildNumberChange();
569 }
570 }
571 }
572
573 // Last forwarded number comes in as an array of strings. We want to choose the
574 // last item in the array. The forwarding numbers arrive independently of when the
575 // call is originally set up, so we need to notify the the UI of the change.
576 if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) {
577 ArrayList<String> lastForwardedNumbers =
578 callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER);
579
580 if (lastForwardedNumbers != null) {
581 String lastForwardedNumber = null;
582 if (!lastForwardedNumbers.isEmpty()) {
583 lastForwardedNumber = lastForwardedNumbers.get(lastForwardedNumbers.size() - 1);
584 }
585
586 if (!Objects.equals(lastForwardedNumber, mLastForwardedNumber)) {
587 mLastForwardedNumber = lastForwardedNumber;
588 for (DialerCallListener listener : mListeners) {
589 listener.onDialerCallLastForwardedNumberChange();
590 }
591 }
592 }
593 }
594
595 // DialerCall subject is present in the extras at the start of call, so we do not need to
596 // notify any other listeners of this.
597 if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) {
598 String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT);
599 if (!Objects.equals(mCallSubject, callSubject)) {
600 mCallSubject = callSubject;
601 }
602 }
603 }
604
Eric Erfanianccca3152017-02-22 16:32:36 -0800605 public String getId() {
606 return mId;
607 }
608
Eric Erfanianc857f902017-05-15 14:05:33 -0700609 /**
610 * @return name appended with a number if the number is restricted/unknown and the user has
611 * received more than one restricted/unknown call.
612 */
613 @Nullable
614 public String updateNameIfRestricted(@Nullable String name) {
615 if (name != null && isHiddenNumber() && mHiddenId != 0 && sHiddenCounter > 1) {
616 return mContext.getString(R.string.unknown_counter, name, mHiddenId);
617 }
618 return name;
619 }
620
621 public static void clearRestrictedCount() {
622 sHiddenCounter = 0;
623 }
624
625 private boolean isHiddenNumber() {
626 return getNumberPresentation() == TelecomManager.PRESENTATION_RESTRICTED
627 || getNumberPresentation() == TelecomManager.PRESENTATION_UNKNOWN;
628 }
629
Eric Erfanianccca3152017-02-22 16:32:36 -0800630 public boolean hasShownWiFiToLteHandoverToast() {
631 return hasShownWiFiToLteHandoverToast;
632 }
633
634 public void setHasShownWiFiToLteHandoverToast() {
635 hasShownWiFiToLteHandoverToast = true;
636 }
637
638 public boolean showWifiHandoverAlertAsToast() {
639 return doNotShowDialogForHandoffToWifiFailure;
640 }
641
642 public void setDoNotShowDialogForHandoffToWifiFailure(boolean bool) {
643 doNotShowDialogForHandoffToWifiFailure = bool;
644 }
645
646 public long getTimeAddedMs() {
647 return mTimeAddedMs;
648 }
649
650 @Nullable
651 public String getNumber() {
652 return TelecomCallUtil.getNumber(mTelecomCall);
653 }
654
655 public void blockCall() {
656 mTelecomCall.reject(false, null);
657 setState(State.BLOCKED);
658 }
659
660 @Nullable
661 public Uri getHandle() {
662 return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandle();
663 }
664
665 public boolean isEmergencyCall() {
666 return mIsEmergencyCall;
667 }
668
669 public boolean isPotentialEmergencyCallback() {
670 // The property PROPERTY_EMERGENCY_CALLBACK_MODE is only set for CDMA calls when the system
671 // is actually in emergency callback mode (ie data is disabled).
672 if (hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE)) {
673 return true;
674 }
675 // We want to treat any incoming call that arrives a short time after an outgoing emergency call
676 // as a potential emergency callback.
677 if (getExtras() != null
678 && getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0)
679 > 0) {
680 long lastEmergencyCallMillis =
681 getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0);
682 if (isInEmergencyCallbackWindow(lastEmergencyCallMillis)) {
683 return true;
684 }
685 }
686 return false;
687 }
688
689 boolean isInEmergencyCallbackWindow(long timestampMillis) {
690 long emergencyCallbackWindowMillis =
691 ConfigProviderBindings.get(mContext)
692 .getLong(CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS, TimeUnit.MINUTES.toMillis(5));
693 return System.currentTimeMillis() - timestampMillis < emergencyCallbackWindowMillis;
694 }
695
696 public int getState() {
697 if (mTelecomCall != null && mTelecomCall.getParent() != null) {
698 return State.CONFERENCED;
699 } else {
700 return mState;
701 }
702 }
703
yueg265089a2017-10-06 14:35:15 -0700704 public int getNonConferenceState() {
705 return mState;
706 }
707
Eric Erfanianccca3152017-02-22 16:32:36 -0800708 public void setState(int state) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700709 if (state == State.INCOMING) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800710 mLogState.isIncoming = true;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700711 } else if (state == State.DISCONNECTED) {
712 long newDuration =
Eric Erfanianccca3152017-02-22 16:32:36 -0800713 getConnectTimeMillis() == 0 ? 0 : System.currentTimeMillis() - getConnectTimeMillis();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700714 if (mState != state) {
715 mLogState.duration = newDuration;
716 } else {
717 LogUtil.i(
718 "DialerCall.setState",
719 "ignoring state transition from DISCONNECTED to DISCONNECTED."
720 + " Duration would have changed from %s to %s",
721 mLogState.duration,
722 newDuration);
723 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800724 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700725 mState = state;
Eric Erfanianccca3152017-02-22 16:32:36 -0800726 }
727
728 public int getNumberPresentation() {
729 return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getHandlePresentation();
730 }
731
732 public int getCnapNamePresentation() {
733 return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getCallerDisplayNamePresentation();
734 }
735
736 @Nullable
737 public String getCnapName() {
738 return mTelecomCall == null ? null : getTelecomCall().getDetails().getCallerDisplayName();
739 }
740
741 public Bundle getIntentExtras() {
742 return mTelecomCall.getDetails().getIntentExtras();
743 }
744
745 @Nullable
746 public Bundle getExtras() {
747 return mTelecomCall == null ? null : mTelecomCall.getDetails().getExtras();
748 }
749
750 /** @return The child number for the call, or {@code null} if none specified. */
751 public String getChildNumber() {
752 return mChildNumber;
753 }
754
755 /** @return The last forwarded number for the call, or {@code null} if none specified. */
756 public String getLastForwardedNumber() {
757 return mLastForwardedNumber;
758 }
759
760 /** @return The call subject, or {@code null} if none specified. */
761 public String getCallSubject() {
762 return mCallSubject;
763 }
764
765 /**
766 * @return {@code true} if the call's phone account supports call subjects, {@code false}
767 * otherwise.
768 */
769 public boolean isCallSubjectSupported() {
770 return mIsCallSubjectSupported;
771 }
772
773 /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
774 public DisconnectCause getDisconnectCause() {
775 if (mState == State.DISCONNECTED || mState == State.IDLE) {
776 return mDisconnectCause;
777 }
778
779 return new DisconnectCause(DisconnectCause.UNKNOWN);
780 }
781
782 public void setDisconnectCause(DisconnectCause disconnectCause) {
783 mDisconnectCause = disconnectCause;
784 mLogState.disconnectCause = mDisconnectCause;
785 }
786
787 /** Returns the possible text message responses. */
788 public List<String> getCannedSmsResponses() {
789 return mTelecomCall.getCannedTextResponses();
790 }
791
792 /** Checks if the call supports the given set of capabilities supplied as a bit mask. */
793 public boolean can(int capabilities) {
794 int supportedCapabilities = mTelecomCall.getDetails().getCallCapabilities();
795
796 if ((capabilities & Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) {
797 // We allow you to merge if the capabilities allow it or if it is a call with
798 // conferenceable calls.
799 if (mTelecomCall.getConferenceableCalls().isEmpty()
800 && ((Call.Details.CAPABILITY_MERGE_CONFERENCE & supportedCapabilities) == 0)) {
801 // Cannot merge calls if there are no calls to merge with.
802 return false;
803 }
804 capabilities &= ~Call.Details.CAPABILITY_MERGE_CONFERENCE;
805 }
806 return (capabilities == (capabilities & supportedCapabilities));
807 }
808
809 public boolean hasProperty(int property) {
810 return mTelecomCall.getDetails().hasProperty(property);
811 }
812
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700813 @NonNull
Eric Erfanianccca3152017-02-22 16:32:36 -0800814 public String getUniqueCallId() {
815 return uniqueCallId;
816 }
817
818 /** Gets the time when the call first became active. */
819 public long getConnectTimeMillis() {
820 return mTelecomCall.getDetails().getConnectTimeMillis();
821 }
822
823 public boolean isConferenceCall() {
824 return hasProperty(Call.Details.PROPERTY_CONFERENCE);
825 }
826
827 @Nullable
828 public GatewayInfo getGatewayInfo() {
829 return mTelecomCall == null ? null : mTelecomCall.getDetails().getGatewayInfo();
830 }
831
832 @Nullable
833 public PhoneAccountHandle getAccountHandle() {
834 return mTelecomCall == null ? null : mTelecomCall.getDetails().getAccountHandle();
835 }
836
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700837 /** @return The {@link VideoCall} instance associated with the {@link Call}. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800838 public VideoCall getVideoCall() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700839 return mTelecomCall == null ? null : mTelecomCall.getVideoCall();
Eric Erfanianccca3152017-02-22 16:32:36 -0800840 }
841
842 public List<String> getChildCallIds() {
843 return mChildCallIds;
844 }
845
846 public String getParentId() {
847 Call parentCall = mTelecomCall.getParent();
848 if (parentCall != null) {
849 return mDialerCallDelegate.getDialerCallFromTelecomCall(parentCall).getId();
850 }
851 return null;
852 }
853
854 public int getVideoState() {
855 return mTelecomCall.getDetails().getVideoState();
856 }
857
858 public boolean isVideoCall() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700859 return getVideoTech().isTransmittingOrReceiving();
860 }
861
862 public boolean hasReceivedVideoUpgradeRequest() {
863 return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState());
864 }
865
866 public boolean hasSentVideoUpgradeRequest() {
867 return VideoUtils.hasSentVideoUpgradeRequest(getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800868 }
869
870 /**
871 * Determines if the call handle is an emergency number or not and caches the result to avoid
872 * repeated calls to isEmergencyNumber.
873 */
874 private void updateEmergencyCallState() {
875 mIsEmergencyCall = TelecomCallUtil.isEmergencyCall(mTelecomCall);
876 }
877
Eric Erfanianccca3152017-02-22 16:32:36 -0800878 public LogState getLogState() {
879 return mLogState;
880 }
881
882 /**
883 * Determines if the call is an external call.
884 *
885 * <p>An external call is one which does not exist locally for the {@link
886 * android.telecom.ConnectionService} it is associated with.
887 *
888 * <p>External calls are only supported in N and higher.
889 *
890 * @return {@code true} if the call is an external call, {@code false} otherwise.
891 */
892 public boolean isExternalCall() {
893 return VERSION.SDK_INT >= VERSION_CODES.N
894 && hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL);
895 }
896
897 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800898 * Determines if answering this call will cause an ongoing video call to be dropped.
899 *
900 * @return {@code true} if answering this call will drop an ongoing video call, {@code false}
901 * otherwise.
902 */
903 public boolean answeringDisconnectsForegroundVideoCall() {
904 Bundle extras = getExtras();
905 if (extras == null
906 || !extras.containsKey(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL)) {
907 return false;
908 }
909 return extras.getBoolean(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL);
910 }
911
912 private void parseCallSpecificAppData() {
913 if (isExternalCall()) {
914 return;
915 }
916
917 mLogState.callSpecificAppData = CallIntentParser.getCallSpecificAppData(getIntentExtras());
918 if (mLogState.callSpecificAppData == null) {
Eric Erfanian8369df02017-05-03 10:27:13 -0700919
920 mLogState.callSpecificAppData =
921 CallSpecificAppData.newBuilder()
922 .setCallInitiationType(CallInitiationType.Type.EXTERNAL_INITIATION)
923 .build();
Eric Erfanianccca3152017-02-22 16:32:36 -0800924 }
925 if (getState() == State.INCOMING) {
Eric Erfanian8369df02017-05-03 10:27:13 -0700926 mLogState.callSpecificAppData =
927 mLogState
928 .callSpecificAppData
929 .toBuilder()
930 .setCallInitiationType(CallInitiationType.Type.INCOMING_INITIATION)
931 .build();
Eric Erfanianccca3152017-02-22 16:32:36 -0800932 }
933 }
934
935 @Override
936 public String toString() {
937 if (mTelecomCall == null) {
938 // This should happen only in testing since otherwise we would never have a null
939 // Telecom call.
940 return String.valueOf(mId);
941 }
942
943 return String.format(
944 Locale.US,
945 "[%s, %s, %s, %s, children:%s, parent:%s, "
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700946 + "conferenceable:%s, videoState:%s, mSessionModificationState:%d, CameraDir:%s]",
Eric Erfanianccca3152017-02-22 16:32:36 -0800947 mId,
948 State.toString(getState()),
949 Details.capabilitiesToString(mTelecomCall.getDetails().getCallCapabilities()),
950 Details.propertiesToString(mTelecomCall.getDetails().getCallProperties()),
951 mChildCallIds,
952 getParentId(),
953 this.mTelecomCall.getConferenceableCalls(),
954 VideoProfile.videoStateToString(mTelecomCall.getDetails().getVideoState()),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700955 getVideoTech().getSessionModificationState(),
956 getCameraDir());
Eric Erfanianccca3152017-02-22 16:32:36 -0800957 }
958
959 public String toSimpleString() {
960 return super.toString();
961 }
962
963 @CallHistoryStatus
964 public int getCallHistoryStatus() {
965 return mCallHistoryStatus;
966 }
967
968 public void setCallHistoryStatus(@CallHistoryStatus int callHistoryStatus) {
969 mCallHistoryStatus = callHistoryStatus;
970 }
971
972 public boolean didShowCameraPermission() {
973 return didShowCameraPermission;
974 }
975
976 public void setDidShowCameraPermission(boolean didShow) {
977 didShowCameraPermission = didShow;
978 }
979
980 public boolean isInGlobalSpamList() {
981 return isInGlobalSpamList;
982 }
983
984 public void setIsInGlobalSpamList(boolean inSpamList) {
985 isInGlobalSpamList = inSpamList;
986 }
987
988 public boolean isInUserSpamList() {
989 return isInUserSpamList;
990 }
991
992 public void setIsInUserSpamList(boolean inSpamList) {
993 isInUserSpamList = inSpamList;
994 }
995
996 public boolean isInUserWhiteList() {
997 return isInUserWhiteList;
998 }
999
1000 public void setIsInUserWhiteList(boolean inWhiteList) {
1001 isInUserWhiteList = inWhiteList;
1002 }
1003
1004 public boolean isSpam() {
1005 return mIsSpam;
1006 }
1007
1008 public void setSpam(boolean isSpam) {
1009 mIsSpam = isSpam;
1010 }
1011
1012 public boolean isBlocked() {
1013 return mIsBlocked;
1014 }
1015
1016 public void setBlockedStatus(boolean isBlocked) {
1017 mIsBlocked = isBlocked;
1018 }
1019
1020 public boolean isRemotelyHeld() {
1021 return isRemotelyHeld;
1022 }
1023
Eric Erfanian2ca43182017-08-31 06:57:16 -07001024 public boolean isMergeInProcess() {
1025 return isMergeInProcess;
1026 }
1027
Eric Erfanianccca3152017-02-22 16:32:36 -08001028 public boolean isIncoming() {
1029 return mLogState.isIncoming;
1030 }
1031
Eric Erfanian2ca43182017-08-31 06:57:16 -07001032 public boolean isAssistedDialed() {
1033 if (getIntentExtras() != null) {
1034 return getIntentExtras().getBoolean(TelephonyManagerCompat.IS_ASSISTED_DIALED, false);
1035 }
1036 return false;
1037 }
1038
Eric Erfanianccca3152017-02-22 16:32:36 -08001039 public LatencyReport getLatencyReport() {
1040 return mLatencyReport;
1041 }
1042
Eric Erfanian2ca43182017-08-31 06:57:16 -07001043 public int getAnswerAndReleaseButtonDisplayedTimes() {
1044 return answerAndReleaseButtonDisplayedTimes;
1045 }
1046
1047 public void increaseAnswerAndReleaseButtonDisplayedTimes() {
1048 answerAndReleaseButtonDisplayedTimes++;
1049 }
1050
1051 public boolean getReleasedByAnsweringSecondCall() {
1052 return releasedByAnsweringSecondCall;
1053 }
1054
1055 public void setReleasedByAnsweringSecondCall(boolean releasedByAnsweringSecondCall) {
1056 this.releasedByAnsweringSecondCall = releasedByAnsweringSecondCall;
1057 }
1058
1059 public int getSecondCallWithoutAnswerAndReleasedButtonTimes() {
1060 return secondCallWithoutAnswerAndReleasedButtonTimes;
1061 }
1062
1063 public void increaseSecondCallWithoutAnswerAndReleasedButtonTimes() {
1064 secondCallWithoutAnswerAndReleasedButtonTimes++;
1065 }
1066
Eric Erfanian8369df02017-05-03 10:27:13 -07001067 @Nullable
1068 public EnrichedCallCapabilities getEnrichedCallCapabilities() {
1069 return mEnrichedCallCapabilities;
1070 }
1071
1072 public void setEnrichedCallCapabilities(
1073 @Nullable EnrichedCallCapabilities mEnrichedCallCapabilities) {
1074 this.mEnrichedCallCapabilities = mEnrichedCallCapabilities;
1075 }
1076
1077 @Nullable
1078 public Session getEnrichedCallSession() {
1079 return mEnrichedCallSession;
1080 }
1081
1082 public void setEnrichedCallSession(@Nullable Session mEnrichedCallSession) {
1083 this.mEnrichedCallSession = mEnrichedCallSession;
1084 }
1085
Eric Erfanianccca3152017-02-22 16:32:36 -08001086 public void unregisterCallback() {
1087 mTelecomCall.unregisterCallback(mTelecomCallCallback);
1088 }
1089
Eric Erfanianccca3152017-02-22 16:32:36 -08001090 public void phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault) {
1091 LogUtil.i(
1092 "DialerCall.phoneAccountSelected",
1093 "accountHandle: %s, setDefault: %b",
1094 accountHandle,
1095 setDefault);
1096 mTelecomCall.phoneAccountSelected(accountHandle, setDefault);
1097 }
1098
1099 public void disconnect() {
1100 LogUtil.i("DialerCall.disconnect", "");
1101 setState(DialerCall.State.DISCONNECTING);
1102 for (DialerCallListener listener : mListeners) {
1103 listener.onDialerCallUpdate();
1104 }
1105 mTelecomCall.disconnect();
1106 }
1107
1108 public void hold() {
1109 LogUtil.i("DialerCall.hold", "");
1110 mTelecomCall.hold();
1111 }
1112
1113 public void unhold() {
1114 LogUtil.i("DialerCall.unhold", "");
1115 mTelecomCall.unhold();
1116 }
1117
1118 public void splitFromConference() {
1119 LogUtil.i("DialerCall.splitFromConference", "");
1120 mTelecomCall.splitFromConference();
1121 }
1122
1123 public void answer(int videoState) {
1124 LogUtil.i("DialerCall.answer", "videoState: " + videoState);
1125 mTelecomCall.answer(videoState);
1126 }
1127
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001128 public void answer() {
1129 answer(mTelecomCall.getDetails().getVideoState());
1130 }
1131
Eric Erfanianccca3152017-02-22 16:32:36 -08001132 public void reject(boolean rejectWithMessage, String message) {
1133 LogUtil.i("DialerCall.reject", "");
1134 mTelecomCall.reject(rejectWithMessage, message);
1135 }
1136
1137 /** Return the string label to represent the call provider */
1138 public String getCallProviderLabel() {
1139 if (callProviderLabel == null) {
1140 PhoneAccount account = getPhoneAccount();
1141 if (account != null && !TextUtils.isEmpty(account.getLabel())) {
1142 List<PhoneAccountHandle> accounts =
1143 mContext.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts();
1144 if (accounts != null && accounts.size() > 1) {
1145 callProviderLabel = account.getLabel().toString();
1146 }
1147 }
1148 if (callProviderLabel == null) {
1149 callProviderLabel = "";
1150 }
1151 }
1152 return callProviderLabel;
1153 }
1154
1155 private PhoneAccount getPhoneAccount() {
1156 PhoneAccountHandle accountHandle = getAccountHandle();
1157 if (accountHandle == null) {
1158 return null;
1159 }
1160 return mContext.getSystemService(TelecomManager.class).getPhoneAccount(accountHandle);
1161 }
1162
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001163 public VideoTech getVideoTech() {
roldenburg7bb96232017-10-09 10:32:05 -07001164 if (videoTech == null) {
1165 videoTech = mVideoTechManager.getVideoTech();
1166
1167 // Only store the first video tech type found to be available during the life of the call.
1168 if (selectedAvailableVideoTechType == com.android.dialer.logging.VideoTech.Type.NONE) {
1169 // Update the video tech.
1170 selectedAvailableVideoTechType = videoTech.getVideoTechType();
1171 }
1172 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001173 return videoTech;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001174 }
1175
Eric Erfanianccca3152017-02-22 16:32:36 -08001176 public String getCallbackNumber() {
1177 if (callbackNumber == null) {
1178 // Show the emergency callback number if either:
1179 // 1. This is an emergency call.
1180 // 2. The phone is in Emergency Callback Mode, which means we should show the callback
1181 // number.
1182 boolean showCallbackNumber = hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE);
1183
1184 if (isEmergencyCall() || showCallbackNumber) {
wangqi339b4f32017-09-18 14:32:09 -07001185 callbackNumber =
1186 mContext.getSystemService(TelecomManager.class).getLine1Number(getAccountHandle());
Eric Erfanianccca3152017-02-22 16:32:36 -08001187 }
1188
Eric Erfanianccca3152017-02-22 16:32:36 -08001189 if (callbackNumber == null) {
1190 callbackNumber = "";
1191 }
1192 }
1193 return callbackNumber;
1194 }
1195
wangqi97539352017-09-25 11:15:16 -07001196 public String getSimCountryIso() {
1197 String simCountryIso =
1198 TelephonyManagerCompat.getTelephonyManagerForPhoneAccountHandle(
1199 mContext, getAccountHandle())
1200 .getSimCountryIso();
1201 if (!TextUtils.isEmpty(simCountryIso)) {
1202 simCountryIso = simCountryIso.toUpperCase(Locale.US);
1203 }
1204 return simCountryIso;
1205 }
1206
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001207 @Override
1208 public void onVideoTechStateChanged() {
1209 update();
1210 }
1211
1212 @Override
1213 public void onSessionModificationStateChanged() {
1214 for (DialerCallListener listener : mListeners) {
1215 listener.onDialerCallSessionModificationStateChange();
1216 }
1217 }
1218
1219 @Override
1220 public void onCameraDimensionsChanged(int width, int height) {
1221 InCallVideoCallCallbackNotifier.getInstance().cameraDimensionsChanged(this, width, height);
1222 }
1223
1224 @Override
1225 public void onPeerDimensionsChanged(int width, int height) {
1226 InCallVideoCallCallbackNotifier.getInstance().peerDimensionsChanged(this, width, height);
1227 }
1228
1229 @Override
1230 public void onVideoUpgradeRequestReceived() {
1231 LogUtil.enterBlock("DialerCall.onVideoUpgradeRequestReceived");
1232
1233 for (DialerCallListener listener : mListeners) {
1234 listener.onDialerCallUpgradeToVideo();
1235 }
1236
1237 update();
Eric Erfanian8369df02017-05-03 10:27:13 -07001238
1239 Logger.get(mContext)
1240 .logCallImpression(
1241 DialerImpression.Type.VIDEO_CALL_REQUEST_RECEIVED, getUniqueCallId(), getTimeAddedMs());
1242 }
1243
1244 @Override
1245 public void onUpgradedToVideo(boolean switchToSpeaker) {
1246 LogUtil.enterBlock("DialerCall.onUpgradedToVideo");
1247
1248 if (!switchToSpeaker) {
1249 return;
1250 }
1251
1252 CallAudioState audioState = AudioModeProvider.getInstance().getAudioState();
1253
1254 if (0 != (CallAudioState.ROUTE_BLUETOOTH & audioState.getSupportedRouteMask())) {
1255 LogUtil.e(
1256 "DialerCall.onUpgradedToVideo",
1257 "toggling speakerphone not allowed when bluetooth supported.");
1258 return;
1259 }
1260
1261 if (audioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
1262 return;
1263 }
1264
1265 TelecomAdapter.getInstance().setAudioRoute(CallAudioState.ROUTE_SPEAKER);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001266 }
1267
Eric Erfanian2ca43182017-08-31 06:57:16 -07001268 @Override
1269 public void onCapabilitiesUpdated() {
1270 if (getNumber() == null) {
1271 return;
1272 }
1273 EnrichedCallCapabilities capabilities =
1274 EnrichedCallComponent.get(mContext).getEnrichedCallManager().getCapabilities(getNumber());
1275 if (capabilities != null) {
1276 setEnrichedCallCapabilities(capabilities);
1277 update();
1278 }
1279 }
1280
1281 @Override
1282 public void onEnrichedCallStateChanged() {
1283 updateEnrichedCallSession();
1284 }
1285
1286 @Override
1287 public void onImpressionLoggingNeeded(DialerImpression.Type impressionType) {
1288 Logger.get(mContext).logCallImpression(impressionType, getUniqueCallId(), getTimeAddedMs());
twyendde01c52017-09-22 10:07:31 -07001289 if (impressionType == DialerImpression.Type.LIGHTBRINGER_UPGRADE_REQUESTED) {
1290 if (getLogState().contactLookupResult == Type.NOT_FOUND) {
1291 Logger.get(mContext)
1292 .logCallImpression(
1293 DialerImpression.Type.LIGHTBRINGER_NON_CONTACT_UPGRADE_REQUESTED,
1294 getUniqueCallId(),
1295 getTimeAddedMs());
1296 }
1297 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001298 }
1299
1300 private void updateEnrichedCallSession() {
1301 if (getNumber() == null) {
1302 return;
1303 }
1304 if (getEnrichedCallSession() != null) {
1305 // State changes to existing sessions are currently handled by the UI components (which have
1306 // their own listeners). Someday instead we could remove those and just call update() here and
1307 // have the usual onDialerCallUpdate update the UI.
1308 dispatchOnEnrichedCallSessionUpdate();
1309 return;
1310 }
1311
1312 EnrichedCallManager manager = EnrichedCallComponent.get(mContext).getEnrichedCallManager();
1313
1314 Filter filter =
1315 isIncoming()
1316 ? manager.createIncomingCallComposerFilter()
1317 : manager.createOutgoingCallComposerFilter();
1318
1319 Session session = manager.getSession(getUniqueCallId(), getNumber(), filter);
1320 if (session == null) {
1321 return;
1322 }
1323
1324 session.setUniqueDialerCallId(getUniqueCallId());
1325 setEnrichedCallSession(session);
1326
1327 LogUtil.i(
1328 "DialerCall.updateEnrichedCallSession",
1329 "setting session %d's dialer id to %s",
1330 session.getSessionId(),
1331 getUniqueCallId());
1332
1333 dispatchOnEnrichedCallSessionUpdate();
1334 }
1335
1336 private void dispatchOnEnrichedCallSessionUpdate() {
1337 for (DialerCallListener listener : mListeners) {
1338 listener.onEnrichedCallSessionUpdate();
1339 }
1340 }
1341
1342 void onRemovedFromCallList() {
1343 // Ensure we clean up when this call is removed.
1344 mVideoTechManager.dispatchRemovedFromCallList();
1345 }
1346
wangqi4d705e52017-09-28 12:23:35 -07001347 public com.android.dialer.logging.VideoTech.Type getSelectedAvailableVideoTechType() {
1348 return selectedAvailableVideoTechType;
yueg457b3972017-09-18 15:11:47 -07001349 }
1350
Eric Erfanianccca3152017-02-22 16:32:36 -08001351 /**
1352 * Specifies whether a number is in the call history or not. {@link #CALL_HISTORY_STATUS_UNKNOWN}
1353 * means there is no result.
1354 */
1355 @IntDef({
1356 CALL_HISTORY_STATUS_UNKNOWN,
1357 CALL_HISTORY_STATUS_PRESENT,
1358 CALL_HISTORY_STATUS_NOT_PRESENT
1359 })
1360 @Retention(RetentionPolicy.SOURCE)
1361 public @interface CallHistoryStatus {}
1362
1363 /* Defines different states of this call */
1364 public static class State {
1365
1366 public static final int INVALID = 0;
1367 public static final int NEW = 1; /* The call is new. */
1368 public static final int IDLE = 2; /* The call is idle. Nothing active */
1369 public static final int ACTIVE = 3; /* There is an active call */
1370 public static final int INCOMING = 4; /* A normal incoming phone call */
1371 public static final int CALL_WAITING = 5; /* Incoming call while another is active */
1372 public static final int DIALING = 6; /* An outgoing call during dial phase */
1373 public static final int REDIALING = 7; /* Subsequent dialing attempt after a failure */
1374 public static final int ONHOLD = 8; /* An active phone call placed on hold */
1375 public static final int DISCONNECTING = 9; /* A call is being ended. */
1376 public static final int DISCONNECTED = 10; /* State after a call disconnects */
1377 public static final int CONFERENCED = 11; /* DialerCall part of a conference call */
1378 public static final int SELECT_PHONE_ACCOUNT = 12; /* Waiting for account selection */
1379 public static final int CONNECTING = 13; /* Waiting for Telecom broadcast to finish */
1380 public static final int BLOCKED = 14; /* The number was found on the block list */
1381 public static final int PULLING = 15; /* An external call being pulled to the device */
Eric Erfanian2ca43182017-08-31 06:57:16 -07001382 public static final int CALL_PENDING = 16; /* A call is pending on a long process to finish */
Eric Erfanianccca3152017-02-22 16:32:36 -08001383
1384 public static boolean isConnectingOrConnected(int state) {
1385 switch (state) {
1386 case ACTIVE:
1387 case INCOMING:
1388 case CALL_WAITING:
1389 case CONNECTING:
1390 case DIALING:
1391 case PULLING:
1392 case REDIALING:
1393 case ONHOLD:
1394 case CONFERENCED:
1395 return true;
1396 default:
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001397 return false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001398 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001399 }
1400
1401 public static boolean isDialing(int state) {
1402 return state == DIALING || state == PULLING || state == REDIALING;
1403 }
1404
1405 public static String toString(int state) {
1406 switch (state) {
1407 case INVALID:
1408 return "INVALID";
1409 case NEW:
1410 return "NEW";
1411 case IDLE:
1412 return "IDLE";
1413 case ACTIVE:
1414 return "ACTIVE";
1415 case INCOMING:
1416 return "INCOMING";
1417 case CALL_WAITING:
1418 return "CALL_WAITING";
1419 case DIALING:
1420 return "DIALING";
1421 case PULLING:
1422 return "PULLING";
1423 case REDIALING:
1424 return "REDIALING";
1425 case ONHOLD:
1426 return "ONHOLD";
1427 case DISCONNECTING:
1428 return "DISCONNECTING";
1429 case DISCONNECTED:
1430 return "DISCONNECTED";
1431 case CONFERENCED:
1432 return "CONFERENCED";
1433 case SELECT_PHONE_ACCOUNT:
1434 return "SELECT_PHONE_ACCOUNT";
1435 case CONNECTING:
1436 return "CONNECTING";
1437 case BLOCKED:
1438 return "BLOCKED";
1439 default:
1440 return "UNKNOWN";
1441 }
1442 }
1443 }
1444
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001445 /** Camera direction constants */
1446 public static class CameraDirection {
Eric Erfanianccca3152017-02-22 16:32:36 -08001447 public static final int CAMERA_DIRECTION_UNKNOWN = -1;
1448 public static final int CAMERA_DIRECTION_FRONT_FACING = CameraCharacteristics.LENS_FACING_FRONT;
1449 public static final int CAMERA_DIRECTION_BACK_FACING = CameraCharacteristics.LENS_FACING_BACK;
Eric Erfanianccca3152017-02-22 16:32:36 -08001450 }
1451
1452 /**
1453 * Tracks any state variables that is useful for logging. There is some amount of overlap with
1454 * existing call member variables, but this duplication helps to ensure that none of these logging
1455 * variables will interface with/and affect call logic.
1456 */
1457 public static class LogState {
1458
1459 public DisconnectCause disconnectCause;
1460 public boolean isIncoming = false;
Eric Erfanian8369df02017-05-03 10:27:13 -07001461 public ContactLookupResult.Type contactLookupResult =
1462 ContactLookupResult.Type.UNKNOWN_LOOKUP_RESULT_TYPE;
Eric Erfanianccca3152017-02-22 16:32:36 -08001463 public CallSpecificAppData callSpecificAppData;
1464 // If this was a conference call, the total number of calls involved in the conference.
1465 public int conferencedCalls = 0;
1466 public long duration = 0;
1467 public boolean isLogged = false;
1468
Eric Erfanian8369df02017-05-03 10:27:13 -07001469 private static String lookupToString(ContactLookupResult.Type lookupType) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001470 switch (lookupType) {
Eric Erfanian8369df02017-05-03 10:27:13 -07001471 case LOCAL_CONTACT:
Eric Erfanianccca3152017-02-22 16:32:36 -08001472 return "Local";
Eric Erfanian8369df02017-05-03 10:27:13 -07001473 case LOCAL_CACHE:
Eric Erfanianccca3152017-02-22 16:32:36 -08001474 return "Cache";
Eric Erfanian8369df02017-05-03 10:27:13 -07001475 case REMOTE:
Eric Erfanianccca3152017-02-22 16:32:36 -08001476 return "Remote";
Eric Erfanian8369df02017-05-03 10:27:13 -07001477 case EMERGENCY:
Eric Erfanianccca3152017-02-22 16:32:36 -08001478 return "Emergency";
Eric Erfanian8369df02017-05-03 10:27:13 -07001479 case VOICEMAIL:
Eric Erfanianccca3152017-02-22 16:32:36 -08001480 return "Voicemail";
1481 default:
1482 return "Not found";
1483 }
1484 }
1485
1486 private static String initiationToString(CallSpecificAppData callSpecificAppData) {
1487 if (callSpecificAppData == null) {
1488 return "null";
1489 }
Eric Erfanian8369df02017-05-03 10:27:13 -07001490 switch (callSpecificAppData.getCallInitiationType()) {
1491 case INCOMING_INITIATION:
Eric Erfanianccca3152017-02-22 16:32:36 -08001492 return "Incoming";
Eric Erfanian8369df02017-05-03 10:27:13 -07001493 case DIALPAD:
Eric Erfanianccca3152017-02-22 16:32:36 -08001494 return "Dialpad";
Eric Erfanian8369df02017-05-03 10:27:13 -07001495 case SPEED_DIAL:
Eric Erfanianccca3152017-02-22 16:32:36 -08001496 return "Speed Dial";
Eric Erfanian8369df02017-05-03 10:27:13 -07001497 case REMOTE_DIRECTORY:
Eric Erfanianccca3152017-02-22 16:32:36 -08001498 return "Remote Directory";
Eric Erfanian8369df02017-05-03 10:27:13 -07001499 case SMART_DIAL:
Eric Erfanianccca3152017-02-22 16:32:36 -08001500 return "Smart Dial";
Eric Erfanian8369df02017-05-03 10:27:13 -07001501 case REGULAR_SEARCH:
Eric Erfanianccca3152017-02-22 16:32:36 -08001502 return "Regular Search";
Eric Erfanian8369df02017-05-03 10:27:13 -07001503 case CALL_LOG:
Eric Erfanianccca3152017-02-22 16:32:36 -08001504 return "DialerCall Log";
Eric Erfanian8369df02017-05-03 10:27:13 -07001505 case CALL_LOG_FILTER:
Eric Erfanianccca3152017-02-22 16:32:36 -08001506 return "DialerCall Log Filter";
Eric Erfanian8369df02017-05-03 10:27:13 -07001507 case VOICEMAIL_LOG:
Eric Erfanianccca3152017-02-22 16:32:36 -08001508 return "Voicemail Log";
Eric Erfanian8369df02017-05-03 10:27:13 -07001509 case CALL_DETAILS:
Eric Erfanianccca3152017-02-22 16:32:36 -08001510 return "DialerCall Details";
Eric Erfanian8369df02017-05-03 10:27:13 -07001511 case QUICK_CONTACTS:
Eric Erfanianccca3152017-02-22 16:32:36 -08001512 return "Quick Contacts";
Eric Erfanian8369df02017-05-03 10:27:13 -07001513 case EXTERNAL_INITIATION:
Eric Erfanianccca3152017-02-22 16:32:36 -08001514 return "External";
Eric Erfanian8369df02017-05-03 10:27:13 -07001515 case LAUNCHER_SHORTCUT:
Eric Erfanianccca3152017-02-22 16:32:36 -08001516 return "Launcher Shortcut";
1517 default:
Eric Erfanian8369df02017-05-03 10:27:13 -07001518 return "Unknown: " + callSpecificAppData.getCallInitiationType();
Eric Erfanianccca3152017-02-22 16:32:36 -08001519 }
1520 }
1521
1522 @Override
1523 public String toString() {
1524 return String.format(
1525 Locale.US,
1526 "["
1527 + "%s, " // DisconnectCause toString already describes the object type
1528 + "isIncoming: %s, "
1529 + "contactLookup: %s, "
1530 + "callInitiation: %s, "
1531 + "duration: %s"
1532 + "]",
1533 disconnectCause,
1534 isIncoming,
1535 lookupToString(contactLookupResult),
1536 initiationToString(callSpecificAppData),
1537 duration);
1538 }
1539 }
1540
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001541 private static class VideoTechManager {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001542 private final Context context;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001543 private final EmptyVideoTech emptyVideoTech = new EmptyVideoTech();
Eric Erfanian90508232017-03-24 09:31:16 -07001544 private final List<VideoTech> videoTechs;
roldenburg7bb96232017-10-09 10:32:05 -07001545 private VideoTech savedTech;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001546
1547 VideoTechManager(DialerCall call) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001548 this.context = call.mContext;
1549
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001550 String phoneNumber = call.getNumber();
Eric Erfaniand8046e52017-04-06 09:41:50 -07001551 phoneNumber = phoneNumber != null ? phoneNumber : "";
Eric Erfanian2ca43182017-08-31 06:57:16 -07001552 phoneNumber = phoneNumber.replaceAll("[^+0-9]", "");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001553
1554 // Insert order here determines the priority of that video tech option
Eric Erfanian8369df02017-05-03 10:27:13 -07001555 videoTechs = new ArrayList<>();
yueg457b3972017-09-18 15:11:47 -07001556
wangqi4d705e52017-09-28 12:23:35 -07001557 videoTechs.add(new ImsVideoTech(Logger.get(call.mContext), call, call.mTelecomCall));
Eric Erfanian90508232017-03-24 09:31:16 -07001558
1559 VideoTech rcsVideoTech =
1560 EnrichedCallComponent.get(call.mContext)
1561 .getRcsVideoShareFactory()
1562 .newRcsVideoShare(
1563 EnrichedCallComponent.get(call.mContext).getEnrichedCallManager(),
1564 call,
Eric Erfaniand8046e52017-04-06 09:41:50 -07001565 phoneNumber);
Eric Erfanian90508232017-03-24 09:31:16 -07001566 if (rcsVideoTech != null) {
1567 videoTechs.add(rcsVideoTech);
1568 }
Eric Erfaniand8046e52017-04-06 09:41:50 -07001569
1570 videoTechs.add(
1571 new LightbringerTech(
Eric Erfanian2ca43182017-08-31 06:57:16 -07001572 LightbringerComponent.get(call.mContext).getLightbringer(),
1573 call,
1574 call.mTelecomCall,
1575 phoneNumber));
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001576 }
1577
roldenburg7bb96232017-10-09 10:32:05 -07001578 VideoTech getVideoTech() {
1579 if (savedTech != null) {
1580 return savedTech;
1581 }
1582
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001583 for (VideoTech tech : videoTechs) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001584 if (tech.isAvailable(context)) {
roldenburg7bb96232017-10-09 10:32:05 -07001585 // Remember the first VideoTech that becomes available and always use it
1586 savedTech = tech;
1587 savedTech.becomePrimary();
1588 return savedTech;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001589 }
1590 }
1591
1592 return emptyVideoTech;
1593 }
1594
1595 void dispatchCallStateChanged(int newState) {
1596 for (VideoTech videoTech : videoTechs) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001597 videoTech.onCallStateChanged(context, newState);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001598 }
1599 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001600
1601 void dispatchRemovedFromCallList() {
1602 for (VideoTech videoTech : videoTechs) {
1603 videoTech.onRemovedFromCallList();
1604 }
1605 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001606 }
1607
Eric Erfanianccca3152017-02-22 16:32:36 -08001608 /** Called when canned text responses have been loaded. */
1609 public interface CannedTextResponsesLoadedListener {
1610 void onCannedTextResponsesLoaded(DialerCall call);
1611 }
1612}