blob: ee9a92609e83e0065f5a894016739855f40c3af8 [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;
Eric Erfanianccca3152017-02-22 16:32:36 -080041import android.text.TextUtils;
42import com.android.contacts.common.compat.CallCompat;
Eric Erfanianccca3152017-02-22 16:32:36 -080043import com.android.contacts.common.compat.telecom.TelecomManagerCompat;
Eric Erfanian8369df02017-05-03 10:27:13 -070044import com.android.dialer.callintent.CallInitiationType;
Eric Erfanianccca3152017-02-22 16:32:36 -080045import com.android.dialer.callintent.CallIntentParser;
Eric Erfanian8369df02017-05-03 10:27:13 -070046import com.android.dialer.callintent.CallSpecificAppData;
Eric Erfanianccca3152017-02-22 16:32:36 -080047import com.android.dialer.common.Assert;
Eric Erfanianccca3152017-02-22 16:32:36 -080048import com.android.dialer.common.LogUtil;
Eric Erfanian2ca43182017-08-31 06:57:16 -070049import com.android.dialer.compat.telephony.TelephonyManagerCompat;
50import com.android.dialer.configprovider.ConfigProviderBindings;
Eric Erfanian8369df02017-05-03 10:27:13 -070051import com.android.dialer.enrichedcall.EnrichedCallCapabilities;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070052import com.android.dialer.enrichedcall.EnrichedCallComponent;
Eric Erfanian2ca43182017-08-31 06:57:16 -070053import com.android.dialer.enrichedcall.EnrichedCallManager;
54import com.android.dialer.enrichedcall.EnrichedCallManager.CapabilitiesListener;
55import com.android.dialer.enrichedcall.EnrichedCallManager.Filter;
56import com.android.dialer.enrichedcall.EnrichedCallManager.StateChangedListener;
Eric Erfanian8369df02017-05-03 10:27:13 -070057import com.android.dialer.enrichedcall.Session;
Eric Erfaniand8046e52017-04-06 09:41:50 -070058import com.android.dialer.lightbringer.LightbringerComponent;
Eric Erfanian8369df02017-05-03 10:27:13 -070059import com.android.dialer.logging.ContactLookupResult;
60import com.android.dialer.logging.DialerImpression;
61import com.android.dialer.logging.Logger;
Eric Erfanianc857f902017-05-15 14:05:33 -070062import com.android.dialer.theme.R;
Eric Erfanian8369df02017-05-03 10:27:13 -070063import com.android.incallui.audiomode.AudioModeProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080064import com.android.incallui.latencyreport.LatencyReport;
65import com.android.incallui.util.TelecomCallUtil;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070066import com.android.incallui.videotech.VideoTech;
67import com.android.incallui.videotech.VideoTech.VideoTechListener;
68import com.android.incallui.videotech.empty.EmptyVideoTech;
69import com.android.incallui.videotech.ims.ImsVideoTech;
Eric Erfaniand8046e52017-04-06 09:41:50 -070070import com.android.incallui.videotech.lightbringer.LightbringerTech;
Eric Erfanian90508232017-03-24 09:31:16 -070071import com.android.incallui.videotech.utils.VideoUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080072import java.lang.annotation.Retention;
73import java.lang.annotation.RetentionPolicy;
74import java.util.ArrayList;
75import java.util.List;
76import java.util.Locale;
77import java.util.Objects;
78import java.util.UUID;
79import java.util.concurrent.CopyOnWriteArrayList;
80import java.util.concurrent.TimeUnit;
81
82/** Describes a single call and its state. */
Eric Erfanian2ca43182017-08-31 06:57:16 -070083public class DialerCall implements VideoTechListener, StateChangedListener, CapabilitiesListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080084
85 public static final int CALL_HISTORY_STATUS_UNKNOWN = 0;
86 public static final int CALL_HISTORY_STATUS_PRESENT = 1;
87 public static final int CALL_HISTORY_STATUS_NOT_PRESENT = 2;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070088
89 // Hard coded property for {@code Call}. Upstreamed change from Motorola.
90 // TODO(b/35359461): Move it to Telecom in framework.
91 public static final int PROPERTY_CODEC_KNOWN = 0x04000000;
92
Eric Erfanianccca3152017-02-22 16:32:36 -080093 private static final String ID_PREFIX = "DialerCall_";
94 private static final String CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS =
95 "emergency_callback_window_millis";
96 private static int sIdCounter = 0;
97
98 /**
Eric Erfanianc857f902017-05-15 14:05:33 -070099 * A counter used to append to restricted/private/hidden calls so that users can identify them in
100 * a conversation. This value is reset in {@link CallList#onCallRemoved(Context, Call)} when there
101 * are no live calls.
102 */
103 private static int sHiddenCounter;
104
105 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800106 * The unique call ID for every call. This will help us to identify each call and allow us the
107 * ability to stitch impressions to calls if needed.
108 */
109 private final String uniqueCallId = UUID.randomUUID().toString();
110
111 private final Call mTelecomCall;
112 private final LatencyReport mLatencyReport;
113 private final String mId;
Eric Erfanianc857f902017-05-15 14:05:33 -0700114 private final int mHiddenId;
Eric Erfanianccca3152017-02-22 16:32:36 -0800115 private final List<String> mChildCallIds = new ArrayList<>();
Eric Erfanianccca3152017-02-22 16:32:36 -0800116 private final LogState mLogState = new LogState();
117 private final Context mContext;
118 private final DialerCallDelegate mDialerCallDelegate;
119 private final List<DialerCallListener> mListeners = new CopyOnWriteArrayList<>();
120 private final List<CannedTextResponsesLoadedListener> mCannedTextResponsesLoadedListeners =
121 new CopyOnWriteArrayList<>();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700122 private final VideoTechManager mVideoTechManager;
Eric Erfanianccca3152017-02-22 16:32:36 -0800123
124 private boolean mIsEmergencyCall;
125 private Uri mHandle;
126 private int mState = State.INVALID;
127 private DisconnectCause mDisconnectCause;
128
129 private boolean hasShownWiFiToLteHandoverToast;
130 private boolean doNotShowDialogForHandoffToWifiFailure;
131
Eric Erfanianccca3152017-02-22 16:32:36 -0800132 private String mChildNumber;
133 private String mLastForwardedNumber;
134 private String mCallSubject;
135 private PhoneAccountHandle mPhoneAccountHandle;
136 @CallHistoryStatus private int mCallHistoryStatus = CALL_HISTORY_STATUS_UNKNOWN;
137 private boolean mIsSpam;
138 private boolean mIsBlocked;
139 private boolean isInUserSpamList;
140 private boolean isInUserWhiteList;
141 private boolean isInGlobalSpamList;
142 private boolean didShowCameraPermission;
143 private String callProviderLabel;
144 private String callbackNumber;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700145 private int mCameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
Eric Erfanian8369df02017-05-03 10:27:13 -0700146 private EnrichedCallCapabilities mEnrichedCallCapabilities;
147 private Session mEnrichedCallSession;
Eric Erfanianccca3152017-02-22 16:32:36 -0800148
Eric Erfanian2ca43182017-08-31 06:57:16 -0700149 private int answerAndReleaseButtonDisplayedTimes = 0;
150 private boolean releasedByAnsweringSecondCall = false;
151 // Times when a second call is received but AnswerAndRelease button is not shown
152 // since it's not supported.
153 private int secondCallWithoutAnswerAndReleasedButtonTimes = 0;
154 private VideoTech videoTech;
155
yueg457b3972017-09-18 15:11:47 -0700156 private boolean isImsReachable;
157
Eric Erfanianccca3152017-02-22 16:32:36 -0800158 public static String getNumberFromHandle(Uri handle) {
159 return handle == null ? "" : handle.getSchemeSpecificPart();
160 }
161
162 /**
163 * Whether the call is put on hold by remote party. This is different than the {@link
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700164 * State#ONHOLD} state which indicates that the call is being held locally on the device.
Eric Erfanianccca3152017-02-22 16:32:36 -0800165 */
166 private boolean isRemotelyHeld;
167
Eric Erfanian2ca43182017-08-31 06:57:16 -0700168 /** Indicates whether this call is currently in the process of being merged into a conference. */
169 private boolean isMergeInProcess;
170
Eric Erfanianccca3152017-02-22 16:32:36 -0800171 /**
172 * Indicates whether the phone account associated with this call supports specifying a call
173 * subject.
174 */
175 private boolean mIsCallSubjectSupported;
176
177 private final Call.Callback mTelecomCallCallback =
178 new Call.Callback() {
179 @Override
180 public void onStateChanged(Call call, int newState) {
181 LogUtil.v("TelecomCallCallback.onStateChanged", "call=" + call + " newState=" + newState);
182 update();
183 }
184
185 @Override
186 public void onParentChanged(Call call, Call newParent) {
187 LogUtil.v(
188 "TelecomCallCallback.onParentChanged", "call=" + call + " newParent=" + newParent);
189 update();
190 }
191
192 @Override
193 public void onChildrenChanged(Call call, List<Call> children) {
194 update();
195 }
196
197 @Override
198 public void onDetailsChanged(Call call, Call.Details details) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700199 LogUtil.v(
200 "TelecomCallCallback.onDetailsChanged", " call=" + call + " details=" + details);
Eric Erfanianccca3152017-02-22 16:32:36 -0800201 update();
202 }
203
204 @Override
205 public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {
206 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700207 "TelecomCallCallback.onCannedTextResponsesLoaded",
Eric Erfanianccca3152017-02-22 16:32:36 -0800208 "call=" + call + " cannedTextResponses=" + cannedTextResponses);
209 for (CannedTextResponsesLoadedListener listener : mCannedTextResponsesLoadedListeners) {
210 listener.onCannedTextResponsesLoaded(DialerCall.this);
211 }
212 }
213
214 @Override
215 public void onPostDialWait(Call call, String remainingPostDialSequence) {
216 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700217 "TelecomCallCallback.onPostDialWait",
Eric Erfanianccca3152017-02-22 16:32:36 -0800218 "call=" + call + " remainingPostDialSequence=" + remainingPostDialSequence);
219 update();
220 }
221
222 @Override
223 public void onVideoCallChanged(Call call, VideoCall videoCall) {
224 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700225 "TelecomCallCallback.onVideoCallChanged", "call=" + call + " videoCall=" + videoCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800226 update();
227 }
228
229 @Override
230 public void onCallDestroyed(Call call) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700231 LogUtil.v("TelecomCallCallback.onCallDestroyed", "call=" + call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700232 unregisterCallback();
Eric Erfanianccca3152017-02-22 16:32:36 -0800233 }
234
235 @Override
236 public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
237 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700238 "TelecomCallCallback.onConferenceableCallsChanged",
Eric Erfanianccca3152017-02-22 16:32:36 -0800239 "call %s, conferenceable calls: %d",
240 call,
241 conferenceableCalls.size());
242 update();
243 }
244
245 @Override
246 public void onConnectionEvent(android.telecom.Call call, String event, Bundle extras) {
247 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700248 "TelecomCallCallback.onConnectionEvent",
Eric Erfanianccca3152017-02-22 16:32:36 -0800249 "Call: " + call + ", Event: " + event + ", Extras: " + extras);
250 switch (event) {
251 // The Previous attempt to Merge two calls together has failed in Telecom. We must
252 // now update the UI to possibly re-enable the Merge button based on the number of
253 // currently conferenceable calls available or Connection Capabilities.
254 case android.telecom.Connection.EVENT_CALL_MERGE_FAILED:
255 update();
256 break;
257 case TelephonyManagerCompat.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE:
258 notifyWiFiToLteHandover();
259 break;
260 case TelephonyManagerCompat.EVENT_HANDOVER_TO_WIFI_FAILED:
261 notifyHandoverToWifiFailed();
262 break;
263 case TelephonyManagerCompat.EVENT_CALL_REMOTELY_HELD:
264 isRemotelyHeld = true;
265 update();
266 break;
267 case TelephonyManagerCompat.EVENT_CALL_REMOTELY_UNHELD:
268 isRemotelyHeld = false;
269 update();
270 break;
Eric Erfanianc857f902017-05-15 14:05:33 -0700271 case TelephonyManagerCompat.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC:
272 notifyInternationalCallOnWifi();
273 break;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700274 case TelephonyManagerCompat.EVENT_MERGE_START:
275 LogUtil.i("DialerCall.onConnectionEvent", "merge start");
276 isMergeInProcess = true;
277 break;
278 case TelephonyManagerCompat.EVENT_MERGE_COMPLETE:
279 LogUtil.i("DialerCall.onConnectionEvent", "merge complete");
280 isMergeInProcess = false;
281 break;
Eric Erfanianccca3152017-02-22 16:32:36 -0800282 default:
283 break;
284 }
285 }
286 };
Eric Erfanianc857f902017-05-15 14:05:33 -0700287
Eric Erfanianccca3152017-02-22 16:32:36 -0800288 private long mTimeAddedMs;
289
290 public DialerCall(
291 Context context,
292 DialerCallDelegate dialerCallDelegate,
293 Call telecomCall,
294 LatencyReport latencyReport,
295 boolean registerCallback) {
296 Assert.isNotNull(context);
297 mContext = context;
298 mDialerCallDelegate = dialerCallDelegate;
299 mTelecomCall = telecomCall;
300 mLatencyReport = latencyReport;
301 mId = ID_PREFIX + Integer.toString(sIdCounter++);
302
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700303 // Must be after assigning mTelecomCall
304 mVideoTechManager = new VideoTechManager(this);
305
306 updateFromTelecomCall();
Eric Erfanianc857f902017-05-15 14:05:33 -0700307 if (isHiddenNumber() && TextUtils.isEmpty(getNumber())) {
308 mHiddenId = ++sHiddenCounter;
309 } else {
310 mHiddenId = 0;
311 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800312
313 if (registerCallback) {
314 mTelecomCall.registerCallback(mTelecomCallCallback);
315 }
316
317 mTimeAddedMs = System.currentTimeMillis();
318 parseCallSpecificAppData();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700319
320 updateEnrichedCallSession();
Eric Erfanianccca3152017-02-22 16:32:36 -0800321 }
322
323 private static int translateState(int state) {
324 switch (state) {
325 case Call.STATE_NEW:
326 case Call.STATE_CONNECTING:
327 return DialerCall.State.CONNECTING;
328 case Call.STATE_SELECT_PHONE_ACCOUNT:
329 return DialerCall.State.SELECT_PHONE_ACCOUNT;
330 case Call.STATE_DIALING:
331 return DialerCall.State.DIALING;
332 case Call.STATE_PULLING_CALL:
333 return DialerCall.State.PULLING;
334 case Call.STATE_RINGING:
335 return DialerCall.State.INCOMING;
336 case Call.STATE_ACTIVE:
337 return DialerCall.State.ACTIVE;
338 case Call.STATE_HOLDING:
339 return DialerCall.State.ONHOLD;
340 case Call.STATE_DISCONNECTED:
341 return DialerCall.State.DISCONNECTED;
342 case Call.STATE_DISCONNECTING:
343 return DialerCall.State.DISCONNECTING;
344 default:
345 return DialerCall.State.INVALID;
346 }
347 }
348
349 public static boolean areSame(DialerCall call1, DialerCall call2) {
350 if (call1 == null && call2 == null) {
351 return true;
352 } else if (call1 == null || call2 == null) {
353 return false;
354 }
355
356 // otherwise compare call Ids
357 return call1.getId().equals(call2.getId());
358 }
359
360 public static boolean areSameNumber(DialerCall call1, DialerCall call2) {
361 if (call1 == null && call2 == null) {
362 return true;
363 } else if (call1 == null || call2 == null) {
364 return false;
365 }
366
367 // otherwise compare call Numbers
368 return TextUtils.equals(call1.getNumber(), call2.getNumber());
369 }
370
371 public void addListener(DialerCallListener listener) {
372 Assert.isMainThread();
373 mListeners.add(listener);
374 }
375
376 public void removeListener(DialerCallListener listener) {
377 Assert.isMainThread();
378 mListeners.remove(listener);
379 }
380
381 public void addCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
382 Assert.isMainThread();
383 mCannedTextResponsesLoadedListeners.add(listener);
384 }
385
386 public void removeCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
387 Assert.isMainThread();
388 mCannedTextResponsesLoadedListeners.remove(listener);
389 }
390
391 public void notifyWiFiToLteHandover() {
392 LogUtil.i("DialerCall.notifyWiFiToLteHandover", "");
393 for (DialerCallListener listener : mListeners) {
394 listener.onWiFiToLteHandover();
395 }
396 }
397
398 public void notifyHandoverToWifiFailed() {
399 LogUtil.i("DialerCall.notifyHandoverToWifiFailed", "");
400 for (DialerCallListener listener : mListeners) {
401 listener.onHandoverToWifiFailure();
402 }
403 }
404
Eric Erfanianc857f902017-05-15 14:05:33 -0700405 public void notifyInternationalCallOnWifi() {
406 LogUtil.enterBlock("DialerCall.notifyInternationalCallOnWifi");
407 for (DialerCallListener dialerCallListener : mListeners) {
408 dialerCallListener.onInternationalCallOnWifi();
409 }
410 }
411
Eric Erfanianccca3152017-02-22 16:32:36 -0800412 /* package-private */ Call getTelecomCall() {
413 return mTelecomCall;
414 }
415
416 public StatusHints getStatusHints() {
417 return mTelecomCall.getDetails().getStatusHints();
418 }
419
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700420 public int getCameraDir() {
421 return mCameraDirection;
422 }
423
424 public void setCameraDir(int cameraDir) {
425 if (cameraDir == CameraDirection.CAMERA_DIRECTION_FRONT_FACING
426 || cameraDir == CameraDirection.CAMERA_DIRECTION_BACK_FACING) {
427 mCameraDirection = cameraDir;
428 } else {
429 mCameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
430 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800431 }
432
Eric Erfanian2ca43182017-08-31 06:57:16 -0700433 public boolean wasParentCall() {
434 return mLogState.conferencedCalls != 0;
435 }
436
Eric Erfanianccca3152017-02-22 16:32:36 -0800437 private void update() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700438 Trace.beginSection("DialerCall.update");
Eric Erfanianccca3152017-02-22 16:32:36 -0800439 int oldState = getState();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700440 // Clear any cache here that could potentially change on update.
441 videoTech = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800442 // We want to potentially register a video call callback here.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700443 updateFromTelecomCall();
Eric Erfanianccca3152017-02-22 16:32:36 -0800444 if (oldState != getState() && getState() == DialerCall.State.DISCONNECTED) {
445 for (DialerCallListener listener : mListeners) {
446 listener.onDialerCallDisconnect();
447 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700448 EnrichedCallComponent.get(mContext)
449 .getEnrichedCallManager()
450 .unregisterCapabilitiesListener(this);
451 EnrichedCallComponent.get(mContext)
452 .getEnrichedCallManager()
453 .unregisterStateChangedListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800454 } else {
455 for (DialerCallListener listener : mListeners) {
456 listener.onDialerCallUpdate();
457 }
458 }
459 Trace.endSection();
460 }
461
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700462 private void updateFromTelecomCall() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700463 Trace.beginSection("DialerCall.updateFromTelecomCall");
Eric Erfanianccca3152017-02-22 16:32:36 -0800464 LogUtil.v("DialerCall.updateFromTelecomCall", mTelecomCall.toString());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700465
466 mVideoTechManager.dispatchCallStateChanged(mTelecomCall.getState());
467
Eric Erfanianccca3152017-02-22 16:32:36 -0800468 final int translatedState = translateState(mTelecomCall.getState());
469 if (mState != State.BLOCKED) {
470 setState(translatedState);
471 setDisconnectCause(mTelecomCall.getDetails().getDisconnectCause());
Eric Erfanianccca3152017-02-22 16:32:36 -0800472 }
473
474 mChildCallIds.clear();
475 final int numChildCalls = mTelecomCall.getChildren().size();
476 for (int i = 0; i < numChildCalls; i++) {
477 mChildCallIds.add(
478 mDialerCallDelegate
479 .getDialerCallFromTelecomCall(mTelecomCall.getChildren().get(i))
480 .getId());
481 }
482
483 // The number of conferenced calls can change over the course of the call, so use the
484 // maximum number of conferenced child calls as the metric for conference call usage.
485 mLogState.conferencedCalls = Math.max(numChildCalls, mLogState.conferencedCalls);
486
487 updateFromCallExtras(mTelecomCall.getDetails().getExtras());
488
489 // If the handle of the call has changed, update state for the call determining if it is an
490 // emergency call.
491 Uri newHandle = mTelecomCall.getDetails().getHandle();
492 if (!Objects.equals(mHandle, newHandle)) {
493 mHandle = newHandle;
494 updateEmergencyCallState();
495 }
496
497 // If the phone account handle of the call is set, cache capability bit indicating whether
498 // the phone account supports call subjects.
499 PhoneAccountHandle newPhoneAccountHandle = mTelecomCall.getDetails().getAccountHandle();
500 if (!Objects.equals(mPhoneAccountHandle, newPhoneAccountHandle)) {
501 mPhoneAccountHandle = newPhoneAccountHandle;
502
503 if (mPhoneAccountHandle != null) {
504 PhoneAccount phoneAccount =
505 mContext.getSystemService(TelecomManager.class).getPhoneAccount(mPhoneAccountHandle);
506 if (phoneAccount != null) {
507 mIsCallSubjectSupported =
508 phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT);
509 }
510 }
511 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700512 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800513 }
514
515 /**
516 * Tests corruption of the {@code callExtras} bundle by calling {@link
517 * Bundle#containsKey(String)}. If the bundle is corrupted a {@link IllegalArgumentException} will
518 * be thrown and caught by this function.
519 *
520 * @param callExtras the bundle to verify
521 * @return {@code true} if the bundle is corrupted, {@code false} otherwise.
522 */
523 protected boolean areCallExtrasCorrupted(Bundle callExtras) {
524 /**
525 * There's currently a bug in Telephony service (b/25613098) that could corrupt the extras
526 * bundle, resulting in a IllegalArgumentException while validating data under {@link
527 * Bundle#containsKey(String)}.
528 */
529 try {
530 callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS);
531 return false;
532 } catch (IllegalArgumentException e) {
533 LogUtil.e(
534 "DialerCall.areCallExtrasCorrupted", "callExtras is corrupted, ignoring exception", e);
535 return true;
536 }
537 }
538
539 protected void updateFromCallExtras(Bundle callExtras) {
540 if (callExtras == null || areCallExtrasCorrupted(callExtras)) {
541 /**
542 * If the bundle is corrupted, abandon information update as a work around. These are not
543 * critical for the dialer to function.
544 */
545 return;
546 }
547 // Check for a change in the child address and notify any listeners.
548 if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) {
549 String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS);
550 if (!Objects.equals(childNumber, mChildNumber)) {
551 mChildNumber = childNumber;
552 for (DialerCallListener listener : mListeners) {
553 listener.onDialerCallChildNumberChange();
554 }
555 }
556 }
557
558 // Last forwarded number comes in as an array of strings. We want to choose the
559 // last item in the array. The forwarding numbers arrive independently of when the
560 // call is originally set up, so we need to notify the the UI of the change.
561 if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) {
562 ArrayList<String> lastForwardedNumbers =
563 callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER);
564
565 if (lastForwardedNumbers != null) {
566 String lastForwardedNumber = null;
567 if (!lastForwardedNumbers.isEmpty()) {
568 lastForwardedNumber = lastForwardedNumbers.get(lastForwardedNumbers.size() - 1);
569 }
570
571 if (!Objects.equals(lastForwardedNumber, mLastForwardedNumber)) {
572 mLastForwardedNumber = lastForwardedNumber;
573 for (DialerCallListener listener : mListeners) {
574 listener.onDialerCallLastForwardedNumberChange();
575 }
576 }
577 }
578 }
579
580 // DialerCall subject is present in the extras at the start of call, so we do not need to
581 // notify any other listeners of this.
582 if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) {
583 String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT);
584 if (!Objects.equals(mCallSubject, callSubject)) {
585 mCallSubject = callSubject;
586 }
587 }
588 }
589
Eric Erfanianccca3152017-02-22 16:32:36 -0800590 public String getId() {
591 return mId;
592 }
593
Eric Erfanianc857f902017-05-15 14:05:33 -0700594 /**
595 * @return name appended with a number if the number is restricted/unknown and the user has
596 * received more than one restricted/unknown call.
597 */
598 @Nullable
599 public String updateNameIfRestricted(@Nullable String name) {
600 if (name != null && isHiddenNumber() && mHiddenId != 0 && sHiddenCounter > 1) {
601 return mContext.getString(R.string.unknown_counter, name, mHiddenId);
602 }
603 return name;
604 }
605
606 public static void clearRestrictedCount() {
607 sHiddenCounter = 0;
608 }
609
610 private boolean isHiddenNumber() {
611 return getNumberPresentation() == TelecomManager.PRESENTATION_RESTRICTED
612 || getNumberPresentation() == TelecomManager.PRESENTATION_UNKNOWN;
613 }
614
Eric Erfanianccca3152017-02-22 16:32:36 -0800615 public boolean hasShownWiFiToLteHandoverToast() {
616 return hasShownWiFiToLteHandoverToast;
617 }
618
619 public void setHasShownWiFiToLteHandoverToast() {
620 hasShownWiFiToLteHandoverToast = true;
621 }
622
623 public boolean showWifiHandoverAlertAsToast() {
624 return doNotShowDialogForHandoffToWifiFailure;
625 }
626
627 public void setDoNotShowDialogForHandoffToWifiFailure(boolean bool) {
628 doNotShowDialogForHandoffToWifiFailure = bool;
629 }
630
631 public long getTimeAddedMs() {
632 return mTimeAddedMs;
633 }
634
635 @Nullable
636 public String getNumber() {
637 return TelecomCallUtil.getNumber(mTelecomCall);
638 }
639
640 public void blockCall() {
641 mTelecomCall.reject(false, null);
642 setState(State.BLOCKED);
643 }
644
645 @Nullable
646 public Uri getHandle() {
647 return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandle();
648 }
649
650 public boolean isEmergencyCall() {
651 return mIsEmergencyCall;
652 }
653
654 public boolean isPotentialEmergencyCallback() {
655 // The property PROPERTY_EMERGENCY_CALLBACK_MODE is only set for CDMA calls when the system
656 // is actually in emergency callback mode (ie data is disabled).
657 if (hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE)) {
658 return true;
659 }
660 // We want to treat any incoming call that arrives a short time after an outgoing emergency call
661 // as a potential emergency callback.
662 if (getExtras() != null
663 && getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0)
664 > 0) {
665 long lastEmergencyCallMillis =
666 getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0);
667 if (isInEmergencyCallbackWindow(lastEmergencyCallMillis)) {
668 return true;
669 }
670 }
671 return false;
672 }
673
674 boolean isInEmergencyCallbackWindow(long timestampMillis) {
675 long emergencyCallbackWindowMillis =
676 ConfigProviderBindings.get(mContext)
677 .getLong(CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS, TimeUnit.MINUTES.toMillis(5));
678 return System.currentTimeMillis() - timestampMillis < emergencyCallbackWindowMillis;
679 }
680
681 public int getState() {
682 if (mTelecomCall != null && mTelecomCall.getParent() != null) {
683 return State.CONFERENCED;
684 } else {
685 return mState;
686 }
687 }
688
689 public void setState(int state) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700690 if (state == State.INCOMING) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800691 mLogState.isIncoming = true;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700692 } else if (state == State.DISCONNECTED) {
693 long newDuration =
Eric Erfanianccca3152017-02-22 16:32:36 -0800694 getConnectTimeMillis() == 0 ? 0 : System.currentTimeMillis() - getConnectTimeMillis();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700695 if (mState != state) {
696 mLogState.duration = newDuration;
697 } else {
698 LogUtil.i(
699 "DialerCall.setState",
700 "ignoring state transition from DISCONNECTED to DISCONNECTED."
701 + " Duration would have changed from %s to %s",
702 mLogState.duration,
703 newDuration);
704 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800705 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700706 mState = state;
Eric Erfanianccca3152017-02-22 16:32:36 -0800707 }
708
709 public int getNumberPresentation() {
710 return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getHandlePresentation();
711 }
712
713 public int getCnapNamePresentation() {
714 return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getCallerDisplayNamePresentation();
715 }
716
717 @Nullable
718 public String getCnapName() {
719 return mTelecomCall == null ? null : getTelecomCall().getDetails().getCallerDisplayName();
720 }
721
722 public Bundle getIntentExtras() {
723 return mTelecomCall.getDetails().getIntentExtras();
724 }
725
726 @Nullable
727 public Bundle getExtras() {
728 return mTelecomCall == null ? null : mTelecomCall.getDetails().getExtras();
729 }
730
731 /** @return The child number for the call, or {@code null} if none specified. */
732 public String getChildNumber() {
733 return mChildNumber;
734 }
735
736 /** @return The last forwarded number for the call, or {@code null} if none specified. */
737 public String getLastForwardedNumber() {
738 return mLastForwardedNumber;
739 }
740
741 /** @return The call subject, or {@code null} if none specified. */
742 public String getCallSubject() {
743 return mCallSubject;
744 }
745
746 /**
747 * @return {@code true} if the call's phone account supports call subjects, {@code false}
748 * otherwise.
749 */
750 public boolean isCallSubjectSupported() {
751 return mIsCallSubjectSupported;
752 }
753
754 /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
755 public DisconnectCause getDisconnectCause() {
756 if (mState == State.DISCONNECTED || mState == State.IDLE) {
757 return mDisconnectCause;
758 }
759
760 return new DisconnectCause(DisconnectCause.UNKNOWN);
761 }
762
763 public void setDisconnectCause(DisconnectCause disconnectCause) {
764 mDisconnectCause = disconnectCause;
765 mLogState.disconnectCause = mDisconnectCause;
766 }
767
768 /** Returns the possible text message responses. */
769 public List<String> getCannedSmsResponses() {
770 return mTelecomCall.getCannedTextResponses();
771 }
772
773 /** Checks if the call supports the given set of capabilities supplied as a bit mask. */
774 public boolean can(int capabilities) {
775 int supportedCapabilities = mTelecomCall.getDetails().getCallCapabilities();
776
777 if ((capabilities & Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) {
778 // We allow you to merge if the capabilities allow it or if it is a call with
779 // conferenceable calls.
780 if (mTelecomCall.getConferenceableCalls().isEmpty()
781 && ((Call.Details.CAPABILITY_MERGE_CONFERENCE & supportedCapabilities) == 0)) {
782 // Cannot merge calls if there are no calls to merge with.
783 return false;
784 }
785 capabilities &= ~Call.Details.CAPABILITY_MERGE_CONFERENCE;
786 }
787 return (capabilities == (capabilities & supportedCapabilities));
788 }
789
790 public boolean hasProperty(int property) {
791 return mTelecomCall.getDetails().hasProperty(property);
792 }
793
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700794 @NonNull
Eric Erfanianccca3152017-02-22 16:32:36 -0800795 public String getUniqueCallId() {
796 return uniqueCallId;
797 }
798
799 /** Gets the time when the call first became active. */
800 public long getConnectTimeMillis() {
801 return mTelecomCall.getDetails().getConnectTimeMillis();
802 }
803
804 public boolean isConferenceCall() {
805 return hasProperty(Call.Details.PROPERTY_CONFERENCE);
806 }
807
808 @Nullable
809 public GatewayInfo getGatewayInfo() {
810 return mTelecomCall == null ? null : mTelecomCall.getDetails().getGatewayInfo();
811 }
812
813 @Nullable
814 public PhoneAccountHandle getAccountHandle() {
815 return mTelecomCall == null ? null : mTelecomCall.getDetails().getAccountHandle();
816 }
817
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700818 /** @return The {@link VideoCall} instance associated with the {@link Call}. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800819 public VideoCall getVideoCall() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700820 return mTelecomCall == null ? null : mTelecomCall.getVideoCall();
Eric Erfanianccca3152017-02-22 16:32:36 -0800821 }
822
823 public List<String> getChildCallIds() {
824 return mChildCallIds;
825 }
826
827 public String getParentId() {
828 Call parentCall = mTelecomCall.getParent();
829 if (parentCall != null) {
830 return mDialerCallDelegate.getDialerCallFromTelecomCall(parentCall).getId();
831 }
832 return null;
833 }
834
835 public int getVideoState() {
836 return mTelecomCall.getDetails().getVideoState();
837 }
838
839 public boolean isVideoCall() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700840 return getVideoTech().isTransmittingOrReceiving();
841 }
842
843 public boolean hasReceivedVideoUpgradeRequest() {
844 return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState());
845 }
846
847 public boolean hasSentVideoUpgradeRequest() {
848 return VideoUtils.hasSentVideoUpgradeRequest(getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800849 }
850
851 /**
852 * Determines if the call handle is an emergency number or not and caches the result to avoid
853 * repeated calls to isEmergencyNumber.
854 */
855 private void updateEmergencyCallState() {
856 mIsEmergencyCall = TelecomCallUtil.isEmergencyCall(mTelecomCall);
857 }
858
Eric Erfanianccca3152017-02-22 16:32:36 -0800859 public LogState getLogState() {
860 return mLogState;
861 }
862
863 /**
864 * Determines if the call is an external call.
865 *
866 * <p>An external call is one which does not exist locally for the {@link
867 * android.telecom.ConnectionService} it is associated with.
868 *
869 * <p>External calls are only supported in N and higher.
870 *
871 * @return {@code true} if the call is an external call, {@code false} otherwise.
872 */
873 public boolean isExternalCall() {
874 return VERSION.SDK_INT >= VERSION_CODES.N
875 && hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL);
876 }
877
878 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800879 * Determines if answering this call will cause an ongoing video call to be dropped.
880 *
881 * @return {@code true} if answering this call will drop an ongoing video call, {@code false}
882 * otherwise.
883 */
884 public boolean answeringDisconnectsForegroundVideoCall() {
885 Bundle extras = getExtras();
886 if (extras == null
887 || !extras.containsKey(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL)) {
888 return false;
889 }
890 return extras.getBoolean(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL);
891 }
892
893 private void parseCallSpecificAppData() {
894 if (isExternalCall()) {
895 return;
896 }
897
898 mLogState.callSpecificAppData = CallIntentParser.getCallSpecificAppData(getIntentExtras());
899 if (mLogState.callSpecificAppData == null) {
Eric Erfanian8369df02017-05-03 10:27:13 -0700900
901 mLogState.callSpecificAppData =
902 CallSpecificAppData.newBuilder()
903 .setCallInitiationType(CallInitiationType.Type.EXTERNAL_INITIATION)
904 .build();
Eric Erfanianccca3152017-02-22 16:32:36 -0800905 }
906 if (getState() == State.INCOMING) {
Eric Erfanian8369df02017-05-03 10:27:13 -0700907 mLogState.callSpecificAppData =
908 mLogState
909 .callSpecificAppData
910 .toBuilder()
911 .setCallInitiationType(CallInitiationType.Type.INCOMING_INITIATION)
912 .build();
Eric Erfanianccca3152017-02-22 16:32:36 -0800913 }
914 }
915
916 @Override
917 public String toString() {
918 if (mTelecomCall == null) {
919 // This should happen only in testing since otherwise we would never have a null
920 // Telecom call.
921 return String.valueOf(mId);
922 }
923
924 return String.format(
925 Locale.US,
926 "[%s, %s, %s, %s, children:%s, parent:%s, "
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700927 + "conferenceable:%s, videoState:%s, mSessionModificationState:%d, CameraDir:%s]",
Eric Erfanianccca3152017-02-22 16:32:36 -0800928 mId,
929 State.toString(getState()),
930 Details.capabilitiesToString(mTelecomCall.getDetails().getCallCapabilities()),
931 Details.propertiesToString(mTelecomCall.getDetails().getCallProperties()),
932 mChildCallIds,
933 getParentId(),
934 this.mTelecomCall.getConferenceableCalls(),
935 VideoProfile.videoStateToString(mTelecomCall.getDetails().getVideoState()),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700936 getVideoTech().getSessionModificationState(),
937 getCameraDir());
Eric Erfanianccca3152017-02-22 16:32:36 -0800938 }
939
940 public String toSimpleString() {
941 return super.toString();
942 }
943
944 @CallHistoryStatus
945 public int getCallHistoryStatus() {
946 return mCallHistoryStatus;
947 }
948
949 public void setCallHistoryStatus(@CallHistoryStatus int callHistoryStatus) {
950 mCallHistoryStatus = callHistoryStatus;
951 }
952
953 public boolean didShowCameraPermission() {
954 return didShowCameraPermission;
955 }
956
957 public void setDidShowCameraPermission(boolean didShow) {
958 didShowCameraPermission = didShow;
959 }
960
961 public boolean isInGlobalSpamList() {
962 return isInGlobalSpamList;
963 }
964
965 public void setIsInGlobalSpamList(boolean inSpamList) {
966 isInGlobalSpamList = inSpamList;
967 }
968
969 public boolean isInUserSpamList() {
970 return isInUserSpamList;
971 }
972
973 public void setIsInUserSpamList(boolean inSpamList) {
974 isInUserSpamList = inSpamList;
975 }
976
977 public boolean isInUserWhiteList() {
978 return isInUserWhiteList;
979 }
980
981 public void setIsInUserWhiteList(boolean inWhiteList) {
982 isInUserWhiteList = inWhiteList;
983 }
984
985 public boolean isSpam() {
986 return mIsSpam;
987 }
988
989 public void setSpam(boolean isSpam) {
990 mIsSpam = isSpam;
991 }
992
993 public boolean isBlocked() {
994 return mIsBlocked;
995 }
996
997 public void setBlockedStatus(boolean isBlocked) {
998 mIsBlocked = isBlocked;
999 }
1000
1001 public boolean isRemotelyHeld() {
1002 return isRemotelyHeld;
1003 }
1004
Eric Erfanian2ca43182017-08-31 06:57:16 -07001005 public boolean isMergeInProcess() {
1006 return isMergeInProcess;
1007 }
1008
Eric Erfanianccca3152017-02-22 16:32:36 -08001009 public boolean isIncoming() {
1010 return mLogState.isIncoming;
1011 }
1012
Eric Erfanian2ca43182017-08-31 06:57:16 -07001013 public boolean isAssistedDialed() {
1014 if (getIntentExtras() != null) {
1015 return getIntentExtras().getBoolean(TelephonyManagerCompat.IS_ASSISTED_DIALED, false);
1016 }
1017 return false;
1018 }
1019
Eric Erfanianccca3152017-02-22 16:32:36 -08001020 public LatencyReport getLatencyReport() {
1021 return mLatencyReport;
1022 }
1023
Eric Erfanian2ca43182017-08-31 06:57:16 -07001024 public int getAnswerAndReleaseButtonDisplayedTimes() {
1025 return answerAndReleaseButtonDisplayedTimes;
1026 }
1027
1028 public void increaseAnswerAndReleaseButtonDisplayedTimes() {
1029 answerAndReleaseButtonDisplayedTimes++;
1030 }
1031
1032 public boolean getReleasedByAnsweringSecondCall() {
1033 return releasedByAnsweringSecondCall;
1034 }
1035
1036 public void setReleasedByAnsweringSecondCall(boolean releasedByAnsweringSecondCall) {
1037 this.releasedByAnsweringSecondCall = releasedByAnsweringSecondCall;
1038 }
1039
1040 public int getSecondCallWithoutAnswerAndReleasedButtonTimes() {
1041 return secondCallWithoutAnswerAndReleasedButtonTimes;
1042 }
1043
1044 public void increaseSecondCallWithoutAnswerAndReleasedButtonTimes() {
1045 secondCallWithoutAnswerAndReleasedButtonTimes++;
1046 }
1047
Eric Erfanian8369df02017-05-03 10:27:13 -07001048 @Nullable
1049 public EnrichedCallCapabilities getEnrichedCallCapabilities() {
1050 return mEnrichedCallCapabilities;
1051 }
1052
1053 public void setEnrichedCallCapabilities(
1054 @Nullable EnrichedCallCapabilities mEnrichedCallCapabilities) {
1055 this.mEnrichedCallCapabilities = mEnrichedCallCapabilities;
1056 }
1057
1058 @Nullable
1059 public Session getEnrichedCallSession() {
1060 return mEnrichedCallSession;
1061 }
1062
1063 public void setEnrichedCallSession(@Nullable Session mEnrichedCallSession) {
1064 this.mEnrichedCallSession = mEnrichedCallSession;
1065 }
1066
Eric Erfanianccca3152017-02-22 16:32:36 -08001067 public void unregisterCallback() {
1068 mTelecomCall.unregisterCallback(mTelecomCallCallback);
1069 }
1070
Eric Erfanianccca3152017-02-22 16:32:36 -08001071 public void phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault) {
1072 LogUtil.i(
1073 "DialerCall.phoneAccountSelected",
1074 "accountHandle: %s, setDefault: %b",
1075 accountHandle,
1076 setDefault);
1077 mTelecomCall.phoneAccountSelected(accountHandle, setDefault);
1078 }
1079
1080 public void disconnect() {
1081 LogUtil.i("DialerCall.disconnect", "");
1082 setState(DialerCall.State.DISCONNECTING);
1083 for (DialerCallListener listener : mListeners) {
1084 listener.onDialerCallUpdate();
1085 }
1086 mTelecomCall.disconnect();
1087 }
1088
1089 public void hold() {
1090 LogUtil.i("DialerCall.hold", "");
1091 mTelecomCall.hold();
1092 }
1093
1094 public void unhold() {
1095 LogUtil.i("DialerCall.unhold", "");
1096 mTelecomCall.unhold();
1097 }
1098
1099 public void splitFromConference() {
1100 LogUtil.i("DialerCall.splitFromConference", "");
1101 mTelecomCall.splitFromConference();
1102 }
1103
1104 public void answer(int videoState) {
1105 LogUtil.i("DialerCall.answer", "videoState: " + videoState);
1106 mTelecomCall.answer(videoState);
1107 }
1108
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001109 public void answer() {
1110 answer(mTelecomCall.getDetails().getVideoState());
1111 }
1112
Eric Erfanianccca3152017-02-22 16:32:36 -08001113 public void reject(boolean rejectWithMessage, String message) {
1114 LogUtil.i("DialerCall.reject", "");
1115 mTelecomCall.reject(rejectWithMessage, message);
1116 }
1117
1118 /** Return the string label to represent the call provider */
1119 public String getCallProviderLabel() {
1120 if (callProviderLabel == null) {
1121 PhoneAccount account = getPhoneAccount();
1122 if (account != null && !TextUtils.isEmpty(account.getLabel())) {
1123 List<PhoneAccountHandle> accounts =
1124 mContext.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts();
1125 if (accounts != null && accounts.size() > 1) {
1126 callProviderLabel = account.getLabel().toString();
1127 }
1128 }
1129 if (callProviderLabel == null) {
1130 callProviderLabel = "";
1131 }
1132 }
1133 return callProviderLabel;
1134 }
1135
1136 private PhoneAccount getPhoneAccount() {
1137 PhoneAccountHandle accountHandle = getAccountHandle();
1138 if (accountHandle == null) {
1139 return null;
1140 }
1141 return mContext.getSystemService(TelecomManager.class).getPhoneAccount(accountHandle);
1142 }
1143
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001144 public VideoTech getVideoTech() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001145 if (videoTech == null) {
1146 videoTech = mVideoTechManager.getVideoTech();
1147 }
1148 return videoTech;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001149 }
1150
Eric Erfanianccca3152017-02-22 16:32:36 -08001151 public String getCallbackNumber() {
1152 if (callbackNumber == null) {
1153 // Show the emergency callback number if either:
1154 // 1. This is an emergency call.
1155 // 2. The phone is in Emergency Callback Mode, which means we should show the callback
1156 // number.
1157 boolean showCallbackNumber = hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE);
1158
1159 if (isEmergencyCall() || showCallbackNumber) {
wangqi339b4f32017-09-18 14:32:09 -07001160 callbackNumber =
1161 mContext.getSystemService(TelecomManager.class).getLine1Number(getAccountHandle());
Eric Erfanianccca3152017-02-22 16:32:36 -08001162 }
1163
Eric Erfanianccca3152017-02-22 16:32:36 -08001164 if (callbackNumber == null) {
1165 callbackNumber = "";
1166 }
1167 }
1168 return callbackNumber;
1169 }
1170
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001171 @Override
1172 public void onVideoTechStateChanged() {
1173 update();
1174 }
1175
1176 @Override
1177 public void onSessionModificationStateChanged() {
1178 for (DialerCallListener listener : mListeners) {
1179 listener.onDialerCallSessionModificationStateChange();
1180 }
1181 }
1182
1183 @Override
1184 public void onCameraDimensionsChanged(int width, int height) {
1185 InCallVideoCallCallbackNotifier.getInstance().cameraDimensionsChanged(this, width, height);
1186 }
1187
1188 @Override
1189 public void onPeerDimensionsChanged(int width, int height) {
1190 InCallVideoCallCallbackNotifier.getInstance().peerDimensionsChanged(this, width, height);
1191 }
1192
1193 @Override
1194 public void onVideoUpgradeRequestReceived() {
1195 LogUtil.enterBlock("DialerCall.onVideoUpgradeRequestReceived");
1196
1197 for (DialerCallListener listener : mListeners) {
1198 listener.onDialerCallUpgradeToVideo();
1199 }
1200
1201 update();
Eric Erfanian8369df02017-05-03 10:27:13 -07001202
1203 Logger.get(mContext)
1204 .logCallImpression(
1205 DialerImpression.Type.VIDEO_CALL_REQUEST_RECEIVED, getUniqueCallId(), getTimeAddedMs());
1206 }
1207
1208 @Override
1209 public void onUpgradedToVideo(boolean switchToSpeaker) {
1210 LogUtil.enterBlock("DialerCall.onUpgradedToVideo");
1211
1212 if (!switchToSpeaker) {
1213 return;
1214 }
1215
1216 CallAudioState audioState = AudioModeProvider.getInstance().getAudioState();
1217
1218 if (0 != (CallAudioState.ROUTE_BLUETOOTH & audioState.getSupportedRouteMask())) {
1219 LogUtil.e(
1220 "DialerCall.onUpgradedToVideo",
1221 "toggling speakerphone not allowed when bluetooth supported.");
1222 return;
1223 }
1224
1225 if (audioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
1226 return;
1227 }
1228
1229 TelecomAdapter.getInstance().setAudioRoute(CallAudioState.ROUTE_SPEAKER);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001230 }
1231
Eric Erfanian2ca43182017-08-31 06:57:16 -07001232 @Override
1233 public void onCapabilitiesUpdated() {
1234 if (getNumber() == null) {
1235 return;
1236 }
1237 EnrichedCallCapabilities capabilities =
1238 EnrichedCallComponent.get(mContext).getEnrichedCallManager().getCapabilities(getNumber());
1239 if (capabilities != null) {
1240 setEnrichedCallCapabilities(capabilities);
1241 update();
1242 }
1243 }
1244
1245 @Override
1246 public void onEnrichedCallStateChanged() {
1247 updateEnrichedCallSession();
1248 }
1249
1250 @Override
1251 public void onImpressionLoggingNeeded(DialerImpression.Type impressionType) {
1252 Logger.get(mContext).logCallImpression(impressionType, getUniqueCallId(), getTimeAddedMs());
1253 }
1254
1255 private void updateEnrichedCallSession() {
1256 if (getNumber() == null) {
1257 return;
1258 }
1259 if (getEnrichedCallSession() != null) {
1260 // State changes to existing sessions are currently handled by the UI components (which have
1261 // their own listeners). Someday instead we could remove those and just call update() here and
1262 // have the usual onDialerCallUpdate update the UI.
1263 dispatchOnEnrichedCallSessionUpdate();
1264 return;
1265 }
1266
1267 EnrichedCallManager manager = EnrichedCallComponent.get(mContext).getEnrichedCallManager();
1268
1269 Filter filter =
1270 isIncoming()
1271 ? manager.createIncomingCallComposerFilter()
1272 : manager.createOutgoingCallComposerFilter();
1273
1274 Session session = manager.getSession(getUniqueCallId(), getNumber(), filter);
1275 if (session == null) {
1276 return;
1277 }
1278
1279 session.setUniqueDialerCallId(getUniqueCallId());
1280 setEnrichedCallSession(session);
1281
1282 LogUtil.i(
1283 "DialerCall.updateEnrichedCallSession",
1284 "setting session %d's dialer id to %s",
1285 session.getSessionId(),
1286 getUniqueCallId());
1287
1288 dispatchOnEnrichedCallSessionUpdate();
1289 }
1290
1291 private void dispatchOnEnrichedCallSessionUpdate() {
1292 for (DialerCallListener listener : mListeners) {
1293 listener.onEnrichedCallSessionUpdate();
1294 }
1295 }
1296
1297 void onRemovedFromCallList() {
1298 // Ensure we clean up when this call is removed.
1299 mVideoTechManager.dispatchRemovedFromCallList();
1300 }
1301
yueg457b3972017-09-18 15:11:47 -07001302 public boolean isImsReachable() {
1303 return isImsReachable;
1304 }
1305
1306 private void setImsReachable(boolean imsReachable) {
1307 isImsReachable = imsReachable;
1308 }
1309
Eric Erfanianccca3152017-02-22 16:32:36 -08001310 /**
1311 * Specifies whether a number is in the call history or not. {@link #CALL_HISTORY_STATUS_UNKNOWN}
1312 * means there is no result.
1313 */
1314 @IntDef({
1315 CALL_HISTORY_STATUS_UNKNOWN,
1316 CALL_HISTORY_STATUS_PRESENT,
1317 CALL_HISTORY_STATUS_NOT_PRESENT
1318 })
1319 @Retention(RetentionPolicy.SOURCE)
1320 public @interface CallHistoryStatus {}
1321
1322 /* Defines different states of this call */
1323 public static class State {
1324
1325 public static final int INVALID = 0;
1326 public static final int NEW = 1; /* The call is new. */
1327 public static final int IDLE = 2; /* The call is idle. Nothing active */
1328 public static final int ACTIVE = 3; /* There is an active call */
1329 public static final int INCOMING = 4; /* A normal incoming phone call */
1330 public static final int CALL_WAITING = 5; /* Incoming call while another is active */
1331 public static final int DIALING = 6; /* An outgoing call during dial phase */
1332 public static final int REDIALING = 7; /* Subsequent dialing attempt after a failure */
1333 public static final int ONHOLD = 8; /* An active phone call placed on hold */
1334 public static final int DISCONNECTING = 9; /* A call is being ended. */
1335 public static final int DISCONNECTED = 10; /* State after a call disconnects */
1336 public static final int CONFERENCED = 11; /* DialerCall part of a conference call */
1337 public static final int SELECT_PHONE_ACCOUNT = 12; /* Waiting for account selection */
1338 public static final int CONNECTING = 13; /* Waiting for Telecom broadcast to finish */
1339 public static final int BLOCKED = 14; /* The number was found on the block list */
1340 public static final int PULLING = 15; /* An external call being pulled to the device */
Eric Erfanian2ca43182017-08-31 06:57:16 -07001341 public static final int CALL_PENDING = 16; /* A call is pending on a long process to finish */
Eric Erfanianccca3152017-02-22 16:32:36 -08001342
1343 public static boolean isConnectingOrConnected(int state) {
1344 switch (state) {
1345 case ACTIVE:
1346 case INCOMING:
1347 case CALL_WAITING:
1348 case CONNECTING:
1349 case DIALING:
1350 case PULLING:
1351 case REDIALING:
1352 case ONHOLD:
1353 case CONFERENCED:
1354 return true;
1355 default:
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001356 return false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001357 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001358 }
1359
1360 public static boolean isDialing(int state) {
1361 return state == DIALING || state == PULLING || state == REDIALING;
1362 }
1363
1364 public static String toString(int state) {
1365 switch (state) {
1366 case INVALID:
1367 return "INVALID";
1368 case NEW:
1369 return "NEW";
1370 case IDLE:
1371 return "IDLE";
1372 case ACTIVE:
1373 return "ACTIVE";
1374 case INCOMING:
1375 return "INCOMING";
1376 case CALL_WAITING:
1377 return "CALL_WAITING";
1378 case DIALING:
1379 return "DIALING";
1380 case PULLING:
1381 return "PULLING";
1382 case REDIALING:
1383 return "REDIALING";
1384 case ONHOLD:
1385 return "ONHOLD";
1386 case DISCONNECTING:
1387 return "DISCONNECTING";
1388 case DISCONNECTED:
1389 return "DISCONNECTED";
1390 case CONFERENCED:
1391 return "CONFERENCED";
1392 case SELECT_PHONE_ACCOUNT:
1393 return "SELECT_PHONE_ACCOUNT";
1394 case CONNECTING:
1395 return "CONNECTING";
1396 case BLOCKED:
1397 return "BLOCKED";
1398 default:
1399 return "UNKNOWN";
1400 }
1401 }
1402 }
1403
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001404 /** Camera direction constants */
1405 public static class CameraDirection {
Eric Erfanianccca3152017-02-22 16:32:36 -08001406 public static final int CAMERA_DIRECTION_UNKNOWN = -1;
1407 public static final int CAMERA_DIRECTION_FRONT_FACING = CameraCharacteristics.LENS_FACING_FRONT;
1408 public static final int CAMERA_DIRECTION_BACK_FACING = CameraCharacteristics.LENS_FACING_BACK;
Eric Erfanianccca3152017-02-22 16:32:36 -08001409 }
1410
1411 /**
1412 * Tracks any state variables that is useful for logging. There is some amount of overlap with
1413 * existing call member variables, but this duplication helps to ensure that none of these logging
1414 * variables will interface with/and affect call logic.
1415 */
1416 public static class LogState {
1417
1418 public DisconnectCause disconnectCause;
1419 public boolean isIncoming = false;
Eric Erfanian8369df02017-05-03 10:27:13 -07001420 public ContactLookupResult.Type contactLookupResult =
1421 ContactLookupResult.Type.UNKNOWN_LOOKUP_RESULT_TYPE;
Eric Erfanianccca3152017-02-22 16:32:36 -08001422 public CallSpecificAppData callSpecificAppData;
1423 // If this was a conference call, the total number of calls involved in the conference.
1424 public int conferencedCalls = 0;
1425 public long duration = 0;
1426 public boolean isLogged = false;
1427
Eric Erfanian8369df02017-05-03 10:27:13 -07001428 private static String lookupToString(ContactLookupResult.Type lookupType) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001429 switch (lookupType) {
Eric Erfanian8369df02017-05-03 10:27:13 -07001430 case LOCAL_CONTACT:
Eric Erfanianccca3152017-02-22 16:32:36 -08001431 return "Local";
Eric Erfanian8369df02017-05-03 10:27:13 -07001432 case LOCAL_CACHE:
Eric Erfanianccca3152017-02-22 16:32:36 -08001433 return "Cache";
Eric Erfanian8369df02017-05-03 10:27:13 -07001434 case REMOTE:
Eric Erfanianccca3152017-02-22 16:32:36 -08001435 return "Remote";
Eric Erfanian8369df02017-05-03 10:27:13 -07001436 case EMERGENCY:
Eric Erfanianccca3152017-02-22 16:32:36 -08001437 return "Emergency";
Eric Erfanian8369df02017-05-03 10:27:13 -07001438 case VOICEMAIL:
Eric Erfanianccca3152017-02-22 16:32:36 -08001439 return "Voicemail";
1440 default:
1441 return "Not found";
1442 }
1443 }
1444
1445 private static String initiationToString(CallSpecificAppData callSpecificAppData) {
1446 if (callSpecificAppData == null) {
1447 return "null";
1448 }
Eric Erfanian8369df02017-05-03 10:27:13 -07001449 switch (callSpecificAppData.getCallInitiationType()) {
1450 case INCOMING_INITIATION:
Eric Erfanianccca3152017-02-22 16:32:36 -08001451 return "Incoming";
Eric Erfanian8369df02017-05-03 10:27:13 -07001452 case DIALPAD:
Eric Erfanianccca3152017-02-22 16:32:36 -08001453 return "Dialpad";
Eric Erfanian8369df02017-05-03 10:27:13 -07001454 case SPEED_DIAL:
Eric Erfanianccca3152017-02-22 16:32:36 -08001455 return "Speed Dial";
Eric Erfanian8369df02017-05-03 10:27:13 -07001456 case REMOTE_DIRECTORY:
Eric Erfanianccca3152017-02-22 16:32:36 -08001457 return "Remote Directory";
Eric Erfanian8369df02017-05-03 10:27:13 -07001458 case SMART_DIAL:
Eric Erfanianccca3152017-02-22 16:32:36 -08001459 return "Smart Dial";
Eric Erfanian8369df02017-05-03 10:27:13 -07001460 case REGULAR_SEARCH:
Eric Erfanianccca3152017-02-22 16:32:36 -08001461 return "Regular Search";
Eric Erfanian8369df02017-05-03 10:27:13 -07001462 case CALL_LOG:
Eric Erfanianccca3152017-02-22 16:32:36 -08001463 return "DialerCall Log";
Eric Erfanian8369df02017-05-03 10:27:13 -07001464 case CALL_LOG_FILTER:
Eric Erfanianccca3152017-02-22 16:32:36 -08001465 return "DialerCall Log Filter";
Eric Erfanian8369df02017-05-03 10:27:13 -07001466 case VOICEMAIL_LOG:
Eric Erfanianccca3152017-02-22 16:32:36 -08001467 return "Voicemail Log";
Eric Erfanian8369df02017-05-03 10:27:13 -07001468 case CALL_DETAILS:
Eric Erfanianccca3152017-02-22 16:32:36 -08001469 return "DialerCall Details";
Eric Erfanian8369df02017-05-03 10:27:13 -07001470 case QUICK_CONTACTS:
Eric Erfanianccca3152017-02-22 16:32:36 -08001471 return "Quick Contacts";
Eric Erfanian8369df02017-05-03 10:27:13 -07001472 case EXTERNAL_INITIATION:
Eric Erfanianccca3152017-02-22 16:32:36 -08001473 return "External";
Eric Erfanian8369df02017-05-03 10:27:13 -07001474 case LAUNCHER_SHORTCUT:
Eric Erfanianccca3152017-02-22 16:32:36 -08001475 return "Launcher Shortcut";
1476 default:
Eric Erfanian8369df02017-05-03 10:27:13 -07001477 return "Unknown: " + callSpecificAppData.getCallInitiationType();
Eric Erfanianccca3152017-02-22 16:32:36 -08001478 }
1479 }
1480
1481 @Override
1482 public String toString() {
1483 return String.format(
1484 Locale.US,
1485 "["
1486 + "%s, " // DisconnectCause toString already describes the object type
1487 + "isIncoming: %s, "
1488 + "contactLookup: %s, "
1489 + "callInitiation: %s, "
1490 + "duration: %s"
1491 + "]",
1492 disconnectCause,
1493 isIncoming,
1494 lookupToString(contactLookupResult),
1495 initiationToString(callSpecificAppData),
1496 duration);
1497 }
1498 }
1499
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001500 private static class VideoTechManager {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001501 private final Context context;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001502 private final EmptyVideoTech emptyVideoTech = new EmptyVideoTech();
Eric Erfanian90508232017-03-24 09:31:16 -07001503 private final List<VideoTech> videoTechs;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001504 private VideoTech savedTech;
1505
1506 VideoTechManager(DialerCall call) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001507 this.context = call.mContext;
1508
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001509 String phoneNumber = call.getNumber();
Eric Erfaniand8046e52017-04-06 09:41:50 -07001510 phoneNumber = phoneNumber != null ? phoneNumber : "";
Eric Erfanian2ca43182017-08-31 06:57:16 -07001511 phoneNumber = phoneNumber.replaceAll("[^+0-9]", "");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001512
1513 // Insert order here determines the priority of that video tech option
Eric Erfanian8369df02017-05-03 10:27:13 -07001514 videoTechs = new ArrayList<>();
yueg457b3972017-09-18 15:11:47 -07001515
1516 ImsVideoTech imsVideoTech =
1517 new ImsVideoTech(Logger.get(call.mContext), call, call.mTelecomCall);
1518 call.setImsReachable(imsVideoTech.isAvailable(context));
1519 videoTechs.add(imsVideoTech);
Eric Erfanian90508232017-03-24 09:31:16 -07001520
1521 VideoTech rcsVideoTech =
1522 EnrichedCallComponent.get(call.mContext)
1523 .getRcsVideoShareFactory()
1524 .newRcsVideoShare(
1525 EnrichedCallComponent.get(call.mContext).getEnrichedCallManager(),
1526 call,
Eric Erfaniand8046e52017-04-06 09:41:50 -07001527 phoneNumber);
Eric Erfanian90508232017-03-24 09:31:16 -07001528 if (rcsVideoTech != null) {
1529 videoTechs.add(rcsVideoTech);
1530 }
Eric Erfaniand8046e52017-04-06 09:41:50 -07001531
1532 videoTechs.add(
1533 new LightbringerTech(
Eric Erfanian2ca43182017-08-31 06:57:16 -07001534 LightbringerComponent.get(call.mContext).getLightbringer(),
1535 call,
1536 call.mTelecomCall,
1537 phoneNumber));
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001538 }
1539
1540 VideoTech getVideoTech() {
1541 if (savedTech != null) {
1542 return savedTech;
1543 }
1544
1545 for (VideoTech tech : videoTechs) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001546 if (tech.isAvailable(context)) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001547 // Remember the first VideoTech that becomes available and always use it
1548 savedTech = tech;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001549 savedTech.becomePrimary();
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001550 return savedTech;
1551 }
1552 }
1553
1554 return emptyVideoTech;
1555 }
1556
1557 void dispatchCallStateChanged(int newState) {
1558 for (VideoTech videoTech : videoTechs) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001559 videoTech.onCallStateChanged(context, newState);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001560 }
1561 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001562
1563 void dispatchRemovedFromCallList() {
1564 for (VideoTech videoTech : videoTechs) {
1565 videoTech.onRemovedFromCallList();
1566 }
1567 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001568 }
1569
Eric Erfanianccca3152017-02-22 16:32:36 -08001570 /** Called when canned text responses have been loaded. */
1571 public interface CannedTextResponsesLoadedListener {
1572 void onCannedTextResponsesLoaded(DialerCall call);
1573 }
1574}