blob: 9f8992575dc2e6df790ec0e210808e9d0d181906 [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
Eric Erfanianccca3152017-02-22 16:32:36 -0800156 public static String getNumberFromHandle(Uri handle) {
157 return handle == null ? "" : handle.getSchemeSpecificPart();
158 }
159
160 /**
161 * Whether the call is put on hold by remote party. This is different than the {@link
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700162 * State#ONHOLD} state which indicates that the call is being held locally on the device.
Eric Erfanianccca3152017-02-22 16:32:36 -0800163 */
164 private boolean isRemotelyHeld;
165
Eric Erfanian2ca43182017-08-31 06:57:16 -0700166 /** Indicates whether this call is currently in the process of being merged into a conference. */
167 private boolean isMergeInProcess;
168
Eric Erfanianccca3152017-02-22 16:32:36 -0800169 /**
170 * Indicates whether the phone account associated with this call supports specifying a call
171 * subject.
172 */
173 private boolean mIsCallSubjectSupported;
174
175 private final Call.Callback mTelecomCallCallback =
176 new Call.Callback() {
177 @Override
178 public void onStateChanged(Call call, int newState) {
179 LogUtil.v("TelecomCallCallback.onStateChanged", "call=" + call + " newState=" + newState);
180 update();
181 }
182
183 @Override
184 public void onParentChanged(Call call, Call newParent) {
185 LogUtil.v(
186 "TelecomCallCallback.onParentChanged", "call=" + call + " newParent=" + newParent);
187 update();
188 }
189
190 @Override
191 public void onChildrenChanged(Call call, List<Call> children) {
192 update();
193 }
194
195 @Override
196 public void onDetailsChanged(Call call, Call.Details details) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700197 LogUtil.v(
198 "TelecomCallCallback.onDetailsChanged", " call=" + call + " details=" + details);
Eric Erfanianccca3152017-02-22 16:32:36 -0800199 update();
200 }
201
202 @Override
203 public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {
204 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700205 "TelecomCallCallback.onCannedTextResponsesLoaded",
Eric Erfanianccca3152017-02-22 16:32:36 -0800206 "call=" + call + " cannedTextResponses=" + cannedTextResponses);
207 for (CannedTextResponsesLoadedListener listener : mCannedTextResponsesLoadedListeners) {
208 listener.onCannedTextResponsesLoaded(DialerCall.this);
209 }
210 }
211
212 @Override
213 public void onPostDialWait(Call call, String remainingPostDialSequence) {
214 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700215 "TelecomCallCallback.onPostDialWait",
Eric Erfanianccca3152017-02-22 16:32:36 -0800216 "call=" + call + " remainingPostDialSequence=" + remainingPostDialSequence);
217 update();
218 }
219
220 @Override
221 public void onVideoCallChanged(Call call, VideoCall videoCall) {
222 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700223 "TelecomCallCallback.onVideoCallChanged", "call=" + call + " videoCall=" + videoCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800224 update();
225 }
226
227 @Override
228 public void onCallDestroyed(Call call) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700229 LogUtil.v("TelecomCallCallback.onCallDestroyed", "call=" + call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700230 unregisterCallback();
Eric Erfanianccca3152017-02-22 16:32:36 -0800231 }
232
233 @Override
234 public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
235 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700236 "TelecomCallCallback.onConferenceableCallsChanged",
Eric Erfanianccca3152017-02-22 16:32:36 -0800237 "call %s, conferenceable calls: %d",
238 call,
239 conferenceableCalls.size());
240 update();
241 }
242
243 @Override
244 public void onConnectionEvent(android.telecom.Call call, String event, Bundle extras) {
245 LogUtil.v(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700246 "TelecomCallCallback.onConnectionEvent",
Eric Erfanianccca3152017-02-22 16:32:36 -0800247 "Call: " + call + ", Event: " + event + ", Extras: " + extras);
248 switch (event) {
249 // The Previous attempt to Merge two calls together has failed in Telecom. We must
250 // now update the UI to possibly re-enable the Merge button based on the number of
251 // currently conferenceable calls available or Connection Capabilities.
252 case android.telecom.Connection.EVENT_CALL_MERGE_FAILED:
253 update();
254 break;
255 case TelephonyManagerCompat.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE:
256 notifyWiFiToLteHandover();
257 break;
258 case TelephonyManagerCompat.EVENT_HANDOVER_TO_WIFI_FAILED:
259 notifyHandoverToWifiFailed();
260 break;
261 case TelephonyManagerCompat.EVENT_CALL_REMOTELY_HELD:
262 isRemotelyHeld = true;
263 update();
264 break;
265 case TelephonyManagerCompat.EVENT_CALL_REMOTELY_UNHELD:
266 isRemotelyHeld = false;
267 update();
268 break;
Eric Erfanianc857f902017-05-15 14:05:33 -0700269 case TelephonyManagerCompat.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC:
270 notifyInternationalCallOnWifi();
271 break;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700272 case TelephonyManagerCompat.EVENT_MERGE_START:
273 LogUtil.i("DialerCall.onConnectionEvent", "merge start");
274 isMergeInProcess = true;
275 break;
276 case TelephonyManagerCompat.EVENT_MERGE_COMPLETE:
277 LogUtil.i("DialerCall.onConnectionEvent", "merge complete");
278 isMergeInProcess = false;
279 break;
Eric Erfanianccca3152017-02-22 16:32:36 -0800280 default:
281 break;
282 }
283 }
284 };
Eric Erfanianc857f902017-05-15 14:05:33 -0700285
Eric Erfanianccca3152017-02-22 16:32:36 -0800286 private long mTimeAddedMs;
287
288 public DialerCall(
289 Context context,
290 DialerCallDelegate dialerCallDelegate,
291 Call telecomCall,
292 LatencyReport latencyReport,
293 boolean registerCallback) {
294 Assert.isNotNull(context);
295 mContext = context;
296 mDialerCallDelegate = dialerCallDelegate;
297 mTelecomCall = telecomCall;
298 mLatencyReport = latencyReport;
299 mId = ID_PREFIX + Integer.toString(sIdCounter++);
300
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700301 // Must be after assigning mTelecomCall
302 mVideoTechManager = new VideoTechManager(this);
303
304 updateFromTelecomCall();
Eric Erfanianc857f902017-05-15 14:05:33 -0700305 if (isHiddenNumber() && TextUtils.isEmpty(getNumber())) {
306 mHiddenId = ++sHiddenCounter;
307 } else {
308 mHiddenId = 0;
309 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800310
311 if (registerCallback) {
312 mTelecomCall.registerCallback(mTelecomCallCallback);
313 }
314
315 mTimeAddedMs = System.currentTimeMillis();
316 parseCallSpecificAppData();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700317
318 updateEnrichedCallSession();
Eric Erfanianccca3152017-02-22 16:32:36 -0800319 }
320
321 private static int translateState(int state) {
322 switch (state) {
323 case Call.STATE_NEW:
324 case Call.STATE_CONNECTING:
325 return DialerCall.State.CONNECTING;
326 case Call.STATE_SELECT_PHONE_ACCOUNT:
327 return DialerCall.State.SELECT_PHONE_ACCOUNT;
328 case Call.STATE_DIALING:
329 return DialerCall.State.DIALING;
330 case Call.STATE_PULLING_CALL:
331 return DialerCall.State.PULLING;
332 case Call.STATE_RINGING:
333 return DialerCall.State.INCOMING;
334 case Call.STATE_ACTIVE:
335 return DialerCall.State.ACTIVE;
336 case Call.STATE_HOLDING:
337 return DialerCall.State.ONHOLD;
338 case Call.STATE_DISCONNECTED:
339 return DialerCall.State.DISCONNECTED;
340 case Call.STATE_DISCONNECTING:
341 return DialerCall.State.DISCONNECTING;
342 default:
343 return DialerCall.State.INVALID;
344 }
345 }
346
347 public static boolean areSame(DialerCall call1, DialerCall call2) {
348 if (call1 == null && call2 == null) {
349 return true;
350 } else if (call1 == null || call2 == null) {
351 return false;
352 }
353
354 // otherwise compare call Ids
355 return call1.getId().equals(call2.getId());
356 }
357
358 public static boolean areSameNumber(DialerCall call1, DialerCall call2) {
359 if (call1 == null && call2 == null) {
360 return true;
361 } else if (call1 == null || call2 == null) {
362 return false;
363 }
364
365 // otherwise compare call Numbers
366 return TextUtils.equals(call1.getNumber(), call2.getNumber());
367 }
368
369 public void addListener(DialerCallListener listener) {
370 Assert.isMainThread();
371 mListeners.add(listener);
372 }
373
374 public void removeListener(DialerCallListener listener) {
375 Assert.isMainThread();
376 mListeners.remove(listener);
377 }
378
379 public void addCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
380 Assert.isMainThread();
381 mCannedTextResponsesLoadedListeners.add(listener);
382 }
383
384 public void removeCannedTextResponsesLoadedListener(CannedTextResponsesLoadedListener listener) {
385 Assert.isMainThread();
386 mCannedTextResponsesLoadedListeners.remove(listener);
387 }
388
389 public void notifyWiFiToLteHandover() {
390 LogUtil.i("DialerCall.notifyWiFiToLteHandover", "");
391 for (DialerCallListener listener : mListeners) {
392 listener.onWiFiToLteHandover();
393 }
394 }
395
396 public void notifyHandoverToWifiFailed() {
397 LogUtil.i("DialerCall.notifyHandoverToWifiFailed", "");
398 for (DialerCallListener listener : mListeners) {
399 listener.onHandoverToWifiFailure();
400 }
401 }
402
Eric Erfanianc857f902017-05-15 14:05:33 -0700403 public void notifyInternationalCallOnWifi() {
404 LogUtil.enterBlock("DialerCall.notifyInternationalCallOnWifi");
405 for (DialerCallListener dialerCallListener : mListeners) {
406 dialerCallListener.onInternationalCallOnWifi();
407 }
408 }
409
Eric Erfanianccca3152017-02-22 16:32:36 -0800410 /* package-private */ Call getTelecomCall() {
411 return mTelecomCall;
412 }
413
414 public StatusHints getStatusHints() {
415 return mTelecomCall.getDetails().getStatusHints();
416 }
417
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700418 public int getCameraDir() {
419 return mCameraDirection;
420 }
421
422 public void setCameraDir(int cameraDir) {
423 if (cameraDir == CameraDirection.CAMERA_DIRECTION_FRONT_FACING
424 || cameraDir == CameraDirection.CAMERA_DIRECTION_BACK_FACING) {
425 mCameraDirection = cameraDir;
426 } else {
427 mCameraDirection = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
428 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800429 }
430
Eric Erfanian2ca43182017-08-31 06:57:16 -0700431 public boolean wasParentCall() {
432 return mLogState.conferencedCalls != 0;
433 }
434
Eric Erfanianccca3152017-02-22 16:32:36 -0800435 private void update() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700436 Trace.beginSection("DialerCall.update");
Eric Erfanianccca3152017-02-22 16:32:36 -0800437 int oldState = getState();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700438 // Clear any cache here that could potentially change on update.
439 videoTech = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800440 // We want to potentially register a video call callback here.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700441 updateFromTelecomCall();
Eric Erfanianccca3152017-02-22 16:32:36 -0800442 if (oldState != getState() && getState() == DialerCall.State.DISCONNECTED) {
443 for (DialerCallListener listener : mListeners) {
444 listener.onDialerCallDisconnect();
445 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700446 EnrichedCallComponent.get(mContext)
447 .getEnrichedCallManager()
448 .unregisterCapabilitiesListener(this);
449 EnrichedCallComponent.get(mContext)
450 .getEnrichedCallManager()
451 .unregisterStateChangedListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800452 } else {
453 for (DialerCallListener listener : mListeners) {
454 listener.onDialerCallUpdate();
455 }
456 }
457 Trace.endSection();
458 }
459
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700460 private void updateFromTelecomCall() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700461 Trace.beginSection("DialerCall.updateFromTelecomCall");
Eric Erfanianccca3152017-02-22 16:32:36 -0800462 LogUtil.v("DialerCall.updateFromTelecomCall", mTelecomCall.toString());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700463
464 mVideoTechManager.dispatchCallStateChanged(mTelecomCall.getState());
465
Eric Erfanianccca3152017-02-22 16:32:36 -0800466 final int translatedState = translateState(mTelecomCall.getState());
467 if (mState != State.BLOCKED) {
468 setState(translatedState);
469 setDisconnectCause(mTelecomCall.getDetails().getDisconnectCause());
Eric Erfanianccca3152017-02-22 16:32:36 -0800470 }
471
472 mChildCallIds.clear();
473 final int numChildCalls = mTelecomCall.getChildren().size();
474 for (int i = 0; i < numChildCalls; i++) {
475 mChildCallIds.add(
476 mDialerCallDelegate
477 .getDialerCallFromTelecomCall(mTelecomCall.getChildren().get(i))
478 .getId());
479 }
480
481 // The number of conferenced calls can change over the course of the call, so use the
482 // maximum number of conferenced child calls as the metric for conference call usage.
483 mLogState.conferencedCalls = Math.max(numChildCalls, mLogState.conferencedCalls);
484
485 updateFromCallExtras(mTelecomCall.getDetails().getExtras());
486
487 // If the handle of the call has changed, update state for the call determining if it is an
488 // emergency call.
489 Uri newHandle = mTelecomCall.getDetails().getHandle();
490 if (!Objects.equals(mHandle, newHandle)) {
491 mHandle = newHandle;
492 updateEmergencyCallState();
493 }
494
495 // If the phone account handle of the call is set, cache capability bit indicating whether
496 // the phone account supports call subjects.
497 PhoneAccountHandle newPhoneAccountHandle = mTelecomCall.getDetails().getAccountHandle();
498 if (!Objects.equals(mPhoneAccountHandle, newPhoneAccountHandle)) {
499 mPhoneAccountHandle = newPhoneAccountHandle;
500
501 if (mPhoneAccountHandle != null) {
502 PhoneAccount phoneAccount =
503 mContext.getSystemService(TelecomManager.class).getPhoneAccount(mPhoneAccountHandle);
504 if (phoneAccount != null) {
505 mIsCallSubjectSupported =
506 phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT);
507 }
508 }
509 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700510 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800511 }
512
513 /**
514 * Tests corruption of the {@code callExtras} bundle by calling {@link
515 * Bundle#containsKey(String)}. If the bundle is corrupted a {@link IllegalArgumentException} will
516 * be thrown and caught by this function.
517 *
518 * @param callExtras the bundle to verify
519 * @return {@code true} if the bundle is corrupted, {@code false} otherwise.
520 */
521 protected boolean areCallExtrasCorrupted(Bundle callExtras) {
522 /**
523 * There's currently a bug in Telephony service (b/25613098) that could corrupt the extras
524 * bundle, resulting in a IllegalArgumentException while validating data under {@link
525 * Bundle#containsKey(String)}.
526 */
527 try {
528 callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS);
529 return false;
530 } catch (IllegalArgumentException e) {
531 LogUtil.e(
532 "DialerCall.areCallExtrasCorrupted", "callExtras is corrupted, ignoring exception", e);
533 return true;
534 }
535 }
536
537 protected void updateFromCallExtras(Bundle callExtras) {
538 if (callExtras == null || areCallExtrasCorrupted(callExtras)) {
539 /**
540 * If the bundle is corrupted, abandon information update as a work around. These are not
541 * critical for the dialer to function.
542 */
543 return;
544 }
545 // Check for a change in the child address and notify any listeners.
546 if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) {
547 String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS);
548 if (!Objects.equals(childNumber, mChildNumber)) {
549 mChildNumber = childNumber;
550 for (DialerCallListener listener : mListeners) {
551 listener.onDialerCallChildNumberChange();
552 }
553 }
554 }
555
556 // Last forwarded number comes in as an array of strings. We want to choose the
557 // last item in the array. The forwarding numbers arrive independently of when the
558 // call is originally set up, so we need to notify the the UI of the change.
559 if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) {
560 ArrayList<String> lastForwardedNumbers =
561 callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER);
562
563 if (lastForwardedNumbers != null) {
564 String lastForwardedNumber = null;
565 if (!lastForwardedNumbers.isEmpty()) {
566 lastForwardedNumber = lastForwardedNumbers.get(lastForwardedNumbers.size() - 1);
567 }
568
569 if (!Objects.equals(lastForwardedNumber, mLastForwardedNumber)) {
570 mLastForwardedNumber = lastForwardedNumber;
571 for (DialerCallListener listener : mListeners) {
572 listener.onDialerCallLastForwardedNumberChange();
573 }
574 }
575 }
576 }
577
578 // DialerCall subject is present in the extras at the start of call, so we do not need to
579 // notify any other listeners of this.
580 if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) {
581 String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT);
582 if (!Objects.equals(mCallSubject, callSubject)) {
583 mCallSubject = callSubject;
584 }
585 }
586 }
587
Eric Erfanianccca3152017-02-22 16:32:36 -0800588 public String getId() {
589 return mId;
590 }
591
Eric Erfanianc857f902017-05-15 14:05:33 -0700592 /**
593 * @return name appended with a number if the number is restricted/unknown and the user has
594 * received more than one restricted/unknown call.
595 */
596 @Nullable
597 public String updateNameIfRestricted(@Nullable String name) {
598 if (name != null && isHiddenNumber() && mHiddenId != 0 && sHiddenCounter > 1) {
599 return mContext.getString(R.string.unknown_counter, name, mHiddenId);
600 }
601 return name;
602 }
603
604 public static void clearRestrictedCount() {
605 sHiddenCounter = 0;
606 }
607
608 private boolean isHiddenNumber() {
609 return getNumberPresentation() == TelecomManager.PRESENTATION_RESTRICTED
610 || getNumberPresentation() == TelecomManager.PRESENTATION_UNKNOWN;
611 }
612
Eric Erfanianccca3152017-02-22 16:32:36 -0800613 public boolean hasShownWiFiToLteHandoverToast() {
614 return hasShownWiFiToLteHandoverToast;
615 }
616
617 public void setHasShownWiFiToLteHandoverToast() {
618 hasShownWiFiToLteHandoverToast = true;
619 }
620
621 public boolean showWifiHandoverAlertAsToast() {
622 return doNotShowDialogForHandoffToWifiFailure;
623 }
624
625 public void setDoNotShowDialogForHandoffToWifiFailure(boolean bool) {
626 doNotShowDialogForHandoffToWifiFailure = bool;
627 }
628
629 public long getTimeAddedMs() {
630 return mTimeAddedMs;
631 }
632
633 @Nullable
634 public String getNumber() {
635 return TelecomCallUtil.getNumber(mTelecomCall);
636 }
637
638 public void blockCall() {
639 mTelecomCall.reject(false, null);
640 setState(State.BLOCKED);
641 }
642
643 @Nullable
644 public Uri getHandle() {
645 return mTelecomCall == null ? null : mTelecomCall.getDetails().getHandle();
646 }
647
648 public boolean isEmergencyCall() {
649 return mIsEmergencyCall;
650 }
651
652 public boolean isPotentialEmergencyCallback() {
653 // The property PROPERTY_EMERGENCY_CALLBACK_MODE is only set for CDMA calls when the system
654 // is actually in emergency callback mode (ie data is disabled).
655 if (hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE)) {
656 return true;
657 }
658 // We want to treat any incoming call that arrives a short time after an outgoing emergency call
659 // as a potential emergency callback.
660 if (getExtras() != null
661 && getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0)
662 > 0) {
663 long lastEmergencyCallMillis =
664 getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0);
665 if (isInEmergencyCallbackWindow(lastEmergencyCallMillis)) {
666 return true;
667 }
668 }
669 return false;
670 }
671
672 boolean isInEmergencyCallbackWindow(long timestampMillis) {
673 long emergencyCallbackWindowMillis =
674 ConfigProviderBindings.get(mContext)
675 .getLong(CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS, TimeUnit.MINUTES.toMillis(5));
676 return System.currentTimeMillis() - timestampMillis < emergencyCallbackWindowMillis;
677 }
678
679 public int getState() {
680 if (mTelecomCall != null && mTelecomCall.getParent() != null) {
681 return State.CONFERENCED;
682 } else {
683 return mState;
684 }
685 }
686
687 public void setState(int state) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700688 if (state == State.INCOMING) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800689 mLogState.isIncoming = true;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700690 } else if (state == State.DISCONNECTED) {
691 long newDuration =
Eric Erfanianccca3152017-02-22 16:32:36 -0800692 getConnectTimeMillis() == 0 ? 0 : System.currentTimeMillis() - getConnectTimeMillis();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700693 if (mState != state) {
694 mLogState.duration = newDuration;
695 } else {
696 LogUtil.i(
697 "DialerCall.setState",
698 "ignoring state transition from DISCONNECTED to DISCONNECTED."
699 + " Duration would have changed from %s to %s",
700 mLogState.duration,
701 newDuration);
702 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800703 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700704 mState = state;
Eric Erfanianccca3152017-02-22 16:32:36 -0800705 }
706
707 public int getNumberPresentation() {
708 return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getHandlePresentation();
709 }
710
711 public int getCnapNamePresentation() {
712 return mTelecomCall == null ? -1 : mTelecomCall.getDetails().getCallerDisplayNamePresentation();
713 }
714
715 @Nullable
716 public String getCnapName() {
717 return mTelecomCall == null ? null : getTelecomCall().getDetails().getCallerDisplayName();
718 }
719
720 public Bundle getIntentExtras() {
721 return mTelecomCall.getDetails().getIntentExtras();
722 }
723
724 @Nullable
725 public Bundle getExtras() {
726 return mTelecomCall == null ? null : mTelecomCall.getDetails().getExtras();
727 }
728
729 /** @return The child number for the call, or {@code null} if none specified. */
730 public String getChildNumber() {
731 return mChildNumber;
732 }
733
734 /** @return The last forwarded number for the call, or {@code null} if none specified. */
735 public String getLastForwardedNumber() {
736 return mLastForwardedNumber;
737 }
738
739 /** @return The call subject, or {@code null} if none specified. */
740 public String getCallSubject() {
741 return mCallSubject;
742 }
743
744 /**
745 * @return {@code true} if the call's phone account supports call subjects, {@code false}
746 * otherwise.
747 */
748 public boolean isCallSubjectSupported() {
749 return mIsCallSubjectSupported;
750 }
751
752 /** Returns call disconnect cause, defined by {@link DisconnectCause}. */
753 public DisconnectCause getDisconnectCause() {
754 if (mState == State.DISCONNECTED || mState == State.IDLE) {
755 return mDisconnectCause;
756 }
757
758 return new DisconnectCause(DisconnectCause.UNKNOWN);
759 }
760
761 public void setDisconnectCause(DisconnectCause disconnectCause) {
762 mDisconnectCause = disconnectCause;
763 mLogState.disconnectCause = mDisconnectCause;
764 }
765
766 /** Returns the possible text message responses. */
767 public List<String> getCannedSmsResponses() {
768 return mTelecomCall.getCannedTextResponses();
769 }
770
771 /** Checks if the call supports the given set of capabilities supplied as a bit mask. */
772 public boolean can(int capabilities) {
773 int supportedCapabilities = mTelecomCall.getDetails().getCallCapabilities();
774
775 if ((capabilities & Call.Details.CAPABILITY_MERGE_CONFERENCE) != 0) {
776 // We allow you to merge if the capabilities allow it or if it is a call with
777 // conferenceable calls.
778 if (mTelecomCall.getConferenceableCalls().isEmpty()
779 && ((Call.Details.CAPABILITY_MERGE_CONFERENCE & supportedCapabilities) == 0)) {
780 // Cannot merge calls if there are no calls to merge with.
781 return false;
782 }
783 capabilities &= ~Call.Details.CAPABILITY_MERGE_CONFERENCE;
784 }
785 return (capabilities == (capabilities & supportedCapabilities));
786 }
787
788 public boolean hasProperty(int property) {
789 return mTelecomCall.getDetails().hasProperty(property);
790 }
791
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700792 @NonNull
Eric Erfanianccca3152017-02-22 16:32:36 -0800793 public String getUniqueCallId() {
794 return uniqueCallId;
795 }
796
797 /** Gets the time when the call first became active. */
798 public long getConnectTimeMillis() {
799 return mTelecomCall.getDetails().getConnectTimeMillis();
800 }
801
802 public boolean isConferenceCall() {
803 return hasProperty(Call.Details.PROPERTY_CONFERENCE);
804 }
805
806 @Nullable
807 public GatewayInfo getGatewayInfo() {
808 return mTelecomCall == null ? null : mTelecomCall.getDetails().getGatewayInfo();
809 }
810
811 @Nullable
812 public PhoneAccountHandle getAccountHandle() {
813 return mTelecomCall == null ? null : mTelecomCall.getDetails().getAccountHandle();
814 }
815
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700816 /** @return The {@link VideoCall} instance associated with the {@link Call}. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800817 public VideoCall getVideoCall() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700818 return mTelecomCall == null ? null : mTelecomCall.getVideoCall();
Eric Erfanianccca3152017-02-22 16:32:36 -0800819 }
820
821 public List<String> getChildCallIds() {
822 return mChildCallIds;
823 }
824
825 public String getParentId() {
826 Call parentCall = mTelecomCall.getParent();
827 if (parentCall != null) {
828 return mDialerCallDelegate.getDialerCallFromTelecomCall(parentCall).getId();
829 }
830 return null;
831 }
832
833 public int getVideoState() {
834 return mTelecomCall.getDetails().getVideoState();
835 }
836
837 public boolean isVideoCall() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700838 return getVideoTech().isTransmittingOrReceiving();
839 }
840
841 public boolean hasReceivedVideoUpgradeRequest() {
842 return VideoUtils.hasReceivedVideoUpgradeRequest(getVideoTech().getSessionModificationState());
843 }
844
845 public boolean hasSentVideoUpgradeRequest() {
846 return VideoUtils.hasSentVideoUpgradeRequest(getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800847 }
848
849 /**
850 * Determines if the call handle is an emergency number or not and caches the result to avoid
851 * repeated calls to isEmergencyNumber.
852 */
853 private void updateEmergencyCallState() {
854 mIsEmergencyCall = TelecomCallUtil.isEmergencyCall(mTelecomCall);
855 }
856
Eric Erfanianccca3152017-02-22 16:32:36 -0800857 public LogState getLogState() {
858 return mLogState;
859 }
860
861 /**
862 * Determines if the call is an external call.
863 *
864 * <p>An external call is one which does not exist locally for the {@link
865 * android.telecom.ConnectionService} it is associated with.
866 *
867 * <p>External calls are only supported in N and higher.
868 *
869 * @return {@code true} if the call is an external call, {@code false} otherwise.
870 */
871 public boolean isExternalCall() {
872 return VERSION.SDK_INT >= VERSION_CODES.N
873 && hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL);
874 }
875
876 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800877 * Determines if answering this call will cause an ongoing video call to be dropped.
878 *
879 * @return {@code true} if answering this call will drop an ongoing video call, {@code false}
880 * otherwise.
881 */
882 public boolean answeringDisconnectsForegroundVideoCall() {
883 Bundle extras = getExtras();
884 if (extras == null
885 || !extras.containsKey(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL)) {
886 return false;
887 }
888 return extras.getBoolean(CallCompat.Details.EXTRA_ANSWERING_DROPS_FOREGROUND_CALL);
889 }
890
891 private void parseCallSpecificAppData() {
892 if (isExternalCall()) {
893 return;
894 }
895
896 mLogState.callSpecificAppData = CallIntentParser.getCallSpecificAppData(getIntentExtras());
897 if (mLogState.callSpecificAppData == null) {
Eric Erfanian8369df02017-05-03 10:27:13 -0700898
899 mLogState.callSpecificAppData =
900 CallSpecificAppData.newBuilder()
901 .setCallInitiationType(CallInitiationType.Type.EXTERNAL_INITIATION)
902 .build();
Eric Erfanianccca3152017-02-22 16:32:36 -0800903 }
904 if (getState() == State.INCOMING) {
Eric Erfanian8369df02017-05-03 10:27:13 -0700905 mLogState.callSpecificAppData =
906 mLogState
907 .callSpecificAppData
908 .toBuilder()
909 .setCallInitiationType(CallInitiationType.Type.INCOMING_INITIATION)
910 .build();
Eric Erfanianccca3152017-02-22 16:32:36 -0800911 }
912 }
913
914 @Override
915 public String toString() {
916 if (mTelecomCall == null) {
917 // This should happen only in testing since otherwise we would never have a null
918 // Telecom call.
919 return String.valueOf(mId);
920 }
921
922 return String.format(
923 Locale.US,
924 "[%s, %s, %s, %s, children:%s, parent:%s, "
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700925 + "conferenceable:%s, videoState:%s, mSessionModificationState:%d, CameraDir:%s]",
Eric Erfanianccca3152017-02-22 16:32:36 -0800926 mId,
927 State.toString(getState()),
928 Details.capabilitiesToString(mTelecomCall.getDetails().getCallCapabilities()),
929 Details.propertiesToString(mTelecomCall.getDetails().getCallProperties()),
930 mChildCallIds,
931 getParentId(),
932 this.mTelecomCall.getConferenceableCalls(),
933 VideoProfile.videoStateToString(mTelecomCall.getDetails().getVideoState()),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700934 getVideoTech().getSessionModificationState(),
935 getCameraDir());
Eric Erfanianccca3152017-02-22 16:32:36 -0800936 }
937
938 public String toSimpleString() {
939 return super.toString();
940 }
941
942 @CallHistoryStatus
943 public int getCallHistoryStatus() {
944 return mCallHistoryStatus;
945 }
946
947 public void setCallHistoryStatus(@CallHistoryStatus int callHistoryStatus) {
948 mCallHistoryStatus = callHistoryStatus;
949 }
950
951 public boolean didShowCameraPermission() {
952 return didShowCameraPermission;
953 }
954
955 public void setDidShowCameraPermission(boolean didShow) {
956 didShowCameraPermission = didShow;
957 }
958
959 public boolean isInGlobalSpamList() {
960 return isInGlobalSpamList;
961 }
962
963 public void setIsInGlobalSpamList(boolean inSpamList) {
964 isInGlobalSpamList = inSpamList;
965 }
966
967 public boolean isInUserSpamList() {
968 return isInUserSpamList;
969 }
970
971 public void setIsInUserSpamList(boolean inSpamList) {
972 isInUserSpamList = inSpamList;
973 }
974
975 public boolean isInUserWhiteList() {
976 return isInUserWhiteList;
977 }
978
979 public void setIsInUserWhiteList(boolean inWhiteList) {
980 isInUserWhiteList = inWhiteList;
981 }
982
983 public boolean isSpam() {
984 return mIsSpam;
985 }
986
987 public void setSpam(boolean isSpam) {
988 mIsSpam = isSpam;
989 }
990
991 public boolean isBlocked() {
992 return mIsBlocked;
993 }
994
995 public void setBlockedStatus(boolean isBlocked) {
996 mIsBlocked = isBlocked;
997 }
998
999 public boolean isRemotelyHeld() {
1000 return isRemotelyHeld;
1001 }
1002
Eric Erfanian2ca43182017-08-31 06:57:16 -07001003 public boolean isMergeInProcess() {
1004 return isMergeInProcess;
1005 }
1006
Eric Erfanianccca3152017-02-22 16:32:36 -08001007 public boolean isIncoming() {
1008 return mLogState.isIncoming;
1009 }
1010
Eric Erfanian2ca43182017-08-31 06:57:16 -07001011 public boolean isAssistedDialed() {
1012 if (getIntentExtras() != null) {
1013 return getIntentExtras().getBoolean(TelephonyManagerCompat.IS_ASSISTED_DIALED, false);
1014 }
1015 return false;
1016 }
1017
Eric Erfanianccca3152017-02-22 16:32:36 -08001018 public LatencyReport getLatencyReport() {
1019 return mLatencyReport;
1020 }
1021
Eric Erfanian2ca43182017-08-31 06:57:16 -07001022 public int getAnswerAndReleaseButtonDisplayedTimes() {
1023 return answerAndReleaseButtonDisplayedTimes;
1024 }
1025
1026 public void increaseAnswerAndReleaseButtonDisplayedTimes() {
1027 answerAndReleaseButtonDisplayedTimes++;
1028 }
1029
1030 public boolean getReleasedByAnsweringSecondCall() {
1031 return releasedByAnsweringSecondCall;
1032 }
1033
1034 public void setReleasedByAnsweringSecondCall(boolean releasedByAnsweringSecondCall) {
1035 this.releasedByAnsweringSecondCall = releasedByAnsweringSecondCall;
1036 }
1037
1038 public int getSecondCallWithoutAnswerAndReleasedButtonTimes() {
1039 return secondCallWithoutAnswerAndReleasedButtonTimes;
1040 }
1041
1042 public void increaseSecondCallWithoutAnswerAndReleasedButtonTimes() {
1043 secondCallWithoutAnswerAndReleasedButtonTimes++;
1044 }
1045
Eric Erfanian8369df02017-05-03 10:27:13 -07001046 @Nullable
1047 public EnrichedCallCapabilities getEnrichedCallCapabilities() {
1048 return mEnrichedCallCapabilities;
1049 }
1050
1051 public void setEnrichedCallCapabilities(
1052 @Nullable EnrichedCallCapabilities mEnrichedCallCapabilities) {
1053 this.mEnrichedCallCapabilities = mEnrichedCallCapabilities;
1054 }
1055
1056 @Nullable
1057 public Session getEnrichedCallSession() {
1058 return mEnrichedCallSession;
1059 }
1060
1061 public void setEnrichedCallSession(@Nullable Session mEnrichedCallSession) {
1062 this.mEnrichedCallSession = mEnrichedCallSession;
1063 }
1064
Eric Erfanianccca3152017-02-22 16:32:36 -08001065 public void unregisterCallback() {
1066 mTelecomCall.unregisterCallback(mTelecomCallCallback);
1067 }
1068
Eric Erfanianccca3152017-02-22 16:32:36 -08001069 public void phoneAccountSelected(PhoneAccountHandle accountHandle, boolean setDefault) {
1070 LogUtil.i(
1071 "DialerCall.phoneAccountSelected",
1072 "accountHandle: %s, setDefault: %b",
1073 accountHandle,
1074 setDefault);
1075 mTelecomCall.phoneAccountSelected(accountHandle, setDefault);
1076 }
1077
1078 public void disconnect() {
1079 LogUtil.i("DialerCall.disconnect", "");
1080 setState(DialerCall.State.DISCONNECTING);
1081 for (DialerCallListener listener : mListeners) {
1082 listener.onDialerCallUpdate();
1083 }
1084 mTelecomCall.disconnect();
1085 }
1086
1087 public void hold() {
1088 LogUtil.i("DialerCall.hold", "");
1089 mTelecomCall.hold();
1090 }
1091
1092 public void unhold() {
1093 LogUtil.i("DialerCall.unhold", "");
1094 mTelecomCall.unhold();
1095 }
1096
1097 public void splitFromConference() {
1098 LogUtil.i("DialerCall.splitFromConference", "");
1099 mTelecomCall.splitFromConference();
1100 }
1101
1102 public void answer(int videoState) {
1103 LogUtil.i("DialerCall.answer", "videoState: " + videoState);
1104 mTelecomCall.answer(videoState);
1105 }
1106
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001107 public void answer() {
1108 answer(mTelecomCall.getDetails().getVideoState());
1109 }
1110
Eric Erfanianccca3152017-02-22 16:32:36 -08001111 public void reject(boolean rejectWithMessage, String message) {
1112 LogUtil.i("DialerCall.reject", "");
1113 mTelecomCall.reject(rejectWithMessage, message);
1114 }
1115
1116 /** Return the string label to represent the call provider */
1117 public String getCallProviderLabel() {
1118 if (callProviderLabel == null) {
1119 PhoneAccount account = getPhoneAccount();
1120 if (account != null && !TextUtils.isEmpty(account.getLabel())) {
1121 List<PhoneAccountHandle> accounts =
1122 mContext.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts();
1123 if (accounts != null && accounts.size() > 1) {
1124 callProviderLabel = account.getLabel().toString();
1125 }
1126 }
1127 if (callProviderLabel == null) {
1128 callProviderLabel = "";
1129 }
1130 }
1131 return callProviderLabel;
1132 }
1133
1134 private PhoneAccount getPhoneAccount() {
1135 PhoneAccountHandle accountHandle = getAccountHandle();
1136 if (accountHandle == null) {
1137 return null;
1138 }
1139 return mContext.getSystemService(TelecomManager.class).getPhoneAccount(accountHandle);
1140 }
1141
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001142 public VideoTech getVideoTech() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001143 if (videoTech == null) {
1144 videoTech = mVideoTechManager.getVideoTech();
1145 }
1146 return videoTech;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001147 }
1148
Eric Erfanianccca3152017-02-22 16:32:36 -08001149 public String getCallbackNumber() {
1150 if (callbackNumber == null) {
1151 // Show the emergency callback number if either:
1152 // 1. This is an emergency call.
1153 // 2. The phone is in Emergency Callback Mode, which means we should show the callback
1154 // number.
1155 boolean showCallbackNumber = hasProperty(Details.PROPERTY_EMERGENCY_CALLBACK_MODE);
1156
1157 if (isEmergencyCall() || showCallbackNumber) {
wangqi339b4f32017-09-18 14:32:09 -07001158 callbackNumber =
1159 mContext.getSystemService(TelecomManager.class).getLine1Number(getAccountHandle());
Eric Erfanianccca3152017-02-22 16:32:36 -08001160 }
1161
Eric Erfanianccca3152017-02-22 16:32:36 -08001162 if (callbackNumber == null) {
1163 callbackNumber = "";
1164 }
1165 }
1166 return callbackNumber;
1167 }
1168
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001169 @Override
1170 public void onVideoTechStateChanged() {
1171 update();
1172 }
1173
1174 @Override
1175 public void onSessionModificationStateChanged() {
1176 for (DialerCallListener listener : mListeners) {
1177 listener.onDialerCallSessionModificationStateChange();
1178 }
1179 }
1180
1181 @Override
1182 public void onCameraDimensionsChanged(int width, int height) {
1183 InCallVideoCallCallbackNotifier.getInstance().cameraDimensionsChanged(this, width, height);
1184 }
1185
1186 @Override
1187 public void onPeerDimensionsChanged(int width, int height) {
1188 InCallVideoCallCallbackNotifier.getInstance().peerDimensionsChanged(this, width, height);
1189 }
1190
1191 @Override
1192 public void onVideoUpgradeRequestReceived() {
1193 LogUtil.enterBlock("DialerCall.onVideoUpgradeRequestReceived");
1194
1195 for (DialerCallListener listener : mListeners) {
1196 listener.onDialerCallUpgradeToVideo();
1197 }
1198
1199 update();
Eric Erfanian8369df02017-05-03 10:27:13 -07001200
1201 Logger.get(mContext)
1202 .logCallImpression(
1203 DialerImpression.Type.VIDEO_CALL_REQUEST_RECEIVED, getUniqueCallId(), getTimeAddedMs());
1204 }
1205
1206 @Override
1207 public void onUpgradedToVideo(boolean switchToSpeaker) {
1208 LogUtil.enterBlock("DialerCall.onUpgradedToVideo");
1209
1210 if (!switchToSpeaker) {
1211 return;
1212 }
1213
1214 CallAudioState audioState = AudioModeProvider.getInstance().getAudioState();
1215
1216 if (0 != (CallAudioState.ROUTE_BLUETOOTH & audioState.getSupportedRouteMask())) {
1217 LogUtil.e(
1218 "DialerCall.onUpgradedToVideo",
1219 "toggling speakerphone not allowed when bluetooth supported.");
1220 return;
1221 }
1222
1223 if (audioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
1224 return;
1225 }
1226
1227 TelecomAdapter.getInstance().setAudioRoute(CallAudioState.ROUTE_SPEAKER);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001228 }
1229
Eric Erfanian2ca43182017-08-31 06:57:16 -07001230 @Override
1231 public void onCapabilitiesUpdated() {
1232 if (getNumber() == null) {
1233 return;
1234 }
1235 EnrichedCallCapabilities capabilities =
1236 EnrichedCallComponent.get(mContext).getEnrichedCallManager().getCapabilities(getNumber());
1237 if (capabilities != null) {
1238 setEnrichedCallCapabilities(capabilities);
1239 update();
1240 }
1241 }
1242
1243 @Override
1244 public void onEnrichedCallStateChanged() {
1245 updateEnrichedCallSession();
1246 }
1247
1248 @Override
1249 public void onImpressionLoggingNeeded(DialerImpression.Type impressionType) {
1250 Logger.get(mContext).logCallImpression(impressionType, getUniqueCallId(), getTimeAddedMs());
1251 }
1252
1253 private void updateEnrichedCallSession() {
1254 if (getNumber() == null) {
1255 return;
1256 }
1257 if (getEnrichedCallSession() != null) {
1258 // State changes to existing sessions are currently handled by the UI components (which have
1259 // their own listeners). Someday instead we could remove those and just call update() here and
1260 // have the usual onDialerCallUpdate update the UI.
1261 dispatchOnEnrichedCallSessionUpdate();
1262 return;
1263 }
1264
1265 EnrichedCallManager manager = EnrichedCallComponent.get(mContext).getEnrichedCallManager();
1266
1267 Filter filter =
1268 isIncoming()
1269 ? manager.createIncomingCallComposerFilter()
1270 : manager.createOutgoingCallComposerFilter();
1271
1272 Session session = manager.getSession(getUniqueCallId(), getNumber(), filter);
1273 if (session == null) {
1274 return;
1275 }
1276
1277 session.setUniqueDialerCallId(getUniqueCallId());
1278 setEnrichedCallSession(session);
1279
1280 LogUtil.i(
1281 "DialerCall.updateEnrichedCallSession",
1282 "setting session %d's dialer id to %s",
1283 session.getSessionId(),
1284 getUniqueCallId());
1285
1286 dispatchOnEnrichedCallSessionUpdate();
1287 }
1288
1289 private void dispatchOnEnrichedCallSessionUpdate() {
1290 for (DialerCallListener listener : mListeners) {
1291 listener.onEnrichedCallSessionUpdate();
1292 }
1293 }
1294
1295 void onRemovedFromCallList() {
1296 // Ensure we clean up when this call is removed.
1297 mVideoTechManager.dispatchRemovedFromCallList();
1298 }
1299
Eric Erfanianccca3152017-02-22 16:32:36 -08001300 /**
1301 * Specifies whether a number is in the call history or not. {@link #CALL_HISTORY_STATUS_UNKNOWN}
1302 * means there is no result.
1303 */
1304 @IntDef({
1305 CALL_HISTORY_STATUS_UNKNOWN,
1306 CALL_HISTORY_STATUS_PRESENT,
1307 CALL_HISTORY_STATUS_NOT_PRESENT
1308 })
1309 @Retention(RetentionPolicy.SOURCE)
1310 public @interface CallHistoryStatus {}
1311
1312 /* Defines different states of this call */
1313 public static class State {
1314
1315 public static final int INVALID = 0;
1316 public static final int NEW = 1; /* The call is new. */
1317 public static final int IDLE = 2; /* The call is idle. Nothing active */
1318 public static final int ACTIVE = 3; /* There is an active call */
1319 public static final int INCOMING = 4; /* A normal incoming phone call */
1320 public static final int CALL_WAITING = 5; /* Incoming call while another is active */
1321 public static final int DIALING = 6; /* An outgoing call during dial phase */
1322 public static final int REDIALING = 7; /* Subsequent dialing attempt after a failure */
1323 public static final int ONHOLD = 8; /* An active phone call placed on hold */
1324 public static final int DISCONNECTING = 9; /* A call is being ended. */
1325 public static final int DISCONNECTED = 10; /* State after a call disconnects */
1326 public static final int CONFERENCED = 11; /* DialerCall part of a conference call */
1327 public static final int SELECT_PHONE_ACCOUNT = 12; /* Waiting for account selection */
1328 public static final int CONNECTING = 13; /* Waiting for Telecom broadcast to finish */
1329 public static final int BLOCKED = 14; /* The number was found on the block list */
1330 public static final int PULLING = 15; /* An external call being pulled to the device */
Eric Erfanian2ca43182017-08-31 06:57:16 -07001331 public static final int CALL_PENDING = 16; /* A call is pending on a long process to finish */
Eric Erfanianccca3152017-02-22 16:32:36 -08001332
1333 public static boolean isConnectingOrConnected(int state) {
1334 switch (state) {
1335 case ACTIVE:
1336 case INCOMING:
1337 case CALL_WAITING:
1338 case CONNECTING:
1339 case DIALING:
1340 case PULLING:
1341 case REDIALING:
1342 case ONHOLD:
1343 case CONFERENCED:
1344 return true;
1345 default:
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001346 return false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001347 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001348 }
1349
1350 public static boolean isDialing(int state) {
1351 return state == DIALING || state == PULLING || state == REDIALING;
1352 }
1353
1354 public static String toString(int state) {
1355 switch (state) {
1356 case INVALID:
1357 return "INVALID";
1358 case NEW:
1359 return "NEW";
1360 case IDLE:
1361 return "IDLE";
1362 case ACTIVE:
1363 return "ACTIVE";
1364 case INCOMING:
1365 return "INCOMING";
1366 case CALL_WAITING:
1367 return "CALL_WAITING";
1368 case DIALING:
1369 return "DIALING";
1370 case PULLING:
1371 return "PULLING";
1372 case REDIALING:
1373 return "REDIALING";
1374 case ONHOLD:
1375 return "ONHOLD";
1376 case DISCONNECTING:
1377 return "DISCONNECTING";
1378 case DISCONNECTED:
1379 return "DISCONNECTED";
1380 case CONFERENCED:
1381 return "CONFERENCED";
1382 case SELECT_PHONE_ACCOUNT:
1383 return "SELECT_PHONE_ACCOUNT";
1384 case CONNECTING:
1385 return "CONNECTING";
1386 case BLOCKED:
1387 return "BLOCKED";
1388 default:
1389 return "UNKNOWN";
1390 }
1391 }
1392 }
1393
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001394 /** Camera direction constants */
1395 public static class CameraDirection {
Eric Erfanianccca3152017-02-22 16:32:36 -08001396 public static final int CAMERA_DIRECTION_UNKNOWN = -1;
1397 public static final int CAMERA_DIRECTION_FRONT_FACING = CameraCharacteristics.LENS_FACING_FRONT;
1398 public static final int CAMERA_DIRECTION_BACK_FACING = CameraCharacteristics.LENS_FACING_BACK;
Eric Erfanianccca3152017-02-22 16:32:36 -08001399 }
1400
1401 /**
1402 * Tracks any state variables that is useful for logging. There is some amount of overlap with
1403 * existing call member variables, but this duplication helps to ensure that none of these logging
1404 * variables will interface with/and affect call logic.
1405 */
1406 public static class LogState {
1407
1408 public DisconnectCause disconnectCause;
1409 public boolean isIncoming = false;
Eric Erfanian8369df02017-05-03 10:27:13 -07001410 public ContactLookupResult.Type contactLookupResult =
1411 ContactLookupResult.Type.UNKNOWN_LOOKUP_RESULT_TYPE;
Eric Erfanianccca3152017-02-22 16:32:36 -08001412 public CallSpecificAppData callSpecificAppData;
1413 // If this was a conference call, the total number of calls involved in the conference.
1414 public int conferencedCalls = 0;
1415 public long duration = 0;
1416 public boolean isLogged = false;
1417
Eric Erfanian8369df02017-05-03 10:27:13 -07001418 private static String lookupToString(ContactLookupResult.Type lookupType) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001419 switch (lookupType) {
Eric Erfanian8369df02017-05-03 10:27:13 -07001420 case LOCAL_CONTACT:
Eric Erfanianccca3152017-02-22 16:32:36 -08001421 return "Local";
Eric Erfanian8369df02017-05-03 10:27:13 -07001422 case LOCAL_CACHE:
Eric Erfanianccca3152017-02-22 16:32:36 -08001423 return "Cache";
Eric Erfanian8369df02017-05-03 10:27:13 -07001424 case REMOTE:
Eric Erfanianccca3152017-02-22 16:32:36 -08001425 return "Remote";
Eric Erfanian8369df02017-05-03 10:27:13 -07001426 case EMERGENCY:
Eric Erfanianccca3152017-02-22 16:32:36 -08001427 return "Emergency";
Eric Erfanian8369df02017-05-03 10:27:13 -07001428 case VOICEMAIL:
Eric Erfanianccca3152017-02-22 16:32:36 -08001429 return "Voicemail";
1430 default:
1431 return "Not found";
1432 }
1433 }
1434
1435 private static String initiationToString(CallSpecificAppData callSpecificAppData) {
1436 if (callSpecificAppData == null) {
1437 return "null";
1438 }
Eric Erfanian8369df02017-05-03 10:27:13 -07001439 switch (callSpecificAppData.getCallInitiationType()) {
1440 case INCOMING_INITIATION:
Eric Erfanianccca3152017-02-22 16:32:36 -08001441 return "Incoming";
Eric Erfanian8369df02017-05-03 10:27:13 -07001442 case DIALPAD:
Eric Erfanianccca3152017-02-22 16:32:36 -08001443 return "Dialpad";
Eric Erfanian8369df02017-05-03 10:27:13 -07001444 case SPEED_DIAL:
Eric Erfanianccca3152017-02-22 16:32:36 -08001445 return "Speed Dial";
Eric Erfanian8369df02017-05-03 10:27:13 -07001446 case REMOTE_DIRECTORY:
Eric Erfanianccca3152017-02-22 16:32:36 -08001447 return "Remote Directory";
Eric Erfanian8369df02017-05-03 10:27:13 -07001448 case SMART_DIAL:
Eric Erfanianccca3152017-02-22 16:32:36 -08001449 return "Smart Dial";
Eric Erfanian8369df02017-05-03 10:27:13 -07001450 case REGULAR_SEARCH:
Eric Erfanianccca3152017-02-22 16:32:36 -08001451 return "Regular Search";
Eric Erfanian8369df02017-05-03 10:27:13 -07001452 case CALL_LOG:
Eric Erfanianccca3152017-02-22 16:32:36 -08001453 return "DialerCall Log";
Eric Erfanian8369df02017-05-03 10:27:13 -07001454 case CALL_LOG_FILTER:
Eric Erfanianccca3152017-02-22 16:32:36 -08001455 return "DialerCall Log Filter";
Eric Erfanian8369df02017-05-03 10:27:13 -07001456 case VOICEMAIL_LOG:
Eric Erfanianccca3152017-02-22 16:32:36 -08001457 return "Voicemail Log";
Eric Erfanian8369df02017-05-03 10:27:13 -07001458 case CALL_DETAILS:
Eric Erfanianccca3152017-02-22 16:32:36 -08001459 return "DialerCall Details";
Eric Erfanian8369df02017-05-03 10:27:13 -07001460 case QUICK_CONTACTS:
Eric Erfanianccca3152017-02-22 16:32:36 -08001461 return "Quick Contacts";
Eric Erfanian8369df02017-05-03 10:27:13 -07001462 case EXTERNAL_INITIATION:
Eric Erfanianccca3152017-02-22 16:32:36 -08001463 return "External";
Eric Erfanian8369df02017-05-03 10:27:13 -07001464 case LAUNCHER_SHORTCUT:
Eric Erfanianccca3152017-02-22 16:32:36 -08001465 return "Launcher Shortcut";
1466 default:
Eric Erfanian8369df02017-05-03 10:27:13 -07001467 return "Unknown: " + callSpecificAppData.getCallInitiationType();
Eric Erfanianccca3152017-02-22 16:32:36 -08001468 }
1469 }
1470
1471 @Override
1472 public String toString() {
1473 return String.format(
1474 Locale.US,
1475 "["
1476 + "%s, " // DisconnectCause toString already describes the object type
1477 + "isIncoming: %s, "
1478 + "contactLookup: %s, "
1479 + "callInitiation: %s, "
1480 + "duration: %s"
1481 + "]",
1482 disconnectCause,
1483 isIncoming,
1484 lookupToString(contactLookupResult),
1485 initiationToString(callSpecificAppData),
1486 duration);
1487 }
1488 }
1489
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001490 private static class VideoTechManager {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001491 private final Context context;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001492 private final EmptyVideoTech emptyVideoTech = new EmptyVideoTech();
Eric Erfanian90508232017-03-24 09:31:16 -07001493 private final List<VideoTech> videoTechs;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001494 private VideoTech savedTech;
1495
1496 VideoTechManager(DialerCall call) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001497 this.context = call.mContext;
1498
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001499 String phoneNumber = call.getNumber();
Eric Erfaniand8046e52017-04-06 09:41:50 -07001500 phoneNumber = phoneNumber != null ? phoneNumber : "";
Eric Erfanian2ca43182017-08-31 06:57:16 -07001501 phoneNumber = phoneNumber.replaceAll("[^+0-9]", "");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001502
1503 // Insert order here determines the priority of that video tech option
Eric Erfanian8369df02017-05-03 10:27:13 -07001504 videoTechs = new ArrayList<>();
1505 videoTechs.add(new ImsVideoTech(Logger.get(call.mContext), call, call.mTelecomCall));
Eric Erfanian90508232017-03-24 09:31:16 -07001506
1507 VideoTech rcsVideoTech =
1508 EnrichedCallComponent.get(call.mContext)
1509 .getRcsVideoShareFactory()
1510 .newRcsVideoShare(
1511 EnrichedCallComponent.get(call.mContext).getEnrichedCallManager(),
1512 call,
Eric Erfaniand8046e52017-04-06 09:41:50 -07001513 phoneNumber);
Eric Erfanian90508232017-03-24 09:31:16 -07001514 if (rcsVideoTech != null) {
1515 videoTechs.add(rcsVideoTech);
1516 }
Eric Erfaniand8046e52017-04-06 09:41:50 -07001517
1518 videoTechs.add(
1519 new LightbringerTech(
Eric Erfanian2ca43182017-08-31 06:57:16 -07001520 LightbringerComponent.get(call.mContext).getLightbringer(),
1521 call,
1522 call.mTelecomCall,
1523 phoneNumber));
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001524 }
1525
1526 VideoTech getVideoTech() {
1527 if (savedTech != null) {
1528 return savedTech;
1529 }
1530
1531 for (VideoTech tech : videoTechs) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001532 if (tech.isAvailable(context)) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001533 // Remember the first VideoTech that becomes available and always use it
1534 savedTech = tech;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001535 savedTech.becomePrimary();
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001536 return savedTech;
1537 }
1538 }
1539
1540 return emptyVideoTech;
1541 }
1542
1543 void dispatchCallStateChanged(int newState) {
1544 for (VideoTech videoTech : videoTechs) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001545 videoTech.onCallStateChanged(context, newState);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001546 }
1547 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001548
1549 void dispatchRemovedFromCallList() {
1550 for (VideoTech videoTech : videoTechs) {
1551 videoTech.onRemovedFromCallList();
1552 }
1553 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001554 }
1555
Eric Erfanianccca3152017-02-22 16:32:36 -08001556 /** Called when canned text responses have been loaded. */
1557 public interface CannedTextResponsesLoadedListener {
1558 void onCannedTextResponsesLoaded(DialerCall call);
1559 }
1560}