blob: 2f88c88362a8d12e646ce562d79189f0b454b893 [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;
39import android.text.TextUtils;
40import android.view.Display;
41import android.view.View;
42import android.view.accessibility.AccessibilityEvent;
43import android.view.accessibility.AccessibilityManager;
44import com.android.contacts.common.ContactsUtils;
45import com.android.contacts.common.preference.ContactsPreferences;
46import com.android.contacts.common.util.ContactDisplayUtils;
47import 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.configprovider.ConfigProviderBindings;
Eric Erfanian8369df02017-05-03 10:27:13 -070050import com.android.dialer.logging.DialerImpression;
Eric Erfaniand8046e52017-04-06 09:41:50 -070051import com.android.dialer.logging.Logger;
Eric Erfanianccca3152017-02-22 16:32:36 -080052import com.android.dialer.multimedia.MultimediaData;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070053import com.android.dialer.oem.MotorolaUtils;
wangqi97539352017-09-25 11:15:16 -070054import com.android.dialer.phonenumberutil.PhoneNumberHelper;
Eric Erfanian2ca43182017-08-31 06:57:16 -070055import com.android.dialer.postcall.PostCall;
twyen4fde0ac2018-03-22 18:04:23 -070056import com.android.dialer.preferredsim.suggestion.SuggestionProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080057import com.android.incallui.ContactInfoCache.ContactCacheEntry;
58import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
59import com.android.incallui.InCallPresenter.InCallDetailsListener;
60import com.android.incallui.InCallPresenter.InCallEventListener;
61import com.android.incallui.InCallPresenter.InCallState;
62import com.android.incallui.InCallPresenter.InCallStateListener;
63import com.android.incallui.InCallPresenter.IncomingCallListener;
64import com.android.incallui.call.CallList;
65import com.android.incallui.call.DialerCall;
Eric Erfanian2ca43182017-08-31 06:57:16 -070066import com.android.incallui.call.DialerCall.State;
Eric Erfanianccca3152017-02-22 16:32:36 -080067import com.android.incallui.call.DialerCallListener;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070068import com.android.incallui.calllocation.CallLocation;
69import com.android.incallui.calllocation.CallLocationComponent;
Eric Erfanianccca3152017-02-22 16:32:36 -080070import com.android.incallui.incall.protocol.ContactPhotoType;
71import com.android.incallui.incall.protocol.InCallScreen;
72import com.android.incallui.incall.protocol.InCallScreenDelegate;
73import com.android.incallui.incall.protocol.PrimaryCallState;
Eric Erfanian2ca43182017-08-31 06:57:16 -070074import com.android.incallui.incall.protocol.PrimaryCallState.ButtonState;
Eric Erfanianccca3152017-02-22 16:32:36 -080075import com.android.incallui.incall.protocol.PrimaryInfo;
76import com.android.incallui.incall.protocol.SecondaryInfo;
Eric Erfanian90508232017-03-24 09:31:16 -070077import com.android.incallui.videotech.utils.SessionModificationState;
Eric Erfanianccca3152017-02-22 16:32:36 -080078import java.lang.ref.WeakReference;
79
80/**
81 * Controller for the Call Card Fragment. This class listens for changes to InCallState and passes
82 * it along to the fragment.
83 */
84public class CallCardPresenter
85 implements InCallStateListener,
86 IncomingCallListener,
87 InCallDetailsListener,
88 InCallEventListener,
89 InCallScreenDelegate,
Eric Erfanian2ca43182017-08-31 06:57:16 -070090 DialerCallListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080091
92 /**
93 * Amount of time to wait before sending an announcement via the accessibility manager. When the
94 * call state changes to an outgoing or incoming state for the first time, the UI can often be
95 * changing due to call updates or contact lookup. This allows the UI to settle to a stable state
96 * to ensure that the correct information is announced.
97 */
98 private static final long ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS = 500;
99
100 /** Flag to allow the user's current location to be shown during emergency calls. */
101 private static final String CONFIG_ENABLE_EMERGENCY_LOCATION = "config_enable_emergency_location";
102
103 private static final boolean CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT = true;
104
105 /**
106 * Make it possible to not get location during an emergency call if the battery is too low, since
107 * doing so could trigger gps and thus potentially cause the phone to die in the middle of the
108 * call.
109 */
110 private static final String CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION =
111 "min_battery_percent_for_emergency_location";
112
113 private static final long CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT = 10;
114
linyuh183cb712017-12-27 17:02:37 -0800115 private final Context context;
Eric Erfanianccca3152017-02-22 16:32:36 -0800116 private final Handler handler = new Handler();
117
linyuh183cb712017-12-27 17:02:37 -0800118 private DialerCall primary;
wangqidd5f1a52018-01-23 18:01:00 -0800119 private String primaryNumber;
linyuh183cb712017-12-27 17:02:37 -0800120 private DialerCall secondary;
wangqidd5f1a52018-01-23 18:01:00 -0800121 private String secondaryNumber;
linyuh183cb712017-12-27 17:02:37 -0800122 private ContactCacheEntry primaryContactInfo;
123 private ContactCacheEntry secondaryContactInfo;
124 @Nullable private ContactsPreferences contactsPreferences;
125 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;
160 contactsPreferences = ContactsPreferencesFactory.newContactsPreferences(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800161
162 // Call may be null if disconnect happened already.
163 DialerCall call = CallList.getInstance().getFirstCall();
164 if (call != null) {
linyuh183cb712017-12-27 17:02:37 -0800165 primary = call;
166 if (shouldShowNoteSentToast(primary)) {
167 this.inCallScreen.showNoteSentToast();
Eric Erfanianccca3152017-02-22 16:32:36 -0800168 }
169 call.addListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800170 // start processing lookups right away.
171 if (!call.isConferenceCall()) {
172 startContactInfoSearch(call, true, call.getState() == DialerCall.State.INCOMING);
173 } else {
174 updateContactEntry(null, true);
175 }
176 }
177
178 onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
179 }
180
181 @Override
182 public void onInCallScreenReady() {
wangqi385a5a12017-09-28 10:44:54 -0700183 LogUtil.i("CallCardPresenter.onInCallScreenReady", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800184 Assert.checkState(!isInCallScreenReady);
linyuh183cb712017-12-27 17:02:37 -0800185 if (contactsPreferences != null) {
186 contactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
Eric Erfanianccca3152017-02-22 16:32:36 -0800187 }
188
Eric Erfanianccca3152017-02-22 16:32:36 -0800189 // Contact search may have completed before ui is ready.
linyuh183cb712017-12-27 17:02:37 -0800190 if (primaryContactInfo != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800191 updatePrimaryDisplayInfo();
192 }
193
194 // Register for call state changes last
195 InCallPresenter.getInstance().addListener(this);
196 InCallPresenter.getInstance().addIncomingCallListener(this);
197 InCallPresenter.getInstance().addDetailsListener(this);
198 InCallPresenter.getInstance().addInCallEventListener(this);
199 isInCallScreenReady = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700200
Eric Erfaniand8046e52017-04-06 09:41:50 -0700201 // Log location impressions
linyuh183cb712017-12-27 17:02:37 -0800202 if (isOutgoingEmergencyCall(primary)) {
203 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_NEW_EMERGENCY_CALL);
204 } else if (isIncomingEmergencyCall(primary) || isIncomingEmergencyCall(secondary)) {
205 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_CALLBACK);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700206 }
207
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700208 // Showing the location may have been skipped if the UI wasn't ready during previous layout.
209 if (shouldShowLocation()) {
linyuh183cb712017-12-27 17:02:37 -0800210 inCallScreen.showLocationUi(getLocationFragment());
Eric Erfaniand8046e52017-04-06 09:41:50 -0700211
212 // Log location impressions
213 if (!hasLocationPermission()) {
linyuh183cb712017-12-27 17:02:37 -0800214 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_NO_LOCATION_PERMISSION);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700215 } else if (isBatteryTooLowForEmergencyLocation()) {
linyuh183cb712017-12-27 17:02:37 -0800216 Logger.get(context)
Eric Erfaniand8046e52017-04-06 09:41:50 -0700217 .logImpression(DialerImpression.Type.EMERGENCY_BATTERY_TOO_LOW_TO_GET_LOCATION);
linyuh183cb712017-12-27 17:02:37 -0800218 } else if (!callLocation.canGetLocation(context)) {
219 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_CANT_GET_LOCATION);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700220 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700221 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800222 }
223
224 @Override
225 public void onInCallScreenUnready() {
wangqi385a5a12017-09-28 10:44:54 -0700226 LogUtil.i("CallCardPresenter.onInCallScreenUnready", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800227 Assert.checkState(isInCallScreenReady);
228
Eric Erfanianccca3152017-02-22 16:32:36 -0800229 // stop getting call state changes
230 InCallPresenter.getInstance().removeListener(this);
231 InCallPresenter.getInstance().removeIncomingCallListener(this);
232 InCallPresenter.getInstance().removeDetailsListener(this);
233 InCallPresenter.getInstance().removeInCallEventListener(this);
linyuh183cb712017-12-27 17:02:37 -0800234 if (primary != null) {
235 primary.removeListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800236 }
237
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700238 callLocation.close();
239
linyuh183cb712017-12-27 17:02:37 -0800240 primary = null;
241 primaryContactInfo = null;
242 secondaryContactInfo = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800243 isInCallScreenReady = false;
244 }
245
246 @Override
247 public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
248 // same logic should happen as with onStateChange()
249 onStateChange(oldState, newState, CallList.getInstance());
250 }
251
252 @Override
253 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700254 Trace.beginSection("CallCardPresenter.onStateChange");
255 LogUtil.v("CallCardPresenter.onStateChange", "oldState: %s, newState: %s", oldState, newState);
linyuh183cb712017-12-27 17:02:37 -0800256 if (inCallScreen == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700257 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800258 return;
259 }
260
261 DialerCall primary = null;
262 DialerCall secondary = null;
263
264 if (newState == InCallState.INCOMING) {
265 primary = callList.getIncomingCall();
266 } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
267 primary = callList.getOutgoingCall();
268 if (primary == null) {
269 primary = callList.getPendingOutgoingCall();
270 }
271
272 // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
273 // highest priority call to display as the secondary call.
yuegb9103042018-03-30 12:12:25 -0700274 secondary = InCallPresenter.getCallToDisplay(callList, null, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800275 } else if (newState == InCallState.INCALL) {
yuegb9103042018-03-30 12:12:25 -0700276 primary = InCallPresenter.getCallToDisplay(callList, null, false);
277 secondary = InCallPresenter.getCallToDisplay(callList, primary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800278 }
279
280 LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
281 LogUtil.v("CallCardPresenter.onStateChange", "secondary call: " + secondary);
wangqidd5f1a52018-01-23 18:01:00 -0800282 String primaryNumber = null;
283 String secondaryNumber = null;
284 if (primary != null) {
285 primaryNumber = primary.getNumber();
286 }
287 if (secondary != null) {
288 secondaryNumber = secondary.getNumber();
289 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800290
291 final boolean primaryChanged =
linyuh183cb712017-12-27 17:02:37 -0800292 !(DialerCall.areSame(this.primary, primary)
wangqidd5f1a52018-01-23 18:01:00 -0800293 && TextUtils.equals(this.primaryNumber, primaryNumber));
Eric Erfanianccca3152017-02-22 16:32:36 -0800294 final boolean secondaryChanged =
linyuh183cb712017-12-27 17:02:37 -0800295 !(DialerCall.areSame(this.secondary, secondary)
wangqidd5f1a52018-01-23 18:01:00 -0800296 && TextUtils.equals(this.secondaryNumber, secondaryNumber));
Eric Erfanianccca3152017-02-22 16:32:36 -0800297
linyuh183cb712017-12-27 17:02:37 -0800298 this.secondary = secondary;
wangqidd5f1a52018-01-23 18:01:00 -0800299 this.secondaryNumber = secondaryNumber;
linyuh183cb712017-12-27 17:02:37 -0800300 DialerCall previousPrimary = this.primary;
301 this.primary = primary;
wangqidd5f1a52018-01-23 18:01:00 -0800302 this.primaryNumber = primaryNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800303
linyuh183cb712017-12-27 17:02:37 -0800304 if (this.primary != null) {
linyuh183cb712017-12-27 17:02:37 -0800305 inCallScreen.updateInCallScreenColors();
Eric Erfanianccca3152017-02-22 16:32:36 -0800306 }
307
308 if (primaryChanged && shouldShowNoteSentToast(primary)) {
linyuh183cb712017-12-27 17:02:37 -0800309 inCallScreen.showNoteSentToast();
Eric Erfanianccca3152017-02-22 16:32:36 -0800310 }
311
312 // Refresh primary call information if either:
313 // 1. Primary call changed.
314 // 2. The call's ability to manage conference has changed.
315 if (shouldRefreshPrimaryInfo(primaryChanged)) {
316 // primary call has changed
317 if (previousPrimary != null) {
318 previousPrimary.removeListener(this);
319 }
linyuh183cb712017-12-27 17:02:37 -0800320 this.primary.addListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800321
linyuh183cb712017-12-27 17:02:37 -0800322 primaryContactInfo =
Eric Erfanianccca3152017-02-22 16:32:36 -0800323 ContactInfoCache.buildCacheEntryFromCall(
linyuh183cb712017-12-27 17:02:37 -0800324 context, this.primary, this.primary.getState() == DialerCall.State.INCOMING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800325 updatePrimaryDisplayInfo();
linyuh183cb712017-12-27 17:02:37 -0800326 maybeStartSearch(this.primary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800327 }
328
linyuh183cb712017-12-27 17:02:37 -0800329 if (previousPrimary != null && this.primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800330 previousPrimary.removeListener(this);
331 }
332
wangqic8cf79e2017-10-17 09:21:00 -0700333 if (secondaryChanged) {
linyuh183cb712017-12-27 17:02:37 -0800334 if (this.secondary == null) {
wangqic8cf79e2017-10-17 09:21:00 -0700335 // Secondary call may have ended. Update the ui.
linyuh183cb712017-12-27 17:02:37 -0800336 secondaryContactInfo = null;
wangqic8cf79e2017-10-17 09:21:00 -0700337 updateSecondaryDisplayInfo();
338 } else {
339 // secondary call has changed
linyuh183cb712017-12-27 17:02:37 -0800340 secondaryContactInfo =
wangqic8cf79e2017-10-17 09:21:00 -0700341 ContactInfoCache.buildCacheEntryFromCall(
linyuh183cb712017-12-27 17:02:37 -0800342 context, this.secondary, this.secondary.getState() == DialerCall.State.INCOMING);
wangqic8cf79e2017-10-17 09:21:00 -0700343 updateSecondaryDisplayInfo();
linyuh183cb712017-12-27 17:02:37 -0800344 maybeStartSearch(this.secondary, false);
wangqic8cf79e2017-10-17 09:21:00 -0700345 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800346 }
347
348 // Set the call state
349 int callState = DialerCall.State.IDLE;
linyuh183cb712017-12-27 17:02:37 -0800350 if (this.primary != null) {
351 callState = this.primary.getState();
Eric Erfanianccca3152017-02-22 16:32:36 -0800352 updatePrimaryCallState();
353 } else {
Android Dialer31fe9982018-02-26 13:29:09 -0800354 getUi().setCallState(PrimaryCallState.empty());
Eric Erfanianccca3152017-02-22 16:32:36 -0800355 }
356
357 maybeShowManageConferenceCallButton();
358
359 // Hide the end call button instantly if we're receiving an incoming call.
360 getUi()
361 .setEndCallButtonEnabled(
linyuh183cb712017-12-27 17:02:37 -0800362 shouldShowEndCallButton(this.primary, callState),
Eric Erfanianccca3152017-02-22 16:32:36 -0800363 callState != DialerCall.State.INCOMING /* animate */);
364
365 maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700366 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800367 }
368
369 @Override
370 public void onDetailsChanged(DialerCall call, Details details) {
371 updatePrimaryCallState();
372
373 if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
374 != details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
375 maybeShowManageConferenceCallButton();
376 }
377 }
378
379 @Override
380 public void onDialerCallDisconnect() {}
381
382 @Override
383 public void onDialerCallUpdate() {
384 // No-op; specific call updates handled elsewhere.
385 }
386
387 @Override
388 public void onWiFiToLteHandover() {}
389
390 @Override
391 public void onHandoverToWifiFailure() {}
392
Eric Erfanianc857f902017-05-15 14:05:33 -0700393 @Override
394 public void onInternationalCallOnWifi() {}
395
Eric Erfanian2ca43182017-08-31 06:57:16 -0700396 @Override
397 public void onEnrichedCallSessionUpdate() {
398 LogUtil.enterBlock("CallCardPresenter.onEnrichedCallSessionUpdate");
399 updatePrimaryDisplayInfo();
400 }
401
Eric Erfanianccca3152017-02-22 16:32:36 -0800402 /** Handles a change to the child number by refreshing the primary call info. */
403 @Override
404 public void onDialerCallChildNumberChange() {
405 LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
406
linyuh183cb712017-12-27 17:02:37 -0800407 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800408 return;
409 }
410 updatePrimaryDisplayInfo();
411 }
412
413 /** Handles a change to the last forwarding number by refreshing the primary call info. */
414 @Override
415 public void onDialerCallLastForwardedNumberChange() {
416 LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
417
linyuh183cb712017-12-27 17:02:37 -0800418 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800419 return;
420 }
421 updatePrimaryDisplayInfo();
422 updatePrimaryCallState();
423 }
424
425 @Override
426 public void onDialerCallUpgradeToVideo() {}
427
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700428 /** Handles a change to the session modification state for a call. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800429 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700430 public void onDialerCallSessionModificationStateChange() {
431 LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
Eric Erfanianccca3152017-02-22 16:32:36 -0800432
linyuh183cb712017-12-27 17:02:37 -0800433 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800434 return;
435 }
436 getUi()
437 .setEndCallButtonEnabled(
linyuh183cb712017-12-27 17:02:37 -0800438 primary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700439 != SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800440 true /* shouldAnimate */);
441 updatePrimaryCallState();
442 }
443
Eric Erfanianccca3152017-02-22 16:32:36 -0800444 private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
linyuh183cb712017-12-27 17:02:37 -0800445 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800446 return false;
447 }
448 return primaryChanged
linyuh183cb712017-12-27 17:02:37 -0800449 || inCallScreen.isManageConferenceVisible() != shouldShowManageConference();
Eric Erfanianccca3152017-02-22 16:32:36 -0800450 }
451
452 private void updatePrimaryCallState() {
linyuh183cb712017-12-27 17:02:37 -0800453 if (getUi() != null && primary != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800454 boolean isWorkCall =
linyuh183cb712017-12-27 17:02:37 -0800455 primary.hasProperty(PROPERTY_ENTERPRISE_CALL)
456 || (primaryContactInfo != null
457 && primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
Eric Erfanianccca3152017-02-22 16:32:36 -0800458 boolean isHdAudioCall =
linyuh183cb712017-12-27 17:02:37 -0800459 isPrimaryCallActive() && primary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700460 boolean isAttemptingHdAudioCall =
461 !isHdAudioCall
linyuh183cb712017-12-27 17:02:37 -0800462 && !primary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
463 && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(context);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700464
linyuh183cb712017-12-27 17:02:37 -0800465 boolean isBusiness = primaryContactInfo != null && primaryContactInfo.isBusiness;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700466
Eric Erfanianccca3152017-02-22 16:32:36 -0800467 // Check for video state change and update the visibility of the contact photo. The contact
468 // photo is hidden when the incoming video surface is shown.
469 // The contact photo visibility can also change in setPrimary().
470 boolean shouldShowContactPhoto =
linyuh183cb712017-12-27 17:02:37 -0800471 !VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800472 getUi()
473 .setCallState(
Android Dialer31fe9982018-02-26 13:29:09 -0800474 PrimaryCallState.builder()
475 .setState(primary.getState())
476 .setIsVideoCall(primary.isVideoCall())
477 .setSessionModificationState(primary.getVideoTech().getSessionModificationState())
478 .setDisconnectCause(primary.getDisconnectCause())
479 .setConnectionLabel(getConnectionLabel())
twyen4fde0ac2018-03-22 18:04:23 -0700480 .setSimSuggestionReason(getSimSuggestionReason())
Android Dialer31fe9982018-02-26 13:29:09 -0800481 .setConnectionIcon(getCallStateIcon())
482 .setGatewayNumber(getGatewayNumber())
483 .setCallSubject(shouldShowCallSubject(primary) ? primary.getCallSubject() : null)
484 .setCallbackNumber(
485 PhoneNumberHelper.formatNumber(
linyuhb06d0092018-03-01 15:05:36 -0800486 context, primary.getCallbackNumber(), primary.getSimCountryIso()))
Android Dialer31fe9982018-02-26 13:29:09 -0800487 .setIsWifi(primary.hasProperty(Details.PROPERTY_WIFI))
488 .setIsConference(
489 primary.isConferenceCall()
490 && !primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE))
491 .setIsWorkCall(isWorkCall)
492 .setIsHdAttempting(isAttemptingHdAudioCall)
493 .setIsHdAudioCall(isHdAudioCall)
494 .setIsForwardedNumber(
495 !TextUtils.isEmpty(primary.getLastForwardedNumber())
496 || primary.isCallForwarded())
497 .setShouldShowContactPhoto(shouldShowContactPhoto)
498 .setConnectTimeMillis(primary.getConnectTimeMillis())
499 .setIsVoiceMailNumber(primary.isVoiceMailNumber())
500 .setIsRemotelyHeld(primary.isRemotelyHeld())
501 .setIsBusinessNumber(isBusiness)
502 .setSupportsCallOnHold(supports2ndCallOnHold())
503 .setSwapToSecondaryButtonState(getSwapToSecondaryButtonState())
504 .setIsAssistedDialed(primary.isAssistedDialed())
505 .setCustomLabel(null)
506 .setAssistedDialingExtras(primary.getAssistedDialingExtras())
507 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800508
509 InCallActivity activity =
linyuh183cb712017-12-27 17:02:37 -0800510 (InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
Eric Erfanianccca3152017-02-22 16:32:36 -0800511 if (activity != null) {
512 activity.onPrimaryCallStateChanged();
513 }
514 }
515 }
516
Eric Erfanian2ca43182017-08-31 06:57:16 -0700517 private @ButtonState int getSwapToSecondaryButtonState() {
linyuh183cb712017-12-27 17:02:37 -0800518 if (secondary == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700519 return ButtonState.NOT_SUPPORT;
520 }
linyuh183cb712017-12-27 17:02:37 -0800521 if (primary.getState() == State.ACTIVE) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700522 return ButtonState.ENABLED;
523 }
524 return ButtonState.DISABLED;
525 }
526
Eric Erfanianccca3152017-02-22 16:32:36 -0800527 /** Only show the conference call button if we can manage the conference. */
528 private void maybeShowManageConferenceCallButton() {
529 getUi().showManageConferenceCallButton(shouldShowManageConference());
530 }
531
532 /**
533 * Determines if the manage conference button should be visible, based on the current primary
534 * call.
535 *
536 * @return {@code True} if the manage conference button should be visible.
537 */
538 private boolean shouldShowManageConference() {
linyuh183cb712017-12-27 17:02:37 -0800539 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800540 return false;
541 }
542
linyuh183cb712017-12-27 17:02:37 -0800543 return primary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) && !isFullscreen;
Eric Erfanianccca3152017-02-22 16:32:36 -0800544 }
545
Eric Erfanian2ca43182017-08-31 06:57:16 -0700546 private boolean supports2ndCallOnHold() {
547 DialerCall firstCall = CallList.getInstance().getActiveOrBackgroundCall();
548 DialerCall incomingCall = CallList.getInstance().getIncomingCall();
549 if (firstCall != null && incomingCall != null && firstCall != incomingCall) {
550 return incomingCall.can(Details.CAPABILITY_HOLD);
551 }
552 return true;
553 }
554
Eric Erfanianccca3152017-02-22 16:32:36 -0800555 @Override
556 public void onCallStateButtonClicked() {
linyuh183cb712017-12-27 17:02:37 -0800557 Intent broadcastIntent = Bindings.get(context).getCallStateButtonBroadcastIntent(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800558 if (broadcastIntent != null) {
559 LogUtil.v(
560 "CallCardPresenter.onCallStateButtonClicked",
561 "sending call state button broadcast: " + broadcastIntent);
linyuh183cb712017-12-27 17:02:37 -0800562 context.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
Eric Erfanianccca3152017-02-22 16:32:36 -0800563 }
564 }
565
566 @Override
567 public void onManageConferenceClicked() {
568 InCallActivity activity =
linyuh183cb712017-12-27 17:02:37 -0800569 (InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
Eric Erfanianccca3152017-02-22 16:32:36 -0800570 activity.showConferenceFragment(true);
571 }
572
573 @Override
574 public void onShrinkAnimationComplete() {
575 InCallPresenter.getInstance().onShrinkAnimationComplete();
576 }
577
Eric Erfanianccca3152017-02-22 16:32:36 -0800578 private void maybeStartSearch(DialerCall call, boolean isPrimary) {
579 // no need to start search for conference calls which show generic info.
580 if (call != null && !call.isConferenceCall()) {
581 startContactInfoSearch(call, isPrimary, call.getState() == DialerCall.State.INCOMING);
582 }
583 }
584
Eric Erfanianccca3152017-02-22 16:32:36 -0800585 /** Starts a query for more contact data for the save primary and secondary calls. */
586 private void startContactInfoSearch(
587 final DialerCall call, final boolean isPrimary, boolean isIncoming) {
linyuh183cb712017-12-27 17:02:37 -0800588 final ContactInfoCache cache = ContactInfoCache.getInstance(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800589
590 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
591 }
592
593 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
594 final boolean entryMatchesExistingCall =
linyuh183cb712017-12-27 17:02:37 -0800595 (isPrimary && primary != null && TextUtils.equals(callId, primary.getId()))
596 || (!isPrimary && secondary != null && TextUtils.equals(callId, secondary.getId()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800597 if (entryMatchesExistingCall) {
598 updateContactEntry(entry, isPrimary);
599 } else {
600 LogUtil.e(
601 "CallCardPresenter.onContactInfoComplete",
602 "dropping stale contact lookup info for " + callId);
603 }
604
605 final DialerCall call = CallList.getInstance().getCallById(callId);
606 if (call != null) {
607 call.getLogState().contactLookupResult = entry.contactLookupResult;
608 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700609 if (entry.lookupUri != null) {
linyuh183cb712017-12-27 17:02:37 -0800610 CallerInfoUtils.sendViewNotification(context, entry.lookupUri);
Eric Erfanianccca3152017-02-22 16:32:36 -0800611 }
612 }
613
614 private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
615 if (getUi() == null) {
616 return;
617 }
618
619 if (entry.photo != null) {
linyuh183cb712017-12-27 17:02:37 -0800620 if (primary != null && callId.equals(primary.getId())) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800621 updateContactEntry(entry, true /* isPrimary */);
linyuh183cb712017-12-27 17:02:37 -0800622 } else if (secondary != null && callId.equals(secondary.getId())) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800623 updateContactEntry(entry, false /* isPrimary */);
624 }
625 }
626 }
627
628 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
629 if (isPrimary) {
linyuh183cb712017-12-27 17:02:37 -0800630 primaryContactInfo = entry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800631 updatePrimaryDisplayInfo();
632 } else {
linyuh183cb712017-12-27 17:02:37 -0800633 secondaryContactInfo = entry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800634 updateSecondaryDisplayInfo();
635 }
636 }
637
Eric Erfanianccca3152017-02-22 16:32:36 -0800638 private void updatePrimaryDisplayInfo() {
linyuh183cb712017-12-27 17:02:37 -0800639 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800640 // TODO: May also occur if search result comes back after ui is destroyed. Look into
641 // removing that case completely.
642 LogUtil.v(
643 "CallCardPresenter.updatePrimaryDisplayInfo",
644 "updatePrimaryDisplayInfo called but ui is null!");
645 return;
646 }
647
linyuh183cb712017-12-27 17:02:37 -0800648 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800649 // Clear the primary display info.
Android Dialerf9439102018-02-21 08:05:14 -0800650 inCallScreen.setPrimary(PrimaryInfo.empty());
Eric Erfanianccca3152017-02-22 16:32:36 -0800651 return;
652 }
653
654 // Hide the contact photo if we are in a video call and the incoming video surface is
655 // showing.
656 boolean showContactPhoto =
linyuh183cb712017-12-27 17:02:37 -0800657 !VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800658
659 // DialerCall placed through a work phone account.
linyuh183cb712017-12-27 17:02:37 -0800660 boolean hasWorkCallProperty = primary.hasProperty(PROPERTY_ENTERPRISE_CALL);
Eric Erfanianccca3152017-02-22 16:32:36 -0800661
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700662 MultimediaData multimediaData = null;
linyuh183cb712017-12-27 17:02:37 -0800663 if (primary.getEnrichedCallSession() != null) {
664 multimediaData = primary.getEnrichedCallSession().getMultimediaData();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700665 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800666
linyuh183cb712017-12-27 17:02:37 -0800667 if (primary.isConferenceCall()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800668 LogUtil.v(
669 "CallCardPresenter.updatePrimaryDisplayInfo",
670 "update primary display info for conference call.");
671
linyuh183cb712017-12-27 17:02:37 -0800672 inCallScreen.setPrimary(
Android Dialerf9439102018-02-21 08:05:14 -0800673 PrimaryInfo.builder()
674 .setName(
675 CallerInfoUtils.getConferenceString(
676 context, primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)))
677 .setNameIsNumber(false)
678 .setPhotoType(ContactPhotoType.DEFAULT_PLACEHOLDER)
679 .setIsSipCall(false)
680 .setIsContactPhotoShown(showContactPhoto)
681 .setIsWorkCall(hasWorkCallProperty)
682 .setIsSpam(false)
683 .setIsLocalContact(false)
684 .setAnsweringDisconnectsOngoingCall(false)
685 .setShouldShowLocation(shouldShowLocation())
686 .setShowInCallButtonGrid(true)
687 .setNumberPresentation(primary.getNumberPresentation())
688 .build());
linyuh183cb712017-12-27 17:02:37 -0800689 } else if (primaryContactInfo != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800690 LogUtil.v(
691 "CallCardPresenter.updatePrimaryDisplayInfo",
linyuh183cb712017-12-27 17:02:37 -0800692 "update primary display info for " + primaryContactInfo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800693
linyuh183cb712017-12-27 17:02:37 -0800694 String name = getNameForCall(primaryContactInfo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800695 String number;
696
linyuh183cb712017-12-27 17:02:37 -0800697 boolean isChildNumberShown = !TextUtils.isEmpty(primary.getChildNumber());
698 boolean isForwardedNumberShown = !TextUtils.isEmpty(primary.getLastForwardedNumber());
699 boolean isCallSubjectShown = shouldShowCallSubject(primary);
Eric Erfanianccca3152017-02-22 16:32:36 -0800700
701 if (isCallSubjectShown) {
702 number = null;
703 } else if (isChildNumberShown) {
linyuh183cb712017-12-27 17:02:37 -0800704 number = context.getString(R.string.child_number, primary.getChildNumber());
Eric Erfanianccca3152017-02-22 16:32:36 -0800705 } else if (isForwardedNumberShown) {
706 // Use last forwarded number instead of second line, if present.
linyuh183cb712017-12-27 17:02:37 -0800707 number = primary.getLastForwardedNumber();
Eric Erfanianccca3152017-02-22 16:32:36 -0800708 } else {
linyuh183cb712017-12-27 17:02:37 -0800709 number = primaryContactInfo.number;
Eric Erfanianccca3152017-02-22 16:32:36 -0800710 }
711
linyuh183cb712017-12-27 17:02:37 -0800712 boolean nameIsNumber = name != null && name.equals(primaryContactInfo.number);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700713
Eric Erfanianccca3152017-02-22 16:32:36 -0800714 // DialerCall with caller that is a work contact.
linyuh183cb712017-12-27 17:02:37 -0800715 boolean isWorkContact = (primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
716 inCallScreen.setPrimary(
Android Dialerf9439102018-02-21 08:05:14 -0800717 PrimaryInfo.builder()
718 .setNumber(number)
719 .setName(primary.updateNameIfRestricted(name))
720 .setNameIsNumber(nameIsNumber)
twyencb2a9812018-03-23 18:21:13 -0700721 .setLocation(
Android Dialerf9439102018-02-21 08:05:14 -0800722 shouldShowLocationAsLabel(nameIsNumber, primaryContactInfo.shouldShowLocation)
723 ? primaryContactInfo.location
724 : null)
twyencb2a9812018-03-23 18:21:13 -0700725 .setLabel(isChildNumberShown || isCallSubjectShown ? null : primaryContactInfo.label)
Android Dialerf9439102018-02-21 08:05:14 -0800726 .setPhoto(primaryContactInfo.photo)
727 .setPhotoType(primaryContactInfo.photoType)
728 .setIsSipCall(primaryContactInfo.isSipCall)
729 .setIsContactPhotoShown(showContactPhoto)
730 .setIsWorkCall(hasWorkCallProperty || isWorkContact)
731 .setIsSpam(primary.isSpam())
732 .setIsLocalContact(primaryContactInfo.isLocalContact())
733 .setAnsweringDisconnectsOngoingCall(primary.answeringDisconnectsForegroundVideoCall())
734 .setShouldShowLocation(shouldShowLocation())
735 .setContactInfoLookupKey(primaryContactInfo.lookupKey)
736 .setMultimediaData(multimediaData)
737 .setShowInCallButtonGrid(true)
738 .setNumberPresentation(primary.getNumberPresentation())
739 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800740 } else {
741 // Clear the primary display info.
Android Dialerf9439102018-02-21 08:05:14 -0800742 inCallScreen.setPrimary(PrimaryInfo.empty());
Eric Erfanianccca3152017-02-22 16:32:36 -0800743 }
744
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700745 if (isInCallScreenReady) {
linyuh183cb712017-12-27 17:02:37 -0800746 inCallScreen.showLocationUi(getLocationFragment());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700747 } else {
748 LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
749 }
750 }
751
Eric Erfaniand8046e52017-04-06 09:41:50 -0700752 private static boolean shouldShowLocationAsLabel(
753 boolean nameIsNumber, boolean shouldShowLocation) {
754 if (nameIsNumber) {
755 return true;
756 }
757 if (shouldShowLocation) {
758 return true;
759 }
760 return false;
761 }
762
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700763 private Fragment getLocationFragment() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700764 if (!shouldShowLocation()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700765 return null;
766 }
767 LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
linyuh183cb712017-12-27 17:02:37 -0800768 return callLocation.getLocationFragment(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800769 }
770
771 private boolean shouldShowLocation() {
linyuh183cb712017-12-27 17:02:37 -0800772 if (!ConfigProviderBindings.get(context)
wangqi1d62ab22017-11-29 14:29:31 -0800773 .getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
774 LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
775 return false;
776 }
777 if (!isPotentialEmergencyCall()) {
778 LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
779 return false;
780 }
781 if (!hasLocationPermission()) {
782 LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
783 return false;
784 }
785 if (isBatteryTooLowForEmergencyLocation()) {
786 LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
787 return false;
788 }
linyuh437ae952018-03-26 12:46:18 -0700789 if (inCallScreen.getInCallScreenFragment().getActivity().isInMultiWindowMode()) {
wangqi1d62ab22017-11-29 14:29:31 -0800790 LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
791 return false;
792 }
linyuh183cb712017-12-27 17:02:37 -0800793 if (primary.isVideoCall()) {
wangqi1d62ab22017-11-29 14:29:31 -0800794 LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
795 return false;
796 }
linyuh183cb712017-12-27 17:02:37 -0800797 if (!callLocation.canGetLocation(context)) {
wangqi1d62ab22017-11-29 14:29:31 -0800798 LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
799 return false;
800 }
801 return true;
802 }
803
804 private boolean isPotentialEmergencyCall() {
linyuh183cb712017-12-27 17:02:37 -0800805 if (isOutgoingEmergencyCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800806 LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
807 return true;
linyuh183cb712017-12-27 17:02:37 -0800808 } else if (isIncomingEmergencyCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800809 LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
810 return true;
linyuh183cb712017-12-27 17:02:37 -0800811 } else if (isIncomingEmergencyCall(secondary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800812 LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
813 return true;
814 }
815 return false;
816 }
817
818 private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
819 return call != null && !call.isIncoming() && call.isEmergencyCall();
820 }
821
822 private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
823 return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
824 }
825
826 private boolean hasLocationPermission() {
linyuh183cb712017-12-27 17:02:37 -0800827 return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
Eric Erfanianccca3152017-02-22 16:32:36 -0800828 == PackageManager.PERMISSION_GRANTED;
829 }
830
831 private boolean isBatteryTooLowForEmergencyLocation() {
832 Intent batteryStatus =
linyuh183cb712017-12-27 17:02:37 -0800833 context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Eric Erfanianccca3152017-02-22 16:32:36 -0800834 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
835 if (status == BatteryManager.BATTERY_STATUS_CHARGING
836 || status == BatteryManager.BATTERY_STATUS_FULL) {
837 // Plugged in or full battery
838 return false;
839 }
840 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
841 int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
842 float batteryPercent = (100f * level) / scale;
843 long threshold =
linyuh183cb712017-12-27 17:02:37 -0800844 ConfigProviderBindings.get(context)
Eric Erfanianccca3152017-02-22 16:32:36 -0800845 .getLong(
846 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
847 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
848 LogUtil.i(
849 "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
850 "percent charged: " + batteryPercent + ", min required charge: " + threshold);
851 return batteryPercent < threshold;
852 }
853
854 private void updateSecondaryDisplayInfo() {
linyuh183cb712017-12-27 17:02:37 -0800855 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800856 return;
857 }
858
linyuh183cb712017-12-27 17:02:37 -0800859 if (secondary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800860 // Clear the secondary display info.
Android Dialer9e335e22018-03-01 08:27:39 -0800861 inCallScreen.setSecondary(SecondaryInfo.builder().setIsFullscreen(isFullscreen).build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800862 return;
863 }
864
linyuh183cb712017-12-27 17:02:37 -0800865 if (secondary.isMergeInProcess()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700866 LogUtil.i(
867 "CallCardPresenter.updateSecondaryDisplayInfo",
868 "secondary call is merge in process, clearing info");
Android Dialer9e335e22018-03-01 08:27:39 -0800869 inCallScreen.setSecondary(SecondaryInfo.builder().setIsFullscreen(isFullscreen).build());
Eric Erfanian2ca43182017-08-31 06:57:16 -0700870 return;
871 }
872
linyuh183cb712017-12-27 17:02:37 -0800873 if (secondary.isConferenceCall()) {
874 inCallScreen.setSecondary(
Android Dialer9e335e22018-03-01 08:27:39 -0800875 SecondaryInfo.builder()
876 .setShouldShow(true)
877 .setName(
878 CallerInfoUtils.getConferenceString(
879 context, secondary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)))
880 .setProviderLabel(secondary.getCallProviderLabel())
881 .setIsConference(true)
882 .setIsVideoCall(secondary.isVideoCall())
883 .setIsFullscreen(isFullscreen)
884 .build());
linyuh183cb712017-12-27 17:02:37 -0800885 } else if (secondaryContactInfo != null) {
886 LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + secondaryContactInfo);
887 String name = getNameForCall(secondaryContactInfo);
888 boolean nameIsNumber = name != null && name.equals(secondaryContactInfo.number);
889 inCallScreen.setSecondary(
Android Dialer9e335e22018-03-01 08:27:39 -0800890 SecondaryInfo.builder()
891 .setShouldShow(true)
892 .setName(secondary.updateNameIfRestricted(name))
893 .setNameIsNumber(nameIsNumber)
894 .setLabel(secondaryContactInfo.label)
895 .setProviderLabel(secondary.getCallProviderLabel())
896 .setIsVideoCall(secondary.isVideoCall())
897 .setIsFullscreen(isFullscreen)
898 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800899 } else {
900 // Clear the secondary display info.
Android Dialer9e335e22018-03-01 08:27:39 -0800901 inCallScreen.setSecondary(SecondaryInfo.builder().setIsFullscreen(isFullscreen).build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800902 }
903 }
904
905 /** Returns the gateway number for any existing outgoing call. */
906 private String getGatewayNumber() {
907 if (hasOutgoingGatewayCall()) {
linyuh183cb712017-12-27 17:02:37 -0800908 return DialerCall.getNumberFromHandle(primary.getGatewayInfo().getGatewayAddress());
Eric Erfanianccca3152017-02-22 16:32:36 -0800909 }
910 return null;
911 }
912
913 /**
914 * Returns the label (line of text above the number/name) for any given call. For example,
915 * "calling via [Account/Google Voice]" for outgoing calls.
916 */
917 private String getConnectionLabel() {
linyuh183cb712017-12-27 17:02:37 -0800918 if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800919 != PackageManager.PERMISSION_GRANTED) {
920 return null;
921 }
linyuh183cb712017-12-27 17:02:37 -0800922 StatusHints statusHints = primary.getStatusHints();
Eric Erfanianccca3152017-02-22 16:32:36 -0800923 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
924 return statusHints.getLabel().toString();
925 }
926
927 if (hasOutgoingGatewayCall() && getUi() != null) {
928 // Return the label for the gateway app on outgoing calls.
linyuh183cb712017-12-27 17:02:37 -0800929 final PackageManager pm = context.getPackageManager();
Eric Erfanianccca3152017-02-22 16:32:36 -0800930 try {
931 ApplicationInfo info =
linyuh183cb712017-12-27 17:02:37 -0800932 pm.getApplicationInfo(primary.getGatewayInfo().getGatewayProviderPackageName(), 0);
Eric Erfanianccca3152017-02-22 16:32:36 -0800933 return pm.getApplicationLabel(info).toString();
934 } catch (PackageManager.NameNotFoundException e) {
935 LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
936 return null;
937 }
938 }
linyuh183cb712017-12-27 17:02:37 -0800939 return primary.getCallProviderLabel();
Eric Erfanianccca3152017-02-22 16:32:36 -0800940 }
941
twyen4fde0ac2018-03-22 18:04:23 -0700942 @Nullable
943 private SuggestionProvider.Reason getSimSuggestionReason() {
944 String value =
945 primary.getIntentExtras().getString(SuggestionProvider.EXTRA_SIM_SUGGESTION_REASON);
946 if (value == null) {
947 return null;
948 }
949 try {
950 return SuggestionProvider.Reason.valueOf(value);
951 } catch (IllegalArgumentException e) {
952 LogUtil.e("CallCardPresenter.getConnectionLabel", "unknown reason " + value);
953 return null;
954 }
955 }
956
Eric Erfanianccca3152017-02-22 16:32:36 -0800957 private Drawable getCallStateIcon() {
958 // Return connection icon if one exists.
linyuh183cb712017-12-27 17:02:37 -0800959 StatusHints statusHints = primary.getStatusHints();
Eric Erfanianccca3152017-02-22 16:32:36 -0800960 if (statusHints != null && statusHints.getIcon() != null) {
linyuh183cb712017-12-27 17:02:37 -0800961 Drawable icon = statusHints.getIcon().loadDrawable(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800962 if (icon != null) {
963 return icon;
964 }
965 }
966
967 return null;
968 }
969
970 private boolean hasOutgoingGatewayCall() {
971 // We only display the gateway information while STATE_DIALING so return false for any other
972 // call state.
973 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
974 // is also called after a contact search completes (call is not present yet). Split the
975 // UI update so it can receive independent updates.
linyuh183cb712017-12-27 17:02:37 -0800976 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800977 return false;
978 }
linyuh183cb712017-12-27 17:02:37 -0800979 return DialerCall.State.isDialing(primary.getState())
980 && primary.getGatewayInfo() != null
981 && !primary.getGatewayInfo().isEmpty();
Eric Erfanianccca3152017-02-22 16:32:36 -0800982 }
983
984 /** Gets the name to display for the call. */
Eric Erfanian2ca43182017-08-31 06:57:16 -0700985 private String getNameForCall(ContactCacheEntry contactInfo) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800986 String preferredName =
987 ContactDisplayUtils.getPreferredDisplayName(
linyuh183cb712017-12-27 17:02:37 -0800988 contactInfo.namePrimary, contactInfo.nameAlternative, contactsPreferences);
Eric Erfanianccca3152017-02-22 16:32:36 -0800989 if (TextUtils.isEmpty(preferredName)) {
990 return contactInfo.number;
991 }
992 return preferredName;
993 }
994
Eric Erfanianccca3152017-02-22 16:32:36 -0800995 @Override
996 public void onSecondaryInfoClicked() {
linyuh183cb712017-12-27 17:02:37 -0800997 if (secondary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800998 LogUtil.e(
999 "CallCardPresenter.onSecondaryInfoClicked",
1000 "secondary info clicked but no secondary call.");
1001 return;
1002 }
1003
linyuh183cb712017-12-27 17:02:37 -08001004 Logger.get(context)
Eric Erfanian2ca43182017-08-31 06:57:16 -07001005 .logCallImpression(
1006 DialerImpression.Type.IN_CALL_SWAP_SECONDARY_BUTTON_PRESSED,
linyuh183cb712017-12-27 17:02:37 -08001007 primary.getUniqueCallId(),
1008 primary.getTimeAddedMs());
Eric Erfanianccca3152017-02-22 16:32:36 -08001009 LogUtil.i(
linyuh183cb712017-12-27 17:02:37 -08001010 "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + secondary);
1011 secondary.unhold();
Eric Erfanianccca3152017-02-22 16:32:36 -08001012 }
1013
1014 @Override
1015 public void onEndCallClicked() {
linyuh183cb712017-12-27 17:02:37 -08001016 LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + primary);
1017 if (primary != null) {
1018 primary.disconnect();
Eric Erfanianccca3152017-02-22 16:32:36 -08001019 }
linyuh183cb712017-12-27 17:02:37 -08001020 PostCall.onDisconnectPressed(context);
Eric Erfanianccca3152017-02-22 16:32:36 -08001021 }
1022
1023 /**
1024 * Handles a change to the fullscreen mode of the in-call UI.
1025 *
1026 * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
1027 */
1028 @Override
1029 public void onFullscreenModeChanged(boolean isFullscreenMode) {
linyuh183cb712017-12-27 17:02:37 -08001030 isFullscreen = isFullscreenMode;
1031 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001032 return;
1033 }
1034 maybeShowManageConferenceCallButton();
1035 }
1036
1037 private boolean isPrimaryCallActive() {
linyuh183cb712017-12-27 17:02:37 -08001038 return primary != null && primary.getState() == DialerCall.State.ACTIVE;
Eric Erfanianccca3152017-02-22 16:32:36 -08001039 }
1040
Eric Erfanianccca3152017-02-22 16:32:36 -08001041 private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
1042 if (primary == null) {
1043 return false;
1044 }
1045 if ((!DialerCall.State.isConnectingOrConnected(callState)
1046 && callState != DialerCall.State.DISCONNECTING
1047 && callState != DialerCall.State.DISCONNECTED)
1048 || callState == DialerCall.State.INCOMING) {
1049 return false;
1050 }
linyuh183cb712017-12-27 17:02:37 -08001051 if (this.primary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001052 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001053 return false;
1054 }
1055 return true;
1056 }
1057
1058 @Override
1059 public void onInCallScreenResumed() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001060 updatePrimaryDisplayInfo();
1061
Eric Erfanianccca3152017-02-22 16:32:36 -08001062 if (shouldSendAccessibilityEvent) {
1063 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1064 }
1065 }
1066
Eric Erfaniand8046e52017-04-06 09:41:50 -07001067 @Override
Eric Erfanian2ca43182017-08-31 06:57:16 -07001068 public void onInCallScreenPaused() {}
Eric Erfaniand8046e52017-04-06 09:41:50 -07001069
Eric Erfanianccca3152017-02-22 16:32:36 -08001070 static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
1071 AccessibilityManager am =
1072 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
1073 if (!am.isEnabled()) {
1074 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
1075 return false;
1076 }
1077 if (inCallScreen == null) {
1078 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
1079 return false;
1080 }
1081 Fragment fragment = inCallScreen.getInCallScreenFragment();
1082 if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
1083 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
1084 return false;
1085 }
1086
1087 DisplayManager displayManager =
1088 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1089 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1090 boolean screenIsOn = display.getState() == Display.STATE_ON;
1091 LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
1092 if (!screenIsOn) {
1093 return false;
1094 }
1095
1096 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1097 inCallScreen.dispatchPopulateAccessibilityEvent(event);
1098 View view = inCallScreen.getInCallScreenFragment().getView();
1099 view.getParent().requestSendAccessibilityEvent(view, event);
1100 return true;
1101 }
1102
1103 private void maybeSendAccessibilityEvent(
1104 InCallState oldState, final InCallState newState, boolean primaryChanged) {
1105 shouldSendAccessibilityEvent = false;
linyuh183cb712017-12-27 17:02:37 -08001106 if (context == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001107 return;
1108 }
1109 final AccessibilityManager am =
linyuh183cb712017-12-27 17:02:37 -08001110 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
Eric Erfanianccca3152017-02-22 16:32:36 -08001111 if (!am.isEnabled()) {
1112 return;
1113 }
1114 // Announce the current call if it's new incoming/outgoing call or primary call is changed
1115 // due to switching calls between two ongoing calls (one is on hold).
1116 if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
1117 || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
1118 || primaryChanged) {
1119 LogUtil.i(
1120 "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
1121 shouldSendAccessibilityEvent = true;
1122 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1123 }
1124 }
1125
1126 /**
1127 * Determines whether the call subject should be visible on the UI. For the call subject to be
1128 * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
1129 *
1130 * @param call The call.
1131 * @return {@code true} if the subject should be shown, {@code false} otherwise.
1132 */
1133 private boolean shouldShowCallSubject(DialerCall call) {
1134 if (call == null) {
1135 return false;
1136 }
1137
1138 boolean isIncomingOrWaiting =
linyuh183cb712017-12-27 17:02:37 -08001139 primary.getState() == DialerCall.State.INCOMING
1140 || primary.getState() == DialerCall.State.CALL_WAITING;
Eric Erfanianccca3152017-02-22 16:32:36 -08001141 return isIncomingOrWaiting
1142 && !TextUtils.isEmpty(call.getCallSubject())
1143 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
1144 && call.isCallSubjectSupported();
1145 }
1146
1147 /**
1148 * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
1149 * call with a subject.
1150 *
1151 * @param call The call
1152 * @return {@code true} if the toast should be shown, {@code false} otherwise.
1153 */
1154 private boolean shouldShowNoteSentToast(DialerCall call) {
1155 return call != null
1156 && hasCallSubject(call)
1157 && (call.getState() == DialerCall.State.DIALING
1158 || call.getState() == DialerCall.State.CONNECTING);
1159 }
1160
1161 private InCallScreen getUi() {
linyuh183cb712017-12-27 17:02:37 -08001162 return inCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -08001163 }
1164
Android Dialerf9439102018-02-21 08:05:14 -08001165 /** Callback for contact lookup. */
Eric Erfanianccca3152017-02-22 16:32:36 -08001166 public static class ContactLookupCallback implements ContactInfoCacheCallback {
1167
linyuh183cb712017-12-27 17:02:37 -08001168 private final WeakReference<CallCardPresenter> callCardPresenter;
1169 private final boolean isPrimary;
Eric Erfanianccca3152017-02-22 16:32:36 -08001170
1171 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
linyuh183cb712017-12-27 17:02:37 -08001172 this.callCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
1173 this.isPrimary = isPrimary;
Eric Erfanianccca3152017-02-22 16:32:36 -08001174 }
1175
1176 @Override
1177 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
linyuh183cb712017-12-27 17:02:37 -08001178 CallCardPresenter presenter = callCardPresenter.get();
Eric Erfanianccca3152017-02-22 16:32:36 -08001179 if (presenter != null) {
linyuh183cb712017-12-27 17:02:37 -08001180 presenter.onContactInfoComplete(callId, entry, isPrimary);
Eric Erfanianccca3152017-02-22 16:32:36 -08001181 }
1182 }
1183
1184 @Override
1185 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
linyuh183cb712017-12-27 17:02:37 -08001186 CallCardPresenter presenter = callCardPresenter.get();
Eric Erfanianccca3152017-02-22 16:32:36 -08001187 if (presenter != null) {
1188 presenter.onImageLoadComplete(callId, entry);
1189 }
1190 }
1191 }
1192}