blob: 67473d75e858ea6ca5f3c575af689a3e5a1af5ab [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 Erfaniand5e47f62017-03-15 14:41:07 -070049import com.android.dialer.compat.ActivityCompat;
Eric Erfanian2ca43182017-08-31 06:57:16 -070050import com.android.dialer.configprovider.ConfigProviderBindings;
Android Dialer3855a6b2017-11-21 14:34:47 -080051import com.android.dialer.feedback.FeedbackComponent;
Eric Erfanian8369df02017-05-03 10:27:13 -070052import com.android.dialer.logging.DialerImpression;
Eric Erfaniand8046e52017-04-06 09:41:50 -070053import com.android.dialer.logging.Logger;
Eric Erfanianccca3152017-02-22 16:32:36 -080054import com.android.dialer.multimedia.MultimediaData;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070055import com.android.dialer.oem.MotorolaUtils;
wangqi97539352017-09-25 11:15:16 -070056import com.android.dialer.phonenumberutil.PhoneNumberHelper;
Eric Erfanian2ca43182017-08-31 06:57:16 -070057import com.android.dialer.postcall.PostCall;
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 Erfanian2ca43182017-08-31 06:57:16 -070067import com.android.incallui.call.DialerCall.State;
Eric Erfanianccca3152017-02-22 16:32:36 -080068import com.android.incallui.call.DialerCallListener;
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;
120 private DialerCall secondary;
121 private ContactCacheEntry primaryContactInfo;
122 private ContactCacheEntry secondaryContactInfo;
123 @Nullable private ContactsPreferences contactsPreferences;
124 private boolean isFullscreen = false;
125 private InCallScreen inCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -0800126 private boolean isInCallScreenReady;
127 private boolean shouldSendAccessibilityEvent;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700128
129 @NonNull private final CallLocation callLocation;
Eric Erfanianccca3152017-02-22 16:32:36 -0800130 private final Runnable sendAccessibilityEventRunnable =
131 new Runnable() {
132 @Override
133 public void run() {
linyuh183cb712017-12-27 17:02:37 -0800134 shouldSendAccessibilityEvent = !sendAccessibilityEvent(context, getUi());
Eric Erfanianccca3152017-02-22 16:32:36 -0800135 LogUtil.i(
136 "CallCardPresenter.sendAccessibilityEventRunnable",
137 "still should send: %b",
138 shouldSendAccessibilityEvent);
139 if (!shouldSendAccessibilityEvent) {
140 handler.removeCallbacks(this);
141 }
142 }
143 };
144
145 public CallCardPresenter(Context context) {
wangqi385a5a12017-09-28 10:44:54 -0700146 LogUtil.i("CallCardPresenter.constructor", null);
linyuh183cb712017-12-27 17:02:37 -0800147 this.context = Assert.isNotNull(context).getApplicationContext();
148 callLocation = CallLocationComponent.get(this.context).getCallLocation();
Eric Erfanianccca3152017-02-22 16:32:36 -0800149 }
150
151 private static boolean hasCallSubject(DialerCall call) {
152 return !TextUtils.isEmpty(call.getCallSubject());
153 }
154
Android Dialer3855a6b2017-11-21 14:34:47 -0800155 private void addCallFeedbackListener(Context context) {
156 LogUtil.d("CallCardPresenter.addCallFeedbackListener", "Adding call feedback listener");
157 CallList.getInstance().addListener(FeedbackComponent.get(context).getCallFeedbackListener());
158 }
159
Eric Erfanianccca3152017-02-22 16:32:36 -0800160 @Override
161 public void onInCallScreenDelegateInit(InCallScreen inCallScreen) {
162 Assert.isNotNull(inCallScreen);
linyuh183cb712017-12-27 17:02:37 -0800163 this.inCallScreen = inCallScreen;
164 contactsPreferences = ContactsPreferencesFactory.newContactsPreferences(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800165
166 // Call may be null if disconnect happened already.
167 DialerCall call = CallList.getInstance().getFirstCall();
168 if (call != null) {
linyuh183cb712017-12-27 17:02:37 -0800169 primary = call;
170 if (shouldShowNoteSentToast(primary)) {
171 this.inCallScreen.showNoteSentToast();
Eric Erfanianccca3152017-02-22 16:32:36 -0800172 }
173 call.addListener(this);
linyuh183cb712017-12-27 17:02:37 -0800174 addCallFeedbackListener(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800175 // start processing lookups right away.
176 if (!call.isConferenceCall()) {
177 startContactInfoSearch(call, true, call.getState() == DialerCall.State.INCOMING);
178 } else {
179 updateContactEntry(null, true);
180 }
181 }
182
183 onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
184 }
185
186 @Override
187 public void onInCallScreenReady() {
wangqi385a5a12017-09-28 10:44:54 -0700188 LogUtil.i("CallCardPresenter.onInCallScreenReady", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800189 Assert.checkState(!isInCallScreenReady);
linyuh183cb712017-12-27 17:02:37 -0800190 if (contactsPreferences != null) {
191 contactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
Eric Erfanianccca3152017-02-22 16:32:36 -0800192 }
193
Eric Erfanianccca3152017-02-22 16:32:36 -0800194 // Contact search may have completed before ui is ready.
linyuh183cb712017-12-27 17:02:37 -0800195 if (primaryContactInfo != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800196 updatePrimaryDisplayInfo();
197 }
198
199 // Register for call state changes last
200 InCallPresenter.getInstance().addListener(this);
201 InCallPresenter.getInstance().addIncomingCallListener(this);
202 InCallPresenter.getInstance().addDetailsListener(this);
203 InCallPresenter.getInstance().addInCallEventListener(this);
204 isInCallScreenReady = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700205
Eric Erfaniand8046e52017-04-06 09:41:50 -0700206 // Log location impressions
linyuh183cb712017-12-27 17:02:37 -0800207 if (isOutgoingEmergencyCall(primary)) {
208 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_NEW_EMERGENCY_CALL);
209 } else if (isIncomingEmergencyCall(primary) || isIncomingEmergencyCall(secondary)) {
210 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_CALLBACK);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700211 }
212
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700213 // Showing the location may have been skipped if the UI wasn't ready during previous layout.
214 if (shouldShowLocation()) {
linyuh183cb712017-12-27 17:02:37 -0800215 inCallScreen.showLocationUi(getLocationFragment());
Eric Erfaniand8046e52017-04-06 09:41:50 -0700216
217 // Log location impressions
218 if (!hasLocationPermission()) {
linyuh183cb712017-12-27 17:02:37 -0800219 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_NO_LOCATION_PERMISSION);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700220 } else if (isBatteryTooLowForEmergencyLocation()) {
linyuh183cb712017-12-27 17:02:37 -0800221 Logger.get(context)
Eric Erfaniand8046e52017-04-06 09:41:50 -0700222 .logImpression(DialerImpression.Type.EMERGENCY_BATTERY_TOO_LOW_TO_GET_LOCATION);
linyuh183cb712017-12-27 17:02:37 -0800223 } else if (!callLocation.canGetLocation(context)) {
224 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_CANT_GET_LOCATION);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700225 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700226 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800227 }
228
229 @Override
230 public void onInCallScreenUnready() {
wangqi385a5a12017-09-28 10:44:54 -0700231 LogUtil.i("CallCardPresenter.onInCallScreenUnready", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800232 Assert.checkState(isInCallScreenReady);
233
Eric Erfanianccca3152017-02-22 16:32:36 -0800234 // stop getting call state changes
235 InCallPresenter.getInstance().removeListener(this);
236 InCallPresenter.getInstance().removeIncomingCallListener(this);
237 InCallPresenter.getInstance().removeDetailsListener(this);
238 InCallPresenter.getInstance().removeInCallEventListener(this);
linyuh183cb712017-12-27 17:02:37 -0800239 if (primary != null) {
240 primary.removeListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800241 }
242
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700243 callLocation.close();
244
linyuh183cb712017-12-27 17:02:37 -0800245 primary = null;
246 primaryContactInfo = null;
247 secondaryContactInfo = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800248 isInCallScreenReady = false;
249 }
250
251 @Override
252 public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
253 // same logic should happen as with onStateChange()
254 onStateChange(oldState, newState, CallList.getInstance());
255 }
256
257 @Override
258 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700259 Trace.beginSection("CallCardPresenter.onStateChange");
260 LogUtil.v("CallCardPresenter.onStateChange", "oldState: %s, newState: %s", oldState, newState);
linyuh183cb712017-12-27 17:02:37 -0800261 if (inCallScreen == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700262 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800263 return;
264 }
265
266 DialerCall primary = null;
267 DialerCall secondary = null;
268
269 if (newState == InCallState.INCOMING) {
270 primary = callList.getIncomingCall();
271 } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
272 primary = callList.getOutgoingCall();
273 if (primary == null) {
274 primary = callList.getPendingOutgoingCall();
275 }
276
277 // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
278 // highest priority call to display as the secondary call.
279 secondary = getCallToDisplay(callList, null, true);
280 } else if (newState == InCallState.INCALL) {
281 primary = getCallToDisplay(callList, null, false);
282 secondary = getCallToDisplay(callList, primary, true);
283 }
284
285 LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
286 LogUtil.v("CallCardPresenter.onStateChange", "secondary call: " + secondary);
287
288 final boolean primaryChanged =
linyuh183cb712017-12-27 17:02:37 -0800289 !(DialerCall.areSame(this.primary, primary)
290 && DialerCall.areSameNumber(this.primary, primary));
Eric Erfanianccca3152017-02-22 16:32:36 -0800291 final boolean secondaryChanged =
linyuh183cb712017-12-27 17:02:37 -0800292 !(DialerCall.areSame(this.secondary, secondary)
293 && DialerCall.areSameNumber(this.secondary, secondary));
Eric Erfanianccca3152017-02-22 16:32:36 -0800294
linyuh183cb712017-12-27 17:02:37 -0800295 this.secondary = secondary;
296 DialerCall previousPrimary = this.primary;
297 this.primary = primary;
Eric Erfanianccca3152017-02-22 16:32:36 -0800298
linyuh183cb712017-12-27 17:02:37 -0800299 if (this.primary != null) {
300 InCallPresenter.getInstance().onForegroundCallChanged(this.primary);
301 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
linyuh183cb712017-12-27 17:02:37 -0800318 primaryContactInfo =
Eric Erfanianccca3152017-02-22 16:32:36 -0800319 ContactInfoCache.buildCacheEntryFromCall(
linyuh183cb712017-12-27 17:02:37 -0800320 context, this.primary, this.primary.getState() == DialerCall.State.INCOMING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800321 updatePrimaryDisplayInfo();
linyuh183cb712017-12-27 17:02:37 -0800322 maybeStartSearch(this.primary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800323 }
324
linyuh183cb712017-12-27 17:02:37 -0800325 if (previousPrimary != null && this.primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800326 previousPrimary.removeListener(this);
327 }
328
wangqic8cf79e2017-10-17 09:21:00 -0700329 if (secondaryChanged) {
linyuh183cb712017-12-27 17:02:37 -0800330 if (this.secondary == null) {
wangqic8cf79e2017-10-17 09:21:00 -0700331 // Secondary call may have ended. Update the ui.
linyuh183cb712017-12-27 17:02:37 -0800332 secondaryContactInfo = null;
wangqic8cf79e2017-10-17 09:21:00 -0700333 updateSecondaryDisplayInfo();
334 } else {
335 // secondary call has changed
linyuh183cb712017-12-27 17:02:37 -0800336 secondaryContactInfo =
wangqic8cf79e2017-10-17 09:21:00 -0700337 ContactInfoCache.buildCacheEntryFromCall(
linyuh183cb712017-12-27 17:02:37 -0800338 context, this.secondary, this.secondary.getState() == DialerCall.State.INCOMING);
wangqic8cf79e2017-10-17 09:21:00 -0700339 updateSecondaryDisplayInfo();
linyuh183cb712017-12-27 17:02:37 -0800340 maybeStartSearch(this.secondary, false);
wangqic8cf79e2017-10-17 09:21:00 -0700341 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800342 }
343
344 // Set the call state
345 int callState = DialerCall.State.IDLE;
linyuh183cb712017-12-27 17:02:37 -0800346 if (this.primary != null) {
347 callState = this.primary.getState();
Eric Erfanianccca3152017-02-22 16:32:36 -0800348 updatePrimaryCallState();
349 } else {
350 getUi().setCallState(PrimaryCallState.createEmptyPrimaryCallState());
351 }
352
353 maybeShowManageConferenceCallButton();
354
355 // Hide the end call button instantly if we're receiving an incoming call.
356 getUi()
357 .setEndCallButtonEnabled(
linyuh183cb712017-12-27 17:02:37 -0800358 shouldShowEndCallButton(this.primary, callState),
Eric Erfanianccca3152017-02-22 16:32:36 -0800359 callState != DialerCall.State.INCOMING /* animate */);
360
361 maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700362 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800363 }
364
365 @Override
366 public void onDetailsChanged(DialerCall call, Details details) {
367 updatePrimaryCallState();
368
369 if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
370 != details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
371 maybeShowManageConferenceCallButton();
372 }
373 }
374
375 @Override
376 public void onDialerCallDisconnect() {}
377
378 @Override
379 public void onDialerCallUpdate() {
380 // No-op; specific call updates handled elsewhere.
381 }
382
383 @Override
384 public void onWiFiToLteHandover() {}
385
386 @Override
387 public void onHandoverToWifiFailure() {}
388
Eric Erfanianc857f902017-05-15 14:05:33 -0700389 @Override
390 public void onInternationalCallOnWifi() {}
391
Eric Erfanian2ca43182017-08-31 06:57:16 -0700392 @Override
393 public void onEnrichedCallSessionUpdate() {
394 LogUtil.enterBlock("CallCardPresenter.onEnrichedCallSessionUpdate");
395 updatePrimaryDisplayInfo();
396 }
397
Eric Erfanianccca3152017-02-22 16:32:36 -0800398 /** Handles a change to the child number by refreshing the primary call info. */
399 @Override
400 public void onDialerCallChildNumberChange() {
401 LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
402
linyuh183cb712017-12-27 17:02:37 -0800403 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800404 return;
405 }
406 updatePrimaryDisplayInfo();
407 }
408
409 /** Handles a change to the last forwarding number by refreshing the primary call info. */
410 @Override
411 public void onDialerCallLastForwardedNumberChange() {
412 LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
413
linyuh183cb712017-12-27 17:02:37 -0800414 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800415 return;
416 }
417 updatePrimaryDisplayInfo();
418 updatePrimaryCallState();
419 }
420
421 @Override
422 public void onDialerCallUpgradeToVideo() {}
423
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700424 /** Handles a change to the session modification state for a call. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800425 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700426 public void onDialerCallSessionModificationStateChange() {
427 LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
Eric Erfanianccca3152017-02-22 16:32:36 -0800428
linyuh183cb712017-12-27 17:02:37 -0800429 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800430 return;
431 }
432 getUi()
433 .setEndCallButtonEnabled(
linyuh183cb712017-12-27 17:02:37 -0800434 primary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700435 != SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800436 true /* shouldAnimate */);
437 updatePrimaryCallState();
438 }
439
Eric Erfanianccca3152017-02-22 16:32:36 -0800440 private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
linyuh183cb712017-12-27 17:02:37 -0800441 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800442 return false;
443 }
444 return primaryChanged
linyuh183cb712017-12-27 17:02:37 -0800445 || inCallScreen.isManageConferenceVisible() != shouldShowManageConference();
Eric Erfanianccca3152017-02-22 16:32:36 -0800446 }
447
448 private void updatePrimaryCallState() {
linyuh183cb712017-12-27 17:02:37 -0800449 if (getUi() != null && primary != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800450 boolean isWorkCall =
linyuh183cb712017-12-27 17:02:37 -0800451 primary.hasProperty(PROPERTY_ENTERPRISE_CALL)
452 || (primaryContactInfo != null
453 && primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
Eric Erfanianccca3152017-02-22 16:32:36 -0800454 boolean isHdAudioCall =
linyuh183cb712017-12-27 17:02:37 -0800455 isPrimaryCallActive() && primary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700456 boolean isAttemptingHdAudioCall =
457 !isHdAudioCall
linyuh183cb712017-12-27 17:02:37 -0800458 && !primary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
459 && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(context);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700460
linyuh183cb712017-12-27 17:02:37 -0800461 boolean isBusiness = primaryContactInfo != null && primaryContactInfo.isBusiness;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700462
Eric Erfanianccca3152017-02-22 16:32:36 -0800463 // Check for video state change and update the visibility of the contact photo. The contact
464 // photo is hidden when the incoming video surface is shown.
465 // The contact photo visibility can also change in setPrimary().
466 boolean shouldShowContactPhoto =
linyuh183cb712017-12-27 17:02:37 -0800467 !VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800468 getUi()
469 .setCallState(
470 new PrimaryCallState(
linyuh183cb712017-12-27 17:02:37 -0800471 primary.getState(),
472 primary.isVideoCall(),
473 primary.getVideoTech().getSessionModificationState(),
474 primary.getDisconnectCause(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800475 getConnectionLabel(),
476 getCallStateIcon(),
477 getGatewayNumber(),
linyuh183cb712017-12-27 17:02:37 -0800478 shouldShowCallSubject(primary) ? primary.getCallSubject() : null,
wangqi97539352017-09-25 11:15:16 -0700479 PhoneNumberHelper.formatNumber(
linyuh183cb712017-12-27 17:02:37 -0800480 primary.getCallbackNumber(), primary.getSimCountryIso()),
481 primary.hasProperty(Details.PROPERTY_WIFI),
482 primary.isConferenceCall()
483 && !primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE),
Eric Erfanianccca3152017-02-22 16:32:36 -0800484 isWorkCall,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700485 isAttemptingHdAudioCall,
Eric Erfanianccca3152017-02-22 16:32:36 -0800486 isHdAudioCall,
linyuh183cb712017-12-27 17:02:37 -0800487 !TextUtils.isEmpty(primary.getLastForwardedNumber()),
Eric Erfanianccca3152017-02-22 16:32:36 -0800488 shouldShowContactPhoto,
linyuh183cb712017-12-27 17:02:37 -0800489 primary.getConnectTimeMillis(),
490 primary.isVoiceMailNumber(),
491 primary.isRemotelyHeld(),
Eric Erfanian2ca43182017-08-31 06:57:16 -0700492 isBusiness,
493 supports2ndCallOnHold(),
494 getSwapToSecondaryButtonState(),
linyuh183cb712017-12-27 17:02:37 -0800495 primary.isAssistedDialed(),
erfaniand0f207f2017-10-11 12:23:29 -0700496 null,
linyuh183cb712017-12-27 17:02:37 -0800497 primary.getAssistedDialingExtras()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800498
499 InCallActivity activity =
linyuh183cb712017-12-27 17:02:37 -0800500 (InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
Eric Erfanianccca3152017-02-22 16:32:36 -0800501 if (activity != null) {
502 activity.onPrimaryCallStateChanged();
503 }
504 }
505 }
506
Eric Erfanian2ca43182017-08-31 06:57:16 -0700507 private @ButtonState int getSwapToSecondaryButtonState() {
linyuh183cb712017-12-27 17:02:37 -0800508 if (secondary == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700509 return ButtonState.NOT_SUPPORT;
510 }
linyuh183cb712017-12-27 17:02:37 -0800511 if (primary.getState() == State.ACTIVE) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700512 return ButtonState.ENABLED;
513 }
514 return ButtonState.DISABLED;
515 }
516
Eric Erfanianccca3152017-02-22 16:32:36 -0800517 /** Only show the conference call button if we can manage the conference. */
518 private void maybeShowManageConferenceCallButton() {
519 getUi().showManageConferenceCallButton(shouldShowManageConference());
520 }
521
522 /**
523 * Determines if the manage conference button should be visible, based on the current primary
524 * call.
525 *
526 * @return {@code True} if the manage conference button should be visible.
527 */
528 private boolean shouldShowManageConference() {
linyuh183cb712017-12-27 17:02:37 -0800529 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800530 return false;
531 }
532
linyuh183cb712017-12-27 17:02:37 -0800533 return primary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) && !isFullscreen;
Eric Erfanianccca3152017-02-22 16:32:36 -0800534 }
535
Eric Erfanian2ca43182017-08-31 06:57:16 -0700536 private boolean supports2ndCallOnHold() {
537 DialerCall firstCall = CallList.getInstance().getActiveOrBackgroundCall();
538 DialerCall incomingCall = CallList.getInstance().getIncomingCall();
539 if (firstCall != null && incomingCall != null && firstCall != incomingCall) {
540 return incomingCall.can(Details.CAPABILITY_HOLD);
541 }
542 return true;
543 }
544
Eric Erfanianccca3152017-02-22 16:32:36 -0800545 @Override
546 public void onCallStateButtonClicked() {
linyuh183cb712017-12-27 17:02:37 -0800547 Intent broadcastIntent = Bindings.get(context).getCallStateButtonBroadcastIntent(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800548 if (broadcastIntent != null) {
549 LogUtil.v(
550 "CallCardPresenter.onCallStateButtonClicked",
551 "sending call state button broadcast: " + broadcastIntent);
linyuh183cb712017-12-27 17:02:37 -0800552 context.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
Eric Erfanianccca3152017-02-22 16:32:36 -0800553 }
554 }
555
556 @Override
557 public void onManageConferenceClicked() {
558 InCallActivity activity =
linyuh183cb712017-12-27 17:02:37 -0800559 (InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
Eric Erfanianccca3152017-02-22 16:32:36 -0800560 activity.showConferenceFragment(true);
561 }
562
563 @Override
564 public void onShrinkAnimationComplete() {
565 InCallPresenter.getInstance().onShrinkAnimationComplete();
566 }
567
Eric Erfanianccca3152017-02-22 16:32:36 -0800568 private void maybeStartSearch(DialerCall call, boolean isPrimary) {
569 // no need to start search for conference calls which show generic info.
570 if (call != null && !call.isConferenceCall()) {
571 startContactInfoSearch(call, isPrimary, call.getState() == DialerCall.State.INCOMING);
572 }
573 }
574
Eric Erfanianccca3152017-02-22 16:32:36 -0800575 /** Starts a query for more contact data for the save primary and secondary calls. */
576 private void startContactInfoSearch(
577 final DialerCall call, final boolean isPrimary, boolean isIncoming) {
linyuh183cb712017-12-27 17:02:37 -0800578 final ContactInfoCache cache = ContactInfoCache.getInstance(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800579
580 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
581 }
582
583 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
584 final boolean entryMatchesExistingCall =
linyuh183cb712017-12-27 17:02:37 -0800585 (isPrimary && primary != null && TextUtils.equals(callId, primary.getId()))
586 || (!isPrimary && secondary != null && TextUtils.equals(callId, secondary.getId()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800587 if (entryMatchesExistingCall) {
588 updateContactEntry(entry, isPrimary);
589 } else {
590 LogUtil.e(
591 "CallCardPresenter.onContactInfoComplete",
592 "dropping stale contact lookup info for " + callId);
593 }
594
595 final DialerCall call = CallList.getInstance().getCallById(callId);
596 if (call != null) {
597 call.getLogState().contactLookupResult = entry.contactLookupResult;
598 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700599 if (entry.lookupUri != null) {
linyuh183cb712017-12-27 17:02:37 -0800600 CallerInfoUtils.sendViewNotification(context, entry.lookupUri);
Eric Erfanianccca3152017-02-22 16:32:36 -0800601 }
602 }
603
604 private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
605 if (getUi() == null) {
606 return;
607 }
608
609 if (entry.photo != null) {
linyuh183cb712017-12-27 17:02:37 -0800610 if (primary != null && callId.equals(primary.getId())) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800611 updateContactEntry(entry, true /* isPrimary */);
linyuh183cb712017-12-27 17:02:37 -0800612 } else if (secondary != null && callId.equals(secondary.getId())) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800613 updateContactEntry(entry, false /* isPrimary */);
614 }
615 }
616 }
617
618 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
619 if (isPrimary) {
linyuh183cb712017-12-27 17:02:37 -0800620 primaryContactInfo = entry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800621 updatePrimaryDisplayInfo();
622 } else {
linyuh183cb712017-12-27 17:02:37 -0800623 secondaryContactInfo = entry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800624 updateSecondaryDisplayInfo();
625 }
626 }
627
628 /**
629 * Get the highest priority call to display. Goes through the calls and chooses which to return
630 * based on priority of which type of call to display to the user. Callers can use the "ignore"
631 * feature to get the second best call by passing a previously found primary call as ignore.
632 *
633 * @param ignore A call to ignore if found.
634 */
635 private DialerCall getCallToDisplay(
636 CallList callList, DialerCall ignore, boolean skipDisconnected) {
637 // Active calls come second. An active call always gets precedent.
638 DialerCall retval = callList.getActiveCall();
639 if (retval != null && retval != ignore) {
640 return retval;
641 }
642
643 // Sometimes there is intemediate state that two calls are in active even one is about
644 // to be on hold.
645 retval = callList.getSecondActiveCall();
646 if (retval != null && retval != ignore) {
647 return retval;
648 }
649
650 // Disconnected calls get primary position if there are no active calls
651 // to let user know quickly what call has disconnected. Disconnected
652 // calls are very short lived.
653 if (!skipDisconnected) {
654 retval = callList.getDisconnectingCall();
655 if (retval != null && retval != ignore) {
656 return retval;
657 }
658 retval = callList.getDisconnectedCall();
659 if (retval != null && retval != ignore) {
660 return retval;
661 }
662 }
663
664 // Then we go to background call (calls on hold)
665 retval = callList.getBackgroundCall();
666 if (retval != null && retval != ignore) {
667 return retval;
668 }
669
670 // Lastly, we go to a second background call.
671 retval = callList.getSecondBackgroundCall();
672
673 return retval;
674 }
675
676 private void updatePrimaryDisplayInfo() {
linyuh183cb712017-12-27 17:02:37 -0800677 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800678 // TODO: May also occur if search result comes back after ui is destroyed. Look into
679 // removing that case completely.
680 LogUtil.v(
681 "CallCardPresenter.updatePrimaryDisplayInfo",
682 "updatePrimaryDisplayInfo called but ui is null!");
683 return;
684 }
685
linyuh183cb712017-12-27 17:02:37 -0800686 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800687 // Clear the primary display info.
linyuh183cb712017-12-27 17:02:37 -0800688 inCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
Eric Erfanianccca3152017-02-22 16:32:36 -0800689 return;
690 }
691
692 // Hide the contact photo if we are in a video call and the incoming video surface is
693 // showing.
694 boolean showContactPhoto =
linyuh183cb712017-12-27 17:02:37 -0800695 !VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800696
697 // DialerCall placed through a work phone account.
linyuh183cb712017-12-27 17:02:37 -0800698 boolean hasWorkCallProperty = primary.hasProperty(PROPERTY_ENTERPRISE_CALL);
Eric Erfanianccca3152017-02-22 16:32:36 -0800699
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700700 MultimediaData multimediaData = null;
linyuh183cb712017-12-27 17:02:37 -0800701 if (primary.getEnrichedCallSession() != null) {
702 multimediaData = primary.getEnrichedCallSession().getMultimediaData();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700703 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800704
linyuh183cb712017-12-27 17:02:37 -0800705 if (primary.isConferenceCall()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800706 LogUtil.v(
707 "CallCardPresenter.updatePrimaryDisplayInfo",
708 "update primary display info for conference call.");
709
linyuh183cb712017-12-27 17:02:37 -0800710 inCallScreen.setPrimary(
Eric Erfanianccca3152017-02-22 16:32:36 -0800711 new PrimaryInfo(
712 null /* number */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700713 CallerInfoUtils.getConferenceString(
linyuh183cb712017-12-27 17:02:37 -0800714 context, primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800715 false /* nameIsNumber */,
716 null /* location */,
717 null /* label */,
Eric Erfanian83b20212017-05-31 08:53:10 -0700718 null /* photo */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800719 ContactPhotoType.DEFAULT_PLACEHOLDER,
720 false /* isSipCall */,
721 showContactPhoto,
722 hasWorkCallProperty,
723 false /* isSpam */,
wangqiae6c8ec2017-09-28 17:39:40 -0700724 false /* isLocalContact */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800725 false /* answeringDisconnectsOngoingCall */,
726 shouldShowLocation(),
727 null /* contactInfoLookupKey */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700728 null /* enrichedCallMultimediaData */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700729 true /* showInCallButtonGrid */,
linyuh183cb712017-12-27 17:02:37 -0800730 primary.getNumberPresentation()));
731 } else if (primaryContactInfo != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800732 LogUtil.v(
733 "CallCardPresenter.updatePrimaryDisplayInfo",
linyuh183cb712017-12-27 17:02:37 -0800734 "update primary display info for " + primaryContactInfo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800735
linyuh183cb712017-12-27 17:02:37 -0800736 String name = getNameForCall(primaryContactInfo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800737 String number;
738
linyuh183cb712017-12-27 17:02:37 -0800739 boolean isChildNumberShown = !TextUtils.isEmpty(primary.getChildNumber());
740 boolean isForwardedNumberShown = !TextUtils.isEmpty(primary.getLastForwardedNumber());
741 boolean isCallSubjectShown = shouldShowCallSubject(primary);
Eric Erfanianccca3152017-02-22 16:32:36 -0800742
743 if (isCallSubjectShown) {
744 number = null;
745 } else if (isChildNumberShown) {
linyuh183cb712017-12-27 17:02:37 -0800746 number = context.getString(R.string.child_number, primary.getChildNumber());
Eric Erfanianccca3152017-02-22 16:32:36 -0800747 } else if (isForwardedNumberShown) {
748 // Use last forwarded number instead of second line, if present.
linyuh183cb712017-12-27 17:02:37 -0800749 number = primary.getLastForwardedNumber();
Eric Erfanianccca3152017-02-22 16:32:36 -0800750 } else {
linyuh183cb712017-12-27 17:02:37 -0800751 number = primaryContactInfo.number;
Eric Erfanianccca3152017-02-22 16:32:36 -0800752 }
753
linyuh183cb712017-12-27 17:02:37 -0800754 boolean nameIsNumber = name != null && name.equals(primaryContactInfo.number);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700755
Eric Erfanianccca3152017-02-22 16:32:36 -0800756 // DialerCall with caller that is a work contact.
linyuh183cb712017-12-27 17:02:37 -0800757 boolean isWorkContact = (primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
758 inCallScreen.setPrimary(
Eric Erfanianccca3152017-02-22 16:32:36 -0800759 new PrimaryInfo(
760 number,
linyuh183cb712017-12-27 17:02:37 -0800761 primary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800762 nameIsNumber,
linyuh183cb712017-12-27 17:02:37 -0800763 shouldShowLocationAsLabel(nameIsNumber, primaryContactInfo.shouldShowLocation)
764 ? primaryContactInfo.location
Eric Erfaniand8046e52017-04-06 09:41:50 -0700765 : null,
linyuh183cb712017-12-27 17:02:37 -0800766 isChildNumberShown || isCallSubjectShown ? null : primaryContactInfo.label,
767 primaryContactInfo.photo,
768 primaryContactInfo.photoType,
769 primaryContactInfo.isSipCall,
Eric Erfanianccca3152017-02-22 16:32:36 -0800770 showContactPhoto,
771 hasWorkCallProperty || isWorkContact,
linyuh183cb712017-12-27 17:02:37 -0800772 primary.isSpam(),
773 primaryContactInfo.isLocalContact(),
774 primary.answeringDisconnectsForegroundVideoCall(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800775 shouldShowLocation(),
linyuh183cb712017-12-27 17:02:37 -0800776 primaryContactInfo.lookupKey,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700777 multimediaData,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700778 true /* showInCallButtonGrid */,
linyuh183cb712017-12-27 17:02:37 -0800779 primary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800780 } else {
781 // Clear the primary display info.
linyuh183cb712017-12-27 17:02:37 -0800782 inCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
Eric Erfanianccca3152017-02-22 16:32:36 -0800783 }
784
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700785 if (isInCallScreenReady) {
linyuh183cb712017-12-27 17:02:37 -0800786 inCallScreen.showLocationUi(getLocationFragment());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700787 } else {
788 LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
789 }
790 }
791
Eric Erfaniand8046e52017-04-06 09:41:50 -0700792 private static boolean shouldShowLocationAsLabel(
793 boolean nameIsNumber, boolean shouldShowLocation) {
794 if (nameIsNumber) {
795 return true;
796 }
797 if (shouldShowLocation) {
798 return true;
799 }
800 return false;
801 }
802
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700803 private Fragment getLocationFragment() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700804 if (!shouldShowLocation()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700805 return null;
806 }
807 LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
linyuh183cb712017-12-27 17:02:37 -0800808 return callLocation.getLocationFragment(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800809 }
810
811 private boolean shouldShowLocation() {
linyuh183cb712017-12-27 17:02:37 -0800812 if (!ConfigProviderBindings.get(context)
wangqi1d62ab22017-11-29 14:29:31 -0800813 .getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
814 LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
815 return false;
816 }
817 if (!isPotentialEmergencyCall()) {
818 LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
819 return false;
820 }
821 if (!hasLocationPermission()) {
822 LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
823 return false;
824 }
825 if (isBatteryTooLowForEmergencyLocation()) {
826 LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
827 return false;
828 }
linyuh183cb712017-12-27 17:02:37 -0800829 if (ActivityCompat.isInMultiWindowMode(inCallScreen.getInCallScreenFragment().getActivity())) {
wangqi1d62ab22017-11-29 14:29:31 -0800830 LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
831 return false;
832 }
linyuh183cb712017-12-27 17:02:37 -0800833 if (primary.isVideoCall()) {
wangqi1d62ab22017-11-29 14:29:31 -0800834 LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
835 return false;
836 }
linyuh183cb712017-12-27 17:02:37 -0800837 if (!callLocation.canGetLocation(context)) {
wangqi1d62ab22017-11-29 14:29:31 -0800838 LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
839 return false;
840 }
841 return true;
842 }
843
844 private boolean isPotentialEmergencyCall() {
linyuh183cb712017-12-27 17:02:37 -0800845 if (isOutgoingEmergencyCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800846 LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
847 return true;
linyuh183cb712017-12-27 17:02:37 -0800848 } else if (isIncomingEmergencyCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800849 LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
850 return true;
linyuh183cb712017-12-27 17:02:37 -0800851 } else if (isIncomingEmergencyCall(secondary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800852 LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
853 return true;
854 }
855 return false;
856 }
857
858 private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
859 return call != null && !call.isIncoming() && call.isEmergencyCall();
860 }
861
862 private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
863 return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
864 }
865
866 private boolean hasLocationPermission() {
linyuh183cb712017-12-27 17:02:37 -0800867 return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
Eric Erfanianccca3152017-02-22 16:32:36 -0800868 == PackageManager.PERMISSION_GRANTED;
869 }
870
871 private boolean isBatteryTooLowForEmergencyLocation() {
872 Intent batteryStatus =
linyuh183cb712017-12-27 17:02:37 -0800873 context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Eric Erfanianccca3152017-02-22 16:32:36 -0800874 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
875 if (status == BatteryManager.BATTERY_STATUS_CHARGING
876 || status == BatteryManager.BATTERY_STATUS_FULL) {
877 // Plugged in or full battery
878 return false;
879 }
880 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
881 int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
882 float batteryPercent = (100f * level) / scale;
883 long threshold =
linyuh183cb712017-12-27 17:02:37 -0800884 ConfigProviderBindings.get(context)
Eric Erfanianccca3152017-02-22 16:32:36 -0800885 .getLong(
886 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
887 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
888 LogUtil.i(
889 "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
890 "percent charged: " + batteryPercent + ", min required charge: " + threshold);
891 return batteryPercent < threshold;
892 }
893
894 private void updateSecondaryDisplayInfo() {
linyuh183cb712017-12-27 17:02:37 -0800895 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800896 return;
897 }
898
linyuh183cb712017-12-27 17:02:37 -0800899 if (secondary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800900 // Clear the secondary display info.
linyuh183cb712017-12-27 17:02:37 -0800901 inCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(isFullscreen));
Eric Erfanianccca3152017-02-22 16:32:36 -0800902 return;
903 }
904
linyuh183cb712017-12-27 17:02:37 -0800905 if (secondary.isMergeInProcess()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700906 LogUtil.i(
907 "CallCardPresenter.updateSecondaryDisplayInfo",
908 "secondary call is merge in process, clearing info");
linyuh183cb712017-12-27 17:02:37 -0800909 inCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(isFullscreen));
Eric Erfanian2ca43182017-08-31 06:57:16 -0700910 return;
911 }
912
linyuh183cb712017-12-27 17:02:37 -0800913 if (secondary.isConferenceCall()) {
914 inCallScreen.setSecondary(
Eric Erfanianccca3152017-02-22 16:32:36 -0800915 new SecondaryInfo(
916 true /* show */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700917 CallerInfoUtils.getConferenceString(
linyuh183cb712017-12-27 17:02:37 -0800918 context, secondary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800919 false /* nameIsNumber */,
920 null /* label */,
linyuh183cb712017-12-27 17:02:37 -0800921 secondary.getCallProviderLabel(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800922 true /* isConference */,
linyuh183cb712017-12-27 17:02:37 -0800923 secondary.isVideoCall(),
924 isFullscreen));
925 } else if (secondaryContactInfo != null) {
926 LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + secondaryContactInfo);
927 String name = getNameForCall(secondaryContactInfo);
928 boolean nameIsNumber = name != null && name.equals(secondaryContactInfo.number);
929 inCallScreen.setSecondary(
Eric Erfanianccca3152017-02-22 16:32:36 -0800930 new SecondaryInfo(
931 true /* show */,
linyuh183cb712017-12-27 17:02:37 -0800932 secondary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800933 nameIsNumber,
linyuh183cb712017-12-27 17:02:37 -0800934 secondaryContactInfo.label,
935 secondary.getCallProviderLabel(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800936 false /* isConference */,
linyuh183cb712017-12-27 17:02:37 -0800937 secondary.isVideoCall(),
938 isFullscreen));
Eric Erfanianccca3152017-02-22 16:32:36 -0800939 } else {
940 // Clear the secondary display info.
linyuh183cb712017-12-27 17:02:37 -0800941 inCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(isFullscreen));
Eric Erfanianccca3152017-02-22 16:32:36 -0800942 }
943 }
944
945 /** Returns the gateway number for any existing outgoing call. */
946 private String getGatewayNumber() {
947 if (hasOutgoingGatewayCall()) {
linyuh183cb712017-12-27 17:02:37 -0800948 return DialerCall.getNumberFromHandle(primary.getGatewayInfo().getGatewayAddress());
Eric Erfanianccca3152017-02-22 16:32:36 -0800949 }
950 return null;
951 }
952
953 /**
954 * Returns the label (line of text above the number/name) for any given call. For example,
955 * "calling via [Account/Google Voice]" for outgoing calls.
956 */
957 private String getConnectionLabel() {
linyuh183cb712017-12-27 17:02:37 -0800958 if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800959 != PackageManager.PERMISSION_GRANTED) {
960 return null;
961 }
linyuh183cb712017-12-27 17:02:37 -0800962 StatusHints statusHints = primary.getStatusHints();
Eric Erfanianccca3152017-02-22 16:32:36 -0800963 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
964 return statusHints.getLabel().toString();
965 }
966
967 if (hasOutgoingGatewayCall() && getUi() != null) {
968 // Return the label for the gateway app on outgoing calls.
linyuh183cb712017-12-27 17:02:37 -0800969 final PackageManager pm = context.getPackageManager();
Eric Erfanianccca3152017-02-22 16:32:36 -0800970 try {
971 ApplicationInfo info =
linyuh183cb712017-12-27 17:02:37 -0800972 pm.getApplicationInfo(primary.getGatewayInfo().getGatewayProviderPackageName(), 0);
Eric Erfanianccca3152017-02-22 16:32:36 -0800973 return pm.getApplicationLabel(info).toString();
974 } catch (PackageManager.NameNotFoundException e) {
975 LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
976 return null;
977 }
978 }
linyuh183cb712017-12-27 17:02:37 -0800979 return primary.getCallProviderLabel();
Eric Erfanianccca3152017-02-22 16:32:36 -0800980 }
981
982 private Drawable getCallStateIcon() {
983 // Return connection icon if one exists.
linyuh183cb712017-12-27 17:02:37 -0800984 StatusHints statusHints = primary.getStatusHints();
Eric Erfanianccca3152017-02-22 16:32:36 -0800985 if (statusHints != null && statusHints.getIcon() != null) {
linyuh183cb712017-12-27 17:02:37 -0800986 Drawable icon = statusHints.getIcon().loadDrawable(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800987 if (icon != null) {
988 return icon;
989 }
990 }
991
992 return null;
993 }
994
995 private boolean hasOutgoingGatewayCall() {
996 // We only display the gateway information while STATE_DIALING so return false for any other
997 // call state.
998 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
999 // is also called after a contact search completes (call is not present yet). Split the
1000 // UI update so it can receive independent updates.
linyuh183cb712017-12-27 17:02:37 -08001001 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001002 return false;
1003 }
linyuh183cb712017-12-27 17:02:37 -08001004 return DialerCall.State.isDialing(primary.getState())
1005 && primary.getGatewayInfo() != null
1006 && !primary.getGatewayInfo().isEmpty();
Eric Erfanianccca3152017-02-22 16:32:36 -08001007 }
1008
1009 /** Gets the name to display for the call. */
Eric Erfanian2ca43182017-08-31 06:57:16 -07001010 private String getNameForCall(ContactCacheEntry contactInfo) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001011 String preferredName =
1012 ContactDisplayUtils.getPreferredDisplayName(
linyuh183cb712017-12-27 17:02:37 -08001013 contactInfo.namePrimary, contactInfo.nameAlternative, contactsPreferences);
Eric Erfanianccca3152017-02-22 16:32:36 -08001014 if (TextUtils.isEmpty(preferredName)) {
1015 return contactInfo.number;
1016 }
1017 return preferredName;
1018 }
1019
Eric Erfanianccca3152017-02-22 16:32:36 -08001020 @Override
1021 public void onSecondaryInfoClicked() {
linyuh183cb712017-12-27 17:02:37 -08001022 if (secondary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001023 LogUtil.e(
1024 "CallCardPresenter.onSecondaryInfoClicked",
1025 "secondary info clicked but no secondary call.");
1026 return;
1027 }
1028
linyuh183cb712017-12-27 17:02:37 -08001029 Logger.get(context)
Eric Erfanian2ca43182017-08-31 06:57:16 -07001030 .logCallImpression(
1031 DialerImpression.Type.IN_CALL_SWAP_SECONDARY_BUTTON_PRESSED,
linyuh183cb712017-12-27 17:02:37 -08001032 primary.getUniqueCallId(),
1033 primary.getTimeAddedMs());
Eric Erfanianccca3152017-02-22 16:32:36 -08001034 LogUtil.i(
linyuh183cb712017-12-27 17:02:37 -08001035 "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + secondary);
1036 secondary.unhold();
Eric Erfanianccca3152017-02-22 16:32:36 -08001037 }
1038
1039 @Override
1040 public void onEndCallClicked() {
linyuh183cb712017-12-27 17:02:37 -08001041 LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + primary);
1042 if (primary != null) {
1043 primary.disconnect();
Eric Erfanianccca3152017-02-22 16:32:36 -08001044 }
linyuh183cb712017-12-27 17:02:37 -08001045 PostCall.onDisconnectPressed(context);
Eric Erfanianccca3152017-02-22 16:32:36 -08001046 }
1047
1048 /**
1049 * Handles a change to the fullscreen mode of the in-call UI.
1050 *
1051 * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
1052 */
1053 @Override
1054 public void onFullscreenModeChanged(boolean isFullscreenMode) {
linyuh183cb712017-12-27 17:02:37 -08001055 isFullscreen = isFullscreenMode;
1056 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001057 return;
1058 }
1059 maybeShowManageConferenceCallButton();
1060 }
1061
1062 private boolean isPrimaryCallActive() {
linyuh183cb712017-12-27 17:02:37 -08001063 return primary != null && primary.getState() == DialerCall.State.ACTIVE;
Eric Erfanianccca3152017-02-22 16:32:36 -08001064 }
1065
Eric Erfanianccca3152017-02-22 16:32:36 -08001066 private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
1067 if (primary == null) {
1068 return false;
1069 }
1070 if ((!DialerCall.State.isConnectingOrConnected(callState)
1071 && callState != DialerCall.State.DISCONNECTING
1072 && callState != DialerCall.State.DISCONNECTED)
1073 || callState == DialerCall.State.INCOMING) {
1074 return false;
1075 }
linyuh183cb712017-12-27 17:02:37 -08001076 if (this.primary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001077 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001078 return false;
1079 }
1080 return true;
1081 }
1082
1083 @Override
1084 public void onInCallScreenResumed() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001085 updatePrimaryDisplayInfo();
1086
Eric Erfanianccca3152017-02-22 16:32:36 -08001087 if (shouldSendAccessibilityEvent) {
1088 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1089 }
1090 }
1091
Eric Erfaniand8046e52017-04-06 09:41:50 -07001092 @Override
Eric Erfanian2ca43182017-08-31 06:57:16 -07001093 public void onInCallScreenPaused() {}
Eric Erfaniand8046e52017-04-06 09:41:50 -07001094
Eric Erfanianccca3152017-02-22 16:32:36 -08001095 static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
1096 AccessibilityManager am =
1097 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
1098 if (!am.isEnabled()) {
1099 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
1100 return false;
1101 }
1102 if (inCallScreen == null) {
1103 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
1104 return false;
1105 }
1106 Fragment fragment = inCallScreen.getInCallScreenFragment();
1107 if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
1108 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
1109 return false;
1110 }
1111
1112 DisplayManager displayManager =
1113 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1114 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1115 boolean screenIsOn = display.getState() == Display.STATE_ON;
1116 LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
1117 if (!screenIsOn) {
1118 return false;
1119 }
1120
1121 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1122 inCallScreen.dispatchPopulateAccessibilityEvent(event);
1123 View view = inCallScreen.getInCallScreenFragment().getView();
1124 view.getParent().requestSendAccessibilityEvent(view, event);
1125 return true;
1126 }
1127
1128 private void maybeSendAccessibilityEvent(
1129 InCallState oldState, final InCallState newState, boolean primaryChanged) {
1130 shouldSendAccessibilityEvent = false;
linyuh183cb712017-12-27 17:02:37 -08001131 if (context == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001132 return;
1133 }
1134 final AccessibilityManager am =
linyuh183cb712017-12-27 17:02:37 -08001135 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
Eric Erfanianccca3152017-02-22 16:32:36 -08001136 if (!am.isEnabled()) {
1137 return;
1138 }
1139 // Announce the current call if it's new incoming/outgoing call or primary call is changed
1140 // due to switching calls between two ongoing calls (one is on hold).
1141 if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
1142 || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
1143 || primaryChanged) {
1144 LogUtil.i(
1145 "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
1146 shouldSendAccessibilityEvent = true;
1147 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1148 }
1149 }
1150
1151 /**
1152 * Determines whether the call subject should be visible on the UI. For the call subject to be
1153 * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
1154 *
1155 * @param call The call.
1156 * @return {@code true} if the subject should be shown, {@code false} otherwise.
1157 */
1158 private boolean shouldShowCallSubject(DialerCall call) {
1159 if (call == null) {
1160 return false;
1161 }
1162
1163 boolean isIncomingOrWaiting =
linyuh183cb712017-12-27 17:02:37 -08001164 primary.getState() == DialerCall.State.INCOMING
1165 || primary.getState() == DialerCall.State.CALL_WAITING;
Eric Erfanianccca3152017-02-22 16:32:36 -08001166 return isIncomingOrWaiting
1167 && !TextUtils.isEmpty(call.getCallSubject())
1168 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
1169 && call.isCallSubjectSupported();
1170 }
1171
1172 /**
1173 * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
1174 * call with a subject.
1175 *
1176 * @param call The call
1177 * @return {@code true} if the toast should be shown, {@code false} otherwise.
1178 */
1179 private boolean shouldShowNoteSentToast(DialerCall call) {
1180 return call != null
1181 && hasCallSubject(call)
1182 && (call.getState() == DialerCall.State.DIALING
1183 || call.getState() == DialerCall.State.CONNECTING);
1184 }
1185
1186 private InCallScreen getUi() {
linyuh183cb712017-12-27 17:02:37 -08001187 return inCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -08001188 }
1189
1190 public static class ContactLookupCallback implements ContactInfoCacheCallback {
1191
linyuh183cb712017-12-27 17:02:37 -08001192 private final WeakReference<CallCardPresenter> callCardPresenter;
1193 private final boolean isPrimary;
Eric Erfanianccca3152017-02-22 16:32:36 -08001194
1195 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
linyuh183cb712017-12-27 17:02:37 -08001196 this.callCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
1197 this.isPrimary = isPrimary;
Eric Erfanianccca3152017-02-22 16:32:36 -08001198 }
1199
1200 @Override
1201 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
linyuh183cb712017-12-27 17:02:37 -08001202 CallCardPresenter presenter = callCardPresenter.get();
Eric Erfanianccca3152017-02-22 16:32:36 -08001203 if (presenter != null) {
linyuh183cb712017-12-27 17:02:37 -08001204 presenter.onContactInfoComplete(callId, entry, isPrimary);
Eric Erfanianccca3152017-02-22 16:32:36 -08001205 }
1206 }
1207
1208 @Override
1209 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
linyuh183cb712017-12-27 17:02:37 -08001210 CallCardPresenter presenter = callCardPresenter.get();
Eric Erfanianccca3152017-02-22 16:32:36 -08001211 if (presenter != null) {
1212 presenter.onImageLoadComplete(callId, entry);
1213 }
1214 }
1215 }
1216}