blob: b9e6744ea36fc5c8c62bed982849b9e15999933a [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;
18
19import static com.android.contacts.common.compat.CallCompat.Details.PROPERTY_ENTERPRISE_CALL;
20
21import android.Manifest;
Eric Erfanianccca3152017-02-22 16:32:36 -080022import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.PackageManager;
27import android.graphics.drawable.Drawable;
28import android.hardware.display.DisplayManager;
29import android.os.BatteryManager;
30import android.os.Handler;
Eric Erfanian2ca43182017-08-31 06:57:16 -070031import android.os.Trace;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070032import android.support.annotation.NonNull;
Eric Erfanianccca3152017-02-22 16:32:36 -080033import android.support.annotation.Nullable;
34import android.support.v4.app.Fragment;
35import android.support.v4.content.ContextCompat;
36import android.telecom.Call.Details;
37import android.telecom.StatusHints;
38import android.telecom.TelecomManager;
wangqia4623702018-04-02 14:23:38 -070039import android.text.BidiFormatter;
40import android.text.TextDirectionHeuristics;
Eric Erfanianccca3152017-02-22 16:32:36 -080041import android.text.TextUtils;
42import android.view.Display;
43import android.view.View;
44import android.view.accessibility.AccessibilityEvent;
45import android.view.accessibility.AccessibilityManager;
46import com.android.contacts.common.ContactsUtils;
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;
zachh190343a2018-05-31 17:30:46 -070049import com.android.dialer.configprovider.ConfigProviderComponent;
twyen5578d922018-06-25 12:36:26 -070050import com.android.dialer.contacts.ContactsComponent;
Eric Erfanian8369df02017-05-03 10:27:13 -070051import com.android.dialer.logging.DialerImpression;
Eric Erfaniand8046e52017-04-06 09:41:50 -070052import com.android.dialer.logging.Logger;
Eric Erfanianccca3152017-02-22 16:32:36 -080053import com.android.dialer.multimedia.MultimediaData;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070054import com.android.dialer.oem.MotorolaUtils;
wangqi97539352017-09-25 11:15:16 -070055import com.android.dialer.phonenumberutil.PhoneNumberHelper;
Eric Erfanian2ca43182017-08-31 06:57:16 -070056import com.android.dialer.postcall.PostCall;
twyen4fde0ac2018-03-22 18:04:23 -070057import com.android.dialer.preferredsim.suggestion.SuggestionProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080058import com.android.incallui.ContactInfoCache.ContactCacheEntry;
59import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
60import com.android.incallui.InCallPresenter.InCallDetailsListener;
61import com.android.incallui.InCallPresenter.InCallEventListener;
62import com.android.incallui.InCallPresenter.InCallState;
63import com.android.incallui.InCallPresenter.InCallStateListener;
64import com.android.incallui.InCallPresenter.IncomingCallListener;
65import com.android.incallui.call.CallList;
66import com.android.incallui.call.DialerCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080067import com.android.incallui.call.DialerCallListener;
wangqibb94ca62018-04-27 14:34:04 -070068import com.android.incallui.call.state.DialerCallState;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070069import com.android.incallui.calllocation.CallLocation;
70import com.android.incallui.calllocation.CallLocationComponent;
Eric Erfanianccca3152017-02-22 16:32:36 -080071import com.android.incallui.incall.protocol.ContactPhotoType;
72import com.android.incallui.incall.protocol.InCallScreen;
73import com.android.incallui.incall.protocol.InCallScreenDelegate;
74import com.android.incallui.incall.protocol.PrimaryCallState;
Eric Erfanian2ca43182017-08-31 06:57:16 -070075import com.android.incallui.incall.protocol.PrimaryCallState.ButtonState;
Eric Erfanianccca3152017-02-22 16:32:36 -080076import com.android.incallui.incall.protocol.PrimaryInfo;
77import com.android.incallui.incall.protocol.SecondaryInfo;
Eric Erfanian90508232017-03-24 09:31:16 -070078import com.android.incallui.videotech.utils.SessionModificationState;
Eric Erfanianccca3152017-02-22 16:32:36 -080079import java.lang.ref.WeakReference;
80
81/**
82 * Controller for the Call Card Fragment. This class listens for changes to InCallState and passes
83 * it along to the fragment.
84 */
85public class CallCardPresenter
86 implements InCallStateListener,
87 IncomingCallListener,
88 InCallDetailsListener,
89 InCallEventListener,
90 InCallScreenDelegate,
Eric Erfanian2ca43182017-08-31 06:57:16 -070091 DialerCallListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080092
93 /**
94 * Amount of time to wait before sending an announcement via the accessibility manager. When the
95 * call state changes to an outgoing or incoming state for the first time, the UI can often be
96 * changing due to call updates or contact lookup. This allows the UI to settle to a stable state
97 * to ensure that the correct information is announced.
98 */
99 private static final long ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS = 500;
100
101 /** Flag to allow the user's current location to be shown during emergency calls. */
102 private static final String CONFIG_ENABLE_EMERGENCY_LOCATION = "config_enable_emergency_location";
103
104 private static final boolean CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT = true;
105
106 /**
107 * Make it possible to not get location during an emergency call if the battery is too low, since
108 * doing so could trigger gps and thus potentially cause the phone to die in the middle of the
109 * call.
110 */
111 private static final String CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION =
112 "min_battery_percent_for_emergency_location";
113
114 private static final long CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT = 10;
115
linyuh183cb712017-12-27 17:02:37 -0800116 private final Context context;
Eric Erfanianccca3152017-02-22 16:32:36 -0800117 private final Handler handler = new Handler();
118
linyuh183cb712017-12-27 17:02:37 -0800119 private DialerCall primary;
wangqidd5f1a52018-01-23 18:01:00 -0800120 private String primaryNumber;
linyuh183cb712017-12-27 17:02:37 -0800121 private DialerCall secondary;
wangqidd5f1a52018-01-23 18:01:00 -0800122 private String secondaryNumber;
linyuh183cb712017-12-27 17:02:37 -0800123 private ContactCacheEntry primaryContactInfo;
124 private ContactCacheEntry secondaryContactInfo;
linyuh183cb712017-12-27 17:02:37 -0800125 private boolean isFullscreen = false;
126 private InCallScreen inCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -0800127 private boolean isInCallScreenReady;
128 private boolean shouldSendAccessibilityEvent;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700129
130 @NonNull private final CallLocation callLocation;
Eric Erfanianccca3152017-02-22 16:32:36 -0800131 private final Runnable sendAccessibilityEventRunnable =
132 new Runnable() {
133 @Override
134 public void run() {
linyuh183cb712017-12-27 17:02:37 -0800135 shouldSendAccessibilityEvent = !sendAccessibilityEvent(context, getUi());
Eric Erfanianccca3152017-02-22 16:32:36 -0800136 LogUtil.i(
137 "CallCardPresenter.sendAccessibilityEventRunnable",
138 "still should send: %b",
139 shouldSendAccessibilityEvent);
140 if (!shouldSendAccessibilityEvent) {
141 handler.removeCallbacks(this);
142 }
143 }
144 };
145
146 public CallCardPresenter(Context context) {
wangqi385a5a12017-09-28 10:44:54 -0700147 LogUtil.i("CallCardPresenter.constructor", null);
linyuh183cb712017-12-27 17:02:37 -0800148 this.context = Assert.isNotNull(context).getApplicationContext();
149 callLocation = CallLocationComponent.get(this.context).getCallLocation();
Eric Erfanianccca3152017-02-22 16:32:36 -0800150 }
151
152 private static boolean hasCallSubject(DialerCall call) {
153 return !TextUtils.isEmpty(call.getCallSubject());
154 }
155
156 @Override
157 public void onInCallScreenDelegateInit(InCallScreen inCallScreen) {
158 Assert.isNotNull(inCallScreen);
linyuh183cb712017-12-27 17:02:37 -0800159 this.inCallScreen = inCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -0800160
161 // Call may be null if disconnect happened already.
162 DialerCall call = CallList.getInstance().getFirstCall();
163 if (call != null) {
linyuh183cb712017-12-27 17:02:37 -0800164 primary = call;
165 if (shouldShowNoteSentToast(primary)) {
166 this.inCallScreen.showNoteSentToast();
Eric Erfanianccca3152017-02-22 16:32:36 -0800167 }
168 call.addListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800169 // start processing lookups right away.
170 if (!call.isConferenceCall()) {
wangqibb94ca62018-04-27 14:34:04 -0700171 startContactInfoSearch(call, true, call.getState() == DialerCallState.INCOMING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800172 } else {
173 updateContactEntry(null, true);
174 }
175 }
176
177 onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
178 }
179
180 @Override
181 public void onInCallScreenReady() {
wangqi385a5a12017-09-28 10:44:54 -0700182 LogUtil.i("CallCardPresenter.onInCallScreenReady", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800183 Assert.checkState(!isInCallScreenReady);
Eric Erfanianccca3152017-02-22 16:32:36 -0800184
Eric Erfanianccca3152017-02-22 16:32:36 -0800185 // Contact search may have completed before ui is ready.
linyuh183cb712017-12-27 17:02:37 -0800186 if (primaryContactInfo != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800187 updatePrimaryDisplayInfo();
188 }
189
190 // Register for call state changes last
191 InCallPresenter.getInstance().addListener(this);
192 InCallPresenter.getInstance().addIncomingCallListener(this);
193 InCallPresenter.getInstance().addDetailsListener(this);
194 InCallPresenter.getInstance().addInCallEventListener(this);
195 isInCallScreenReady = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700196
Eric Erfaniand8046e52017-04-06 09:41:50 -0700197 // Log location impressions
linyuh183cb712017-12-27 17:02:37 -0800198 if (isOutgoingEmergencyCall(primary)) {
199 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_NEW_EMERGENCY_CALL);
200 } else if (isIncomingEmergencyCall(primary) || isIncomingEmergencyCall(secondary)) {
201 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_CALLBACK);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700202 }
203
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700204 // Showing the location may have been skipped if the UI wasn't ready during previous layout.
205 if (shouldShowLocation()) {
linyuh183cb712017-12-27 17:02:37 -0800206 inCallScreen.showLocationUi(getLocationFragment());
Eric Erfaniand8046e52017-04-06 09:41:50 -0700207
208 // Log location impressions
209 if (!hasLocationPermission()) {
linyuh183cb712017-12-27 17:02:37 -0800210 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_NO_LOCATION_PERMISSION);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700211 } else if (isBatteryTooLowForEmergencyLocation()) {
linyuh183cb712017-12-27 17:02:37 -0800212 Logger.get(context)
Eric Erfaniand8046e52017-04-06 09:41:50 -0700213 .logImpression(DialerImpression.Type.EMERGENCY_BATTERY_TOO_LOW_TO_GET_LOCATION);
linyuh183cb712017-12-27 17:02:37 -0800214 } else if (!callLocation.canGetLocation(context)) {
215 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_CANT_GET_LOCATION);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700216 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700217 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800218 }
219
220 @Override
221 public void onInCallScreenUnready() {
wangqi385a5a12017-09-28 10:44:54 -0700222 LogUtil.i("CallCardPresenter.onInCallScreenUnready", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800223 Assert.checkState(isInCallScreenReady);
224
Eric Erfanianccca3152017-02-22 16:32:36 -0800225 // stop getting call state changes
226 InCallPresenter.getInstance().removeListener(this);
227 InCallPresenter.getInstance().removeIncomingCallListener(this);
228 InCallPresenter.getInstance().removeDetailsListener(this);
229 InCallPresenter.getInstance().removeInCallEventListener(this);
linyuh183cb712017-12-27 17:02:37 -0800230 if (primary != null) {
231 primary.removeListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800232 }
233
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700234 callLocation.close();
235
linyuh183cb712017-12-27 17:02:37 -0800236 primary = null;
237 primaryContactInfo = null;
238 secondaryContactInfo = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800239 isInCallScreenReady = false;
240 }
241
242 @Override
243 public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
244 // same logic should happen as with onStateChange()
245 onStateChange(oldState, newState, CallList.getInstance());
246 }
247
248 @Override
249 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700250 Trace.beginSection("CallCardPresenter.onStateChange");
251 LogUtil.v("CallCardPresenter.onStateChange", "oldState: %s, newState: %s", oldState, newState);
linyuh183cb712017-12-27 17:02:37 -0800252 if (inCallScreen == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700253 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800254 return;
255 }
256
257 DialerCall primary = null;
258 DialerCall secondary = null;
259
260 if (newState == InCallState.INCOMING) {
261 primary = callList.getIncomingCall();
262 } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
263 primary = callList.getOutgoingCall();
264 if (primary == null) {
265 primary = callList.getPendingOutgoingCall();
266 }
267
268 // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
269 // highest priority call to display as the secondary call.
yuegb9103042018-03-30 12:12:25 -0700270 secondary = InCallPresenter.getCallToDisplay(callList, null, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800271 } else if (newState == InCallState.INCALL) {
yuegb9103042018-03-30 12:12:25 -0700272 primary = InCallPresenter.getCallToDisplay(callList, null, false);
273 secondary = InCallPresenter.getCallToDisplay(callList, primary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800274 }
275
276 LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
277 LogUtil.v("CallCardPresenter.onStateChange", "secondary call: " + secondary);
wangqidd5f1a52018-01-23 18:01:00 -0800278 String primaryNumber = null;
279 String secondaryNumber = null;
280 if (primary != null) {
281 primaryNumber = primary.getNumber();
282 }
283 if (secondary != null) {
284 secondaryNumber = secondary.getNumber();
285 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800286
287 final boolean primaryChanged =
linyuh183cb712017-12-27 17:02:37 -0800288 !(DialerCall.areSame(this.primary, primary)
wangqidd5f1a52018-01-23 18:01:00 -0800289 && TextUtils.equals(this.primaryNumber, primaryNumber));
Eric Erfanianccca3152017-02-22 16:32:36 -0800290 final boolean secondaryChanged =
linyuh183cb712017-12-27 17:02:37 -0800291 !(DialerCall.areSame(this.secondary, secondary)
wangqidd5f1a52018-01-23 18:01:00 -0800292 && TextUtils.equals(this.secondaryNumber, secondaryNumber));
Eric Erfanianccca3152017-02-22 16:32:36 -0800293
linyuh183cb712017-12-27 17:02:37 -0800294 this.secondary = secondary;
wangqidd5f1a52018-01-23 18:01:00 -0800295 this.secondaryNumber = secondaryNumber;
linyuh183cb712017-12-27 17:02:37 -0800296 DialerCall previousPrimary = this.primary;
297 this.primary = primary;
wangqidd5f1a52018-01-23 18:01:00 -0800298 this.primaryNumber = primaryNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800299
linyuh183cb712017-12-27 17:02:37 -0800300 if (this.primary != null) {
linyuh183cb712017-12-27 17:02:37 -0800301 inCallScreen.updateInCallScreenColors();
Eric Erfanianccca3152017-02-22 16:32:36 -0800302 }
303
304 if (primaryChanged && shouldShowNoteSentToast(primary)) {
linyuh183cb712017-12-27 17:02:37 -0800305 inCallScreen.showNoteSentToast();
Eric Erfanianccca3152017-02-22 16:32:36 -0800306 }
307
308 // Refresh primary call information if either:
309 // 1. Primary call changed.
310 // 2. The call's ability to manage conference has changed.
311 if (shouldRefreshPrimaryInfo(primaryChanged)) {
312 // primary call has changed
313 if (previousPrimary != null) {
314 previousPrimary.removeListener(this);
315 }
linyuh183cb712017-12-27 17:02:37 -0800316 this.primary.addListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800317
twyena40a9ef2018-05-22 17:35:59 -0700318 primaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(context, this.primary);
Eric Erfanianccca3152017-02-22 16:32:36 -0800319 updatePrimaryDisplayInfo();
linyuh183cb712017-12-27 17:02:37 -0800320 maybeStartSearch(this.primary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800321 }
322
linyuh183cb712017-12-27 17:02:37 -0800323 if (previousPrimary != null && this.primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800324 previousPrimary.removeListener(this);
325 }
326
wangqic8cf79e2017-10-17 09:21:00 -0700327 if (secondaryChanged) {
linyuh183cb712017-12-27 17:02:37 -0800328 if (this.secondary == null) {
wangqic8cf79e2017-10-17 09:21:00 -0700329 // Secondary call may have ended. Update the ui.
linyuh183cb712017-12-27 17:02:37 -0800330 secondaryContactInfo = null;
wangqic8cf79e2017-10-17 09:21:00 -0700331 updateSecondaryDisplayInfo();
332 } else {
333 // secondary call has changed
twyena40a9ef2018-05-22 17:35:59 -0700334 secondaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(context, this.secondary);
wangqic8cf79e2017-10-17 09:21:00 -0700335 updateSecondaryDisplayInfo();
linyuh183cb712017-12-27 17:02:37 -0800336 maybeStartSearch(this.secondary, false);
wangqic8cf79e2017-10-17 09:21:00 -0700337 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800338 }
339
340 // Set the call state
wangqibb94ca62018-04-27 14:34:04 -0700341 int callState = DialerCallState.IDLE;
linyuh183cb712017-12-27 17:02:37 -0800342 if (this.primary != null) {
343 callState = this.primary.getState();
Eric Erfanianccca3152017-02-22 16:32:36 -0800344 updatePrimaryCallState();
345 } else {
Android Dialer31fe9982018-02-26 13:29:09 -0800346 getUi().setCallState(PrimaryCallState.empty());
Eric Erfanianccca3152017-02-22 16:32:36 -0800347 }
348
349 maybeShowManageConferenceCallButton();
350
351 // Hide the end call button instantly if we're receiving an incoming call.
352 getUi()
353 .setEndCallButtonEnabled(
linyuh183cb712017-12-27 17:02:37 -0800354 shouldShowEndCallButton(this.primary, callState),
wangqibb94ca62018-04-27 14:34:04 -0700355 callState != DialerCallState.INCOMING /* animate */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800356
357 maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700358 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800359 }
360
361 @Override
362 public void onDetailsChanged(DialerCall call, Details details) {
363 updatePrimaryCallState();
364
365 if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
366 != details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
367 maybeShowManageConferenceCallButton();
368 }
369 }
370
371 @Override
372 public void onDialerCallDisconnect() {}
373
374 @Override
375 public void onDialerCallUpdate() {
376 // No-op; specific call updates handled elsewhere.
377 }
378
379 @Override
380 public void onWiFiToLteHandover() {}
381
382 @Override
383 public void onHandoverToWifiFailure() {}
384
Eric Erfanianc857f902017-05-15 14:05:33 -0700385 @Override
386 public void onInternationalCallOnWifi() {}
387
Eric Erfanian2ca43182017-08-31 06:57:16 -0700388 @Override
389 public void onEnrichedCallSessionUpdate() {
390 LogUtil.enterBlock("CallCardPresenter.onEnrichedCallSessionUpdate");
391 updatePrimaryDisplayInfo();
392 }
393
Eric Erfanianccca3152017-02-22 16:32:36 -0800394 /** Handles a change to the child number by refreshing the primary call info. */
395 @Override
396 public void onDialerCallChildNumberChange() {
397 LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
398
linyuh183cb712017-12-27 17:02:37 -0800399 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800400 return;
401 }
402 updatePrimaryDisplayInfo();
403 }
404
405 /** Handles a change to the last forwarding number by refreshing the primary call info. */
406 @Override
407 public void onDialerCallLastForwardedNumberChange() {
408 LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
409
linyuh183cb712017-12-27 17:02:37 -0800410 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800411 return;
412 }
413 updatePrimaryDisplayInfo();
414 updatePrimaryCallState();
415 }
416
417 @Override
418 public void onDialerCallUpgradeToVideo() {}
419
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700420 /** Handles a change to the session modification state for a call. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800421 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700422 public void onDialerCallSessionModificationStateChange() {
423 LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
Eric Erfanianccca3152017-02-22 16:32:36 -0800424
linyuh183cb712017-12-27 17:02:37 -0800425 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800426 return;
427 }
428 getUi()
429 .setEndCallButtonEnabled(
linyuh183cb712017-12-27 17:02:37 -0800430 primary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700431 != SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800432 true /* shouldAnimate */);
433 updatePrimaryCallState();
434 }
435
Eric Erfanianccca3152017-02-22 16:32:36 -0800436 private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
linyuh183cb712017-12-27 17:02:37 -0800437 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800438 return false;
439 }
440 return primaryChanged
linyuh183cb712017-12-27 17:02:37 -0800441 || inCallScreen.isManageConferenceVisible() != shouldShowManageConference();
Eric Erfanianccca3152017-02-22 16:32:36 -0800442 }
443
444 private void updatePrimaryCallState() {
linyuh183cb712017-12-27 17:02:37 -0800445 if (getUi() != null && primary != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800446 boolean isWorkCall =
linyuh183cb712017-12-27 17:02:37 -0800447 primary.hasProperty(PROPERTY_ENTERPRISE_CALL)
448 || (primaryContactInfo != null
449 && primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
Eric Erfanianccca3152017-02-22 16:32:36 -0800450 boolean isHdAudioCall =
linyuh183cb712017-12-27 17:02:37 -0800451 isPrimaryCallActive() && primary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700452 boolean isAttemptingHdAudioCall =
453 !isHdAudioCall
linyuh183cb712017-12-27 17:02:37 -0800454 && !primary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
455 && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(context);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700456
linyuh183cb712017-12-27 17:02:37 -0800457 boolean isBusiness = primaryContactInfo != null && primaryContactInfo.isBusiness;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700458
Eric Erfanianccca3152017-02-22 16:32:36 -0800459 // Check for video state change and update the visibility of the contact photo. The contact
460 // photo is hidden when the incoming video surface is shown.
461 // The contact photo visibility can also change in setPrimary().
462 boolean shouldShowContactPhoto =
linyuh183cb712017-12-27 17:02:37 -0800463 !VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800464 getUi()
465 .setCallState(
Android Dialer31fe9982018-02-26 13:29:09 -0800466 PrimaryCallState.builder()
467 .setState(primary.getState())
468 .setIsVideoCall(primary.isVideoCall())
469 .setSessionModificationState(primary.getVideoTech().getSessionModificationState())
470 .setDisconnectCause(primary.getDisconnectCause())
471 .setConnectionLabel(getConnectionLabel())
twyen4fde0ac2018-03-22 18:04:23 -0700472 .setSimSuggestionReason(getSimSuggestionReason())
Android Dialer31fe9982018-02-26 13:29:09 -0800473 .setConnectionIcon(getCallStateIcon())
474 .setGatewayNumber(getGatewayNumber())
475 .setCallSubject(shouldShowCallSubject(primary) ? primary.getCallSubject() : null)
476 .setCallbackNumber(
477 PhoneNumberHelper.formatNumber(
linyuhb06d0092018-03-01 15:05:36 -0800478 context, primary.getCallbackNumber(), primary.getSimCountryIso()))
Android Dialer31fe9982018-02-26 13:29:09 -0800479 .setIsWifi(primary.hasProperty(Details.PROPERTY_WIFI))
480 .setIsConference(
481 primary.isConferenceCall()
482 && !primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE))
483 .setIsWorkCall(isWorkCall)
484 .setIsHdAttempting(isAttemptingHdAudioCall)
485 .setIsHdAudioCall(isHdAudioCall)
486 .setIsForwardedNumber(
487 !TextUtils.isEmpty(primary.getLastForwardedNumber())
488 || primary.isCallForwarded())
489 .setShouldShowContactPhoto(shouldShowContactPhoto)
490 .setConnectTimeMillis(primary.getConnectTimeMillis())
491 .setIsVoiceMailNumber(primary.isVoiceMailNumber())
492 .setIsRemotelyHeld(primary.isRemotelyHeld())
493 .setIsBusinessNumber(isBusiness)
494 .setSupportsCallOnHold(supports2ndCallOnHold())
495 .setSwapToSecondaryButtonState(getSwapToSecondaryButtonState())
496 .setIsAssistedDialed(primary.isAssistedDialed())
497 .setCustomLabel(null)
498 .setAssistedDialingExtras(primary.getAssistedDialingExtras())
499 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800500
501 InCallActivity activity =
linyuh183cb712017-12-27 17:02:37 -0800502 (InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
Eric Erfanianccca3152017-02-22 16:32:36 -0800503 if (activity != null) {
504 activity.onPrimaryCallStateChanged();
505 }
506 }
507 }
508
Eric Erfanian2ca43182017-08-31 06:57:16 -0700509 private @ButtonState int getSwapToSecondaryButtonState() {
linyuh183cb712017-12-27 17:02:37 -0800510 if (secondary == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700511 return ButtonState.NOT_SUPPORT;
512 }
wangqibb94ca62018-04-27 14:34:04 -0700513 if (primary.getState() == DialerCallState.ACTIVE) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700514 return ButtonState.ENABLED;
515 }
516 return ButtonState.DISABLED;
517 }
518
Eric Erfanianccca3152017-02-22 16:32:36 -0800519 /** Only show the conference call button if we can manage the conference. */
520 private void maybeShowManageConferenceCallButton() {
521 getUi().showManageConferenceCallButton(shouldShowManageConference());
522 }
523
524 /**
525 * Determines if the manage conference button should be visible, based on the current primary
526 * call.
527 *
528 * @return {@code True} if the manage conference button should be visible.
529 */
530 private boolean shouldShowManageConference() {
linyuh183cb712017-12-27 17:02:37 -0800531 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800532 return false;
533 }
534
linyuh183cb712017-12-27 17:02:37 -0800535 return primary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) && !isFullscreen;
Eric Erfanianccca3152017-02-22 16:32:36 -0800536 }
537
Eric Erfanian2ca43182017-08-31 06:57:16 -0700538 private boolean supports2ndCallOnHold() {
539 DialerCall firstCall = CallList.getInstance().getActiveOrBackgroundCall();
540 DialerCall incomingCall = CallList.getInstance().getIncomingCall();
541 if (firstCall != null && incomingCall != null && firstCall != incomingCall) {
542 return incomingCall.can(Details.CAPABILITY_HOLD);
543 }
544 return true;
545 }
546
Eric Erfanianccca3152017-02-22 16:32:36 -0800547 @Override
548 public void onCallStateButtonClicked() {
linyuh183cb712017-12-27 17:02:37 -0800549 Intent broadcastIntent = Bindings.get(context).getCallStateButtonBroadcastIntent(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800550 if (broadcastIntent != null) {
551 LogUtil.v(
552 "CallCardPresenter.onCallStateButtonClicked",
553 "sending call state button broadcast: " + broadcastIntent);
linyuh183cb712017-12-27 17:02:37 -0800554 context.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
Eric Erfanianccca3152017-02-22 16:32:36 -0800555 }
556 }
557
558 @Override
559 public void onManageConferenceClicked() {
560 InCallActivity activity =
linyuh183cb712017-12-27 17:02:37 -0800561 (InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
Eric Erfanianccca3152017-02-22 16:32:36 -0800562 activity.showConferenceFragment(true);
563 }
564
565 @Override
566 public void onShrinkAnimationComplete() {
567 InCallPresenter.getInstance().onShrinkAnimationComplete();
568 }
569
Eric Erfanianccca3152017-02-22 16:32:36 -0800570 private void maybeStartSearch(DialerCall call, boolean isPrimary) {
571 // no need to start search for conference calls which show generic info.
572 if (call != null && !call.isConferenceCall()) {
wangqibb94ca62018-04-27 14:34:04 -0700573 startContactInfoSearch(call, isPrimary, call.getState() == DialerCallState.INCOMING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800574 }
575 }
576
Eric Erfanianccca3152017-02-22 16:32:36 -0800577 /** Starts a query for more contact data for the save primary and secondary calls. */
578 private void startContactInfoSearch(
579 final DialerCall call, final boolean isPrimary, boolean isIncoming) {
linyuh183cb712017-12-27 17:02:37 -0800580 final ContactInfoCache cache = ContactInfoCache.getInstance(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800581
582 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
583 }
584
585 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
586 final boolean entryMatchesExistingCall =
linyuh183cb712017-12-27 17:02:37 -0800587 (isPrimary && primary != null && TextUtils.equals(callId, primary.getId()))
588 || (!isPrimary && secondary != null && TextUtils.equals(callId, secondary.getId()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800589 if (entryMatchesExistingCall) {
590 updateContactEntry(entry, isPrimary);
591 } else {
592 LogUtil.e(
593 "CallCardPresenter.onContactInfoComplete",
594 "dropping stale contact lookup info for " + callId);
595 }
596
597 final DialerCall call = CallList.getInstance().getCallById(callId);
598 if (call != null) {
599 call.getLogState().contactLookupResult = entry.contactLookupResult;
600 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700601 if (entry.lookupUri != null) {
linyuh183cb712017-12-27 17:02:37 -0800602 CallerInfoUtils.sendViewNotification(context, entry.lookupUri);
Eric Erfanianccca3152017-02-22 16:32:36 -0800603 }
604 }
605
606 private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
607 if (getUi() == null) {
608 return;
609 }
610
611 if (entry.photo != null) {
linyuh183cb712017-12-27 17:02:37 -0800612 if (primary != null && callId.equals(primary.getId())) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800613 updateContactEntry(entry, true /* isPrimary */);
linyuh183cb712017-12-27 17:02:37 -0800614 } else if (secondary != null && callId.equals(secondary.getId())) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800615 updateContactEntry(entry, false /* isPrimary */);
616 }
617 }
618 }
619
620 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
621 if (isPrimary) {
linyuh183cb712017-12-27 17:02:37 -0800622 primaryContactInfo = entry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800623 updatePrimaryDisplayInfo();
624 } else {
linyuh183cb712017-12-27 17:02:37 -0800625 secondaryContactInfo = entry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800626 updateSecondaryDisplayInfo();
627 }
628 }
629
Eric Erfanianccca3152017-02-22 16:32:36 -0800630 private void updatePrimaryDisplayInfo() {
linyuh183cb712017-12-27 17:02:37 -0800631 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800632 // TODO: May also occur if search result comes back after ui is destroyed. Look into
633 // removing that case completely.
634 LogUtil.v(
635 "CallCardPresenter.updatePrimaryDisplayInfo",
636 "updatePrimaryDisplayInfo called but ui is null!");
637 return;
638 }
639
linyuh183cb712017-12-27 17:02:37 -0800640 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800641 // Clear the primary display info.
Android Dialerf9439102018-02-21 08:05:14 -0800642 inCallScreen.setPrimary(PrimaryInfo.empty());
Eric Erfanianccca3152017-02-22 16:32:36 -0800643 return;
644 }
645
646 // Hide the contact photo if we are in a video call and the incoming video surface is
647 // showing.
648 boolean showContactPhoto =
linyuh183cb712017-12-27 17:02:37 -0800649 !VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800650
651 // DialerCall placed through a work phone account.
linyuh183cb712017-12-27 17:02:37 -0800652 boolean hasWorkCallProperty = primary.hasProperty(PROPERTY_ENTERPRISE_CALL);
Eric Erfanianccca3152017-02-22 16:32:36 -0800653
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700654 MultimediaData multimediaData = null;
linyuh183cb712017-12-27 17:02:37 -0800655 if (primary.getEnrichedCallSession() != null) {
656 multimediaData = primary.getEnrichedCallSession().getMultimediaData();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700657 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800658
linyuh183cb712017-12-27 17:02:37 -0800659 if (primary.isConferenceCall()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800660 LogUtil.v(
661 "CallCardPresenter.updatePrimaryDisplayInfo",
662 "update primary display info for conference call.");
663
linyuh183cb712017-12-27 17:02:37 -0800664 inCallScreen.setPrimary(
Android Dialerf9439102018-02-21 08:05:14 -0800665 PrimaryInfo.builder()
666 .setName(
667 CallerInfoUtils.getConferenceString(
668 context, primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)))
669 .setNameIsNumber(false)
670 .setPhotoType(ContactPhotoType.DEFAULT_PLACEHOLDER)
671 .setIsSipCall(false)
672 .setIsContactPhotoShown(showContactPhoto)
673 .setIsWorkCall(hasWorkCallProperty)
674 .setIsSpam(false)
675 .setIsLocalContact(false)
676 .setAnsweringDisconnectsOngoingCall(false)
677 .setShouldShowLocation(shouldShowLocation())
678 .setShowInCallButtonGrid(true)
679 .setNumberPresentation(primary.getNumberPresentation())
680 .build());
linyuh183cb712017-12-27 17:02:37 -0800681 } else if (primaryContactInfo != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800682 LogUtil.v(
683 "CallCardPresenter.updatePrimaryDisplayInfo",
linyuh183cb712017-12-27 17:02:37 -0800684 "update primary display info for " + primaryContactInfo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800685
linyuh183cb712017-12-27 17:02:37 -0800686 String name = getNameForCall(primaryContactInfo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800687 String number;
688
linyuh183cb712017-12-27 17:02:37 -0800689 boolean isChildNumberShown = !TextUtils.isEmpty(primary.getChildNumber());
690 boolean isForwardedNumberShown = !TextUtils.isEmpty(primary.getLastForwardedNumber());
691 boolean isCallSubjectShown = shouldShowCallSubject(primary);
Eric Erfanianccca3152017-02-22 16:32:36 -0800692
693 if (isCallSubjectShown) {
694 number = null;
695 } else if (isChildNumberShown) {
linyuh183cb712017-12-27 17:02:37 -0800696 number = context.getString(R.string.child_number, primary.getChildNumber());
Eric Erfanianccca3152017-02-22 16:32:36 -0800697 } else if (isForwardedNumberShown) {
698 // Use last forwarded number instead of second line, if present.
linyuh183cb712017-12-27 17:02:37 -0800699 number = primary.getLastForwardedNumber();
Eric Erfanianccca3152017-02-22 16:32:36 -0800700 } else {
linyuh183cb712017-12-27 17:02:37 -0800701 number = primaryContactInfo.number;
Eric Erfanianccca3152017-02-22 16:32:36 -0800702 }
703
linyuh183cb712017-12-27 17:02:37 -0800704 boolean nameIsNumber = name != null && name.equals(primaryContactInfo.number);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700705
Eric Erfanianccca3152017-02-22 16:32:36 -0800706 // DialerCall with caller that is a work contact.
linyuh183cb712017-12-27 17:02:37 -0800707 boolean isWorkContact = (primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
708 inCallScreen.setPrimary(
Android Dialerf9439102018-02-21 08:05:14 -0800709 PrimaryInfo.builder()
710 .setNumber(number)
711 .setName(primary.updateNameIfRestricted(name))
712 .setNameIsNumber(nameIsNumber)
twyencb2a9812018-03-23 18:21:13 -0700713 .setLocation(
Android Dialerf9439102018-02-21 08:05:14 -0800714 shouldShowLocationAsLabel(nameIsNumber, primaryContactInfo.shouldShowLocation)
715 ? primaryContactInfo.location
716 : null)
twyencb2a9812018-03-23 18:21:13 -0700717 .setLabel(isChildNumberShown || isCallSubjectShown ? null : primaryContactInfo.label)
Android Dialerf9439102018-02-21 08:05:14 -0800718 .setPhoto(primaryContactInfo.photo)
Android Dialerf13f0b82018-05-23 14:05:34 -0700719 .setPhotoUri(primaryContactInfo.displayPhotoUri)
Android Dialerf9439102018-02-21 08:05:14 -0800720 .setPhotoType(primaryContactInfo.photoType)
721 .setIsSipCall(primaryContactInfo.isSipCall)
722 .setIsContactPhotoShown(showContactPhoto)
723 .setIsWorkCall(hasWorkCallProperty || isWorkContact)
724 .setIsSpam(primary.isSpam())
725 .setIsLocalContact(primaryContactInfo.isLocalContact())
726 .setAnsweringDisconnectsOngoingCall(primary.answeringDisconnectsForegroundVideoCall())
727 .setShouldShowLocation(shouldShowLocation())
728 .setContactInfoLookupKey(primaryContactInfo.lookupKey)
729 .setMultimediaData(multimediaData)
730 .setShowInCallButtonGrid(true)
731 .setNumberPresentation(primary.getNumberPresentation())
732 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800733 } else {
734 // Clear the primary display info.
Android Dialerf9439102018-02-21 08:05:14 -0800735 inCallScreen.setPrimary(PrimaryInfo.empty());
Eric Erfanianccca3152017-02-22 16:32:36 -0800736 }
737
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700738 if (isInCallScreenReady) {
linyuh183cb712017-12-27 17:02:37 -0800739 inCallScreen.showLocationUi(getLocationFragment());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700740 } else {
741 LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
742 }
743 }
744
Eric Erfaniand8046e52017-04-06 09:41:50 -0700745 private static boolean shouldShowLocationAsLabel(
746 boolean nameIsNumber, boolean shouldShowLocation) {
747 if (nameIsNumber) {
748 return true;
749 }
750 if (shouldShowLocation) {
751 return true;
752 }
753 return false;
754 }
755
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700756 private Fragment getLocationFragment() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700757 if (!shouldShowLocation()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700758 return null;
759 }
760 LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
linyuh183cb712017-12-27 17:02:37 -0800761 return callLocation.getLocationFragment(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800762 }
763
764 private boolean shouldShowLocation() {
zachh190343a2018-05-31 17:30:46 -0700765 if (!ConfigProviderComponent.get(context)
766 .getConfigProvider()
wangqi1d62ab22017-11-29 14:29:31 -0800767 .getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
768 LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
769 return false;
770 }
771 if (!isPotentialEmergencyCall()) {
772 LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
773 return false;
774 }
775 if (!hasLocationPermission()) {
776 LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
777 return false;
778 }
779 if (isBatteryTooLowForEmergencyLocation()) {
780 LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
781 return false;
782 }
linyuh437ae952018-03-26 12:46:18 -0700783 if (inCallScreen.getInCallScreenFragment().getActivity().isInMultiWindowMode()) {
wangqi1d62ab22017-11-29 14:29:31 -0800784 LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
785 return false;
786 }
linyuh183cb712017-12-27 17:02:37 -0800787 if (primary.isVideoCall()) {
wangqi1d62ab22017-11-29 14:29:31 -0800788 LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
789 return false;
790 }
linyuh183cb712017-12-27 17:02:37 -0800791 if (!callLocation.canGetLocation(context)) {
wangqi1d62ab22017-11-29 14:29:31 -0800792 LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
793 return false;
794 }
795 return true;
796 }
797
798 private boolean isPotentialEmergencyCall() {
linyuh183cb712017-12-27 17:02:37 -0800799 if (isOutgoingEmergencyCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800800 LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
801 return true;
linyuh183cb712017-12-27 17:02:37 -0800802 } else if (isIncomingEmergencyCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800803 LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
804 return true;
linyuh183cb712017-12-27 17:02:37 -0800805 } else if (isIncomingEmergencyCall(secondary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800806 LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
807 return true;
808 }
809 return false;
810 }
811
812 private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
813 return call != null && !call.isIncoming() && call.isEmergencyCall();
814 }
815
816 private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
817 return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
818 }
819
820 private boolean hasLocationPermission() {
linyuh183cb712017-12-27 17:02:37 -0800821 return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
Eric Erfanianccca3152017-02-22 16:32:36 -0800822 == PackageManager.PERMISSION_GRANTED;
823 }
824
825 private boolean isBatteryTooLowForEmergencyLocation() {
826 Intent batteryStatus =
linyuh183cb712017-12-27 17:02:37 -0800827 context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Eric Erfanianccca3152017-02-22 16:32:36 -0800828 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
829 if (status == BatteryManager.BATTERY_STATUS_CHARGING
830 || status == BatteryManager.BATTERY_STATUS_FULL) {
831 // Plugged in or full battery
832 return false;
833 }
834 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
835 int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
836 float batteryPercent = (100f * level) / scale;
837 long threshold =
zachh190343a2018-05-31 17:30:46 -0700838 ConfigProviderComponent.get(context)
839 .getConfigProvider()
Eric Erfanianccca3152017-02-22 16:32:36 -0800840 .getLong(
841 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
842 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
843 LogUtil.i(
844 "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
845 "percent charged: " + batteryPercent + ", min required charge: " + threshold);
846 return batteryPercent < threshold;
847 }
848
849 private void updateSecondaryDisplayInfo() {
linyuh183cb712017-12-27 17:02:37 -0800850 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800851 return;
852 }
853
linyuh183cb712017-12-27 17:02:37 -0800854 if (secondary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800855 // Clear the secondary display info.
Android Dialer9e335e22018-03-01 08:27:39 -0800856 inCallScreen.setSecondary(SecondaryInfo.builder().setIsFullscreen(isFullscreen).build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800857 return;
858 }
859
linyuh183cb712017-12-27 17:02:37 -0800860 if (secondary.isMergeInProcess()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700861 LogUtil.i(
862 "CallCardPresenter.updateSecondaryDisplayInfo",
863 "secondary call is merge in process, clearing info");
Android Dialer9e335e22018-03-01 08:27:39 -0800864 inCallScreen.setSecondary(SecondaryInfo.builder().setIsFullscreen(isFullscreen).build());
Eric Erfanian2ca43182017-08-31 06:57:16 -0700865 return;
866 }
867
linyuh183cb712017-12-27 17:02:37 -0800868 if (secondary.isConferenceCall()) {
869 inCallScreen.setSecondary(
Android Dialer9e335e22018-03-01 08:27:39 -0800870 SecondaryInfo.builder()
871 .setShouldShow(true)
872 .setName(
873 CallerInfoUtils.getConferenceString(
874 context, secondary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)))
875 .setProviderLabel(secondary.getCallProviderLabel())
876 .setIsConference(true)
877 .setIsVideoCall(secondary.isVideoCall())
878 .setIsFullscreen(isFullscreen)
879 .build());
linyuh183cb712017-12-27 17:02:37 -0800880 } else if (secondaryContactInfo != null) {
881 LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + secondaryContactInfo);
882 String name = getNameForCall(secondaryContactInfo);
883 boolean nameIsNumber = name != null && name.equals(secondaryContactInfo.number);
884 inCallScreen.setSecondary(
Android Dialer9e335e22018-03-01 08:27:39 -0800885 SecondaryInfo.builder()
886 .setShouldShow(true)
887 .setName(secondary.updateNameIfRestricted(name))
888 .setNameIsNumber(nameIsNumber)
889 .setLabel(secondaryContactInfo.label)
890 .setProviderLabel(secondary.getCallProviderLabel())
891 .setIsVideoCall(secondary.isVideoCall())
892 .setIsFullscreen(isFullscreen)
893 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800894 } else {
895 // Clear the secondary display info.
Android Dialer9e335e22018-03-01 08:27:39 -0800896 inCallScreen.setSecondary(SecondaryInfo.builder().setIsFullscreen(isFullscreen).build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800897 }
898 }
899
900 /** Returns the gateway number for any existing outgoing call. */
901 private String getGatewayNumber() {
902 if (hasOutgoingGatewayCall()) {
linyuh183cb712017-12-27 17:02:37 -0800903 return DialerCall.getNumberFromHandle(primary.getGatewayInfo().getGatewayAddress());
Eric Erfanianccca3152017-02-22 16:32:36 -0800904 }
905 return null;
906 }
907
908 /**
909 * Returns the label (line of text above the number/name) for any given call. For example,
910 * "calling via [Account/Google Voice]" for outgoing calls.
911 */
912 private String getConnectionLabel() {
linyuh183cb712017-12-27 17:02:37 -0800913 if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800914 != PackageManager.PERMISSION_GRANTED) {
915 return null;
916 }
linyuh183cb712017-12-27 17:02:37 -0800917 StatusHints statusHints = primary.getStatusHints();
Eric Erfanianccca3152017-02-22 16:32:36 -0800918 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
919 return statusHints.getLabel().toString();
920 }
921
922 if (hasOutgoingGatewayCall() && getUi() != null) {
923 // Return the label for the gateway app on outgoing calls.
linyuh183cb712017-12-27 17:02:37 -0800924 final PackageManager pm = context.getPackageManager();
Eric Erfanianccca3152017-02-22 16:32:36 -0800925 try {
926 ApplicationInfo info =
linyuh183cb712017-12-27 17:02:37 -0800927 pm.getApplicationInfo(primary.getGatewayInfo().getGatewayProviderPackageName(), 0);
Eric Erfanianccca3152017-02-22 16:32:36 -0800928 return pm.getApplicationLabel(info).toString();
929 } catch (PackageManager.NameNotFoundException e) {
930 LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
931 return null;
932 }
933 }
linyuh183cb712017-12-27 17:02:37 -0800934 return primary.getCallProviderLabel();
Eric Erfanianccca3152017-02-22 16:32:36 -0800935 }
936
twyen4fde0ac2018-03-22 18:04:23 -0700937 @Nullable
938 private SuggestionProvider.Reason getSimSuggestionReason() {
939 String value =
940 primary.getIntentExtras().getString(SuggestionProvider.EXTRA_SIM_SUGGESTION_REASON);
941 if (value == null) {
942 return null;
943 }
944 try {
945 return SuggestionProvider.Reason.valueOf(value);
946 } catch (IllegalArgumentException e) {
947 LogUtil.e("CallCardPresenter.getConnectionLabel", "unknown reason " + value);
948 return null;
949 }
950 }
951
Eric Erfanianccca3152017-02-22 16:32:36 -0800952 private Drawable getCallStateIcon() {
953 // Return connection icon if one exists.
linyuh183cb712017-12-27 17:02:37 -0800954 StatusHints statusHints = primary.getStatusHints();
Eric Erfanianccca3152017-02-22 16:32:36 -0800955 if (statusHints != null && statusHints.getIcon() != null) {
linyuh183cb712017-12-27 17:02:37 -0800956 Drawable icon = statusHints.getIcon().loadDrawable(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800957 if (icon != null) {
958 return icon;
959 }
960 }
961
962 return null;
963 }
964
965 private boolean hasOutgoingGatewayCall() {
966 // We only display the gateway information while STATE_DIALING so return false for any other
967 // call state.
968 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
969 // is also called after a contact search completes (call is not present yet). Split the
970 // UI update so it can receive independent updates.
linyuh183cb712017-12-27 17:02:37 -0800971 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800972 return false;
973 }
wangqibb94ca62018-04-27 14:34:04 -0700974 return DialerCallState.isDialing(primary.getState())
linyuh183cb712017-12-27 17:02:37 -0800975 && primary.getGatewayInfo() != null
976 && !primary.getGatewayInfo().isEmpty();
Eric Erfanianccca3152017-02-22 16:32:36 -0800977 }
978
979 /** Gets the name to display for the call. */
Eric Erfanian2ca43182017-08-31 06:57:16 -0700980 private String getNameForCall(ContactCacheEntry contactInfo) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800981 String preferredName =
twyen5578d922018-06-25 12:36:26 -0700982 ContactsComponent.get(context)
983 .contactDisplayPreferences()
984 .getDisplayName(contactInfo.namePrimary, contactInfo.nameAlternative);
Eric Erfanianccca3152017-02-22 16:32:36 -0800985 if (TextUtils.isEmpty(preferredName)) {
wangqia4623702018-04-02 14:23:38 -0700986 return TextUtils.isEmpty(contactInfo.number)
987 ? null
988 : BidiFormatter.getInstance()
989 .unicodeWrap(contactInfo.number, TextDirectionHeuristics.LTR);
Eric Erfanianccca3152017-02-22 16:32:36 -0800990 }
991 return preferredName;
992 }
993
Eric Erfanianccca3152017-02-22 16:32:36 -0800994 @Override
995 public void onSecondaryInfoClicked() {
linyuh183cb712017-12-27 17:02:37 -0800996 if (secondary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800997 LogUtil.e(
998 "CallCardPresenter.onSecondaryInfoClicked",
999 "secondary info clicked but no secondary call.");
1000 return;
1001 }
1002
linyuh183cb712017-12-27 17:02:37 -08001003 Logger.get(context)
Eric Erfanian2ca43182017-08-31 06:57:16 -07001004 .logCallImpression(
1005 DialerImpression.Type.IN_CALL_SWAP_SECONDARY_BUTTON_PRESSED,
linyuh183cb712017-12-27 17:02:37 -08001006 primary.getUniqueCallId(),
1007 primary.getTimeAddedMs());
Eric Erfanianccca3152017-02-22 16:32:36 -08001008 LogUtil.i(
linyuh183cb712017-12-27 17:02:37 -08001009 "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + secondary);
1010 secondary.unhold();
Eric Erfanianccca3152017-02-22 16:32:36 -08001011 }
1012
1013 @Override
1014 public void onEndCallClicked() {
linyuh183cb712017-12-27 17:02:37 -08001015 LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + primary);
1016 if (primary != null) {
1017 primary.disconnect();
Eric Erfanianccca3152017-02-22 16:32:36 -08001018 }
linyuh183cb712017-12-27 17:02:37 -08001019 PostCall.onDisconnectPressed(context);
Eric Erfanianccca3152017-02-22 16:32:36 -08001020 }
1021
1022 /**
1023 * Handles a change to the fullscreen mode of the in-call UI.
1024 *
1025 * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
1026 */
1027 @Override
1028 public void onFullscreenModeChanged(boolean isFullscreenMode) {
linyuh183cb712017-12-27 17:02:37 -08001029 isFullscreen = isFullscreenMode;
1030 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001031 return;
1032 }
1033 maybeShowManageConferenceCallButton();
1034 }
1035
1036 private boolean isPrimaryCallActive() {
wangqibb94ca62018-04-27 14:34:04 -07001037 return primary != null && primary.getState() == DialerCallState.ACTIVE;
Eric Erfanianccca3152017-02-22 16:32:36 -08001038 }
1039
Eric Erfanianccca3152017-02-22 16:32:36 -08001040 private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
1041 if (primary == null) {
1042 return false;
1043 }
wangqibb94ca62018-04-27 14:34:04 -07001044 if ((!DialerCallState.isConnectingOrConnected(callState)
1045 && callState != DialerCallState.DISCONNECTING
1046 && callState != DialerCallState.DISCONNECTED)
1047 || callState == DialerCallState.INCOMING) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001048 return false;
1049 }
linyuh183cb712017-12-27 17:02:37 -08001050 if (this.primary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001051 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001052 return false;
1053 }
1054 return true;
1055 }
1056
1057 @Override
1058 public void onInCallScreenResumed() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001059 updatePrimaryDisplayInfo();
1060
Eric Erfanianccca3152017-02-22 16:32:36 -08001061 if (shouldSendAccessibilityEvent) {
1062 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1063 }
1064 }
1065
Eric Erfaniand8046e52017-04-06 09:41:50 -07001066 @Override
Eric Erfanian2ca43182017-08-31 06:57:16 -07001067 public void onInCallScreenPaused() {}
Eric Erfaniand8046e52017-04-06 09:41:50 -07001068
Eric Erfanianccca3152017-02-22 16:32:36 -08001069 static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
1070 AccessibilityManager am =
1071 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
1072 if (!am.isEnabled()) {
1073 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
1074 return false;
1075 }
1076 if (inCallScreen == null) {
1077 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
1078 return false;
1079 }
1080 Fragment fragment = inCallScreen.getInCallScreenFragment();
1081 if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
1082 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
1083 return false;
1084 }
1085
1086 DisplayManager displayManager =
1087 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1088 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1089 boolean screenIsOn = display.getState() == Display.STATE_ON;
1090 LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
1091 if (!screenIsOn) {
1092 return false;
1093 }
1094
1095 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1096 inCallScreen.dispatchPopulateAccessibilityEvent(event);
1097 View view = inCallScreen.getInCallScreenFragment().getView();
1098 view.getParent().requestSendAccessibilityEvent(view, event);
1099 return true;
1100 }
1101
1102 private void maybeSendAccessibilityEvent(
1103 InCallState oldState, final InCallState newState, boolean primaryChanged) {
1104 shouldSendAccessibilityEvent = false;
linyuh183cb712017-12-27 17:02:37 -08001105 if (context == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001106 return;
1107 }
1108 final AccessibilityManager am =
linyuh183cb712017-12-27 17:02:37 -08001109 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
Eric Erfanianccca3152017-02-22 16:32:36 -08001110 if (!am.isEnabled()) {
1111 return;
1112 }
1113 // Announce the current call if it's new incoming/outgoing call or primary call is changed
1114 // due to switching calls between two ongoing calls (one is on hold).
1115 if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
1116 || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
1117 || primaryChanged) {
1118 LogUtil.i(
1119 "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
1120 shouldSendAccessibilityEvent = true;
1121 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1122 }
1123 }
1124
1125 /**
1126 * Determines whether the call subject should be visible on the UI. For the call subject to be
1127 * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
1128 *
1129 * @param call The call.
1130 * @return {@code true} if the subject should be shown, {@code false} otherwise.
1131 */
1132 private boolean shouldShowCallSubject(DialerCall call) {
1133 if (call == null) {
1134 return false;
1135 }
1136
1137 boolean isIncomingOrWaiting =
wangqibb94ca62018-04-27 14:34:04 -07001138 primary.getState() == DialerCallState.INCOMING
1139 || primary.getState() == DialerCallState.CALL_WAITING;
Eric Erfanianccca3152017-02-22 16:32:36 -08001140 return isIncomingOrWaiting
1141 && !TextUtils.isEmpty(call.getCallSubject())
1142 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
1143 && call.isCallSubjectSupported();
1144 }
1145
1146 /**
1147 * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
1148 * call with a subject.
1149 *
1150 * @param call The call
1151 * @return {@code true} if the toast should be shown, {@code false} otherwise.
1152 */
1153 private boolean shouldShowNoteSentToast(DialerCall call) {
1154 return call != null
1155 && hasCallSubject(call)
wangqibb94ca62018-04-27 14:34:04 -07001156 && (call.getState() == DialerCallState.DIALING
1157 || call.getState() == DialerCallState.CONNECTING);
Eric Erfanianccca3152017-02-22 16:32:36 -08001158 }
1159
1160 private InCallScreen getUi() {
linyuh183cb712017-12-27 17:02:37 -08001161 return inCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -08001162 }
1163
Android Dialerf9439102018-02-21 08:05:14 -08001164 /** Callback for contact lookup. */
Eric Erfanianccca3152017-02-22 16:32:36 -08001165 public static class ContactLookupCallback implements ContactInfoCacheCallback {
1166
linyuh183cb712017-12-27 17:02:37 -08001167 private final WeakReference<CallCardPresenter> callCardPresenter;
1168 private final boolean isPrimary;
Eric Erfanianccca3152017-02-22 16:32:36 -08001169
1170 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
linyuh183cb712017-12-27 17:02:37 -08001171 this.callCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
1172 this.isPrimary = isPrimary;
Eric Erfanianccca3152017-02-22 16:32:36 -08001173 }
1174
1175 @Override
1176 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
linyuh183cb712017-12-27 17:02:37 -08001177 CallCardPresenter presenter = callCardPresenter.get();
Eric Erfanianccca3152017-02-22 16:32:36 -08001178 if (presenter != null) {
linyuh183cb712017-12-27 17:02:37 -08001179 presenter.onContactInfoComplete(callId, entry, isPrimary);
Eric Erfanianccca3152017-02-22 16:32:36 -08001180 }
1181 }
1182
1183 @Override
1184 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
linyuh183cb712017-12-27 17:02:37 -08001185 CallCardPresenter presenter = callCardPresenter.get();
Eric Erfanianccca3152017-02-22 16:32:36 -08001186 if (presenter != null) {
1187 presenter.onImageLoadComplete(callId, entry);
1188 }
1189 }
1190 }
1191}