blob: bdbbe8f7c6a066f370ec88826d2ce68fbd078ce3 [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;
wangqidd5f1a52018-01-23 18:01:00 -0800120 private String primaryNumber;
linyuh183cb712017-12-27 17:02:37 -0800121 private DialerCall secondary;
wangqidd5f1a52018-01-23 18:01:00 -0800122 private String secondaryNumber;
linyuh183cb712017-12-27 17:02:37 -0800123 private ContactCacheEntry primaryContactInfo;
124 private ContactCacheEntry secondaryContactInfo;
125 @Nullable private ContactsPreferences contactsPreferences;
126 private boolean isFullscreen = false;
127 private InCallScreen inCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -0800128 private boolean isInCallScreenReady;
129 private boolean shouldSendAccessibilityEvent;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700130
131 @NonNull private final CallLocation callLocation;
Eric Erfanianccca3152017-02-22 16:32:36 -0800132 private final Runnable sendAccessibilityEventRunnable =
133 new Runnable() {
134 @Override
135 public void run() {
linyuh183cb712017-12-27 17:02:37 -0800136 shouldSendAccessibilityEvent = !sendAccessibilityEvent(context, getUi());
Eric Erfanianccca3152017-02-22 16:32:36 -0800137 LogUtil.i(
138 "CallCardPresenter.sendAccessibilityEventRunnable",
139 "still should send: %b",
140 shouldSendAccessibilityEvent);
141 if (!shouldSendAccessibilityEvent) {
142 handler.removeCallbacks(this);
143 }
144 }
145 };
146
147 public CallCardPresenter(Context context) {
wangqi385a5a12017-09-28 10:44:54 -0700148 LogUtil.i("CallCardPresenter.constructor", null);
linyuh183cb712017-12-27 17:02:37 -0800149 this.context = Assert.isNotNull(context).getApplicationContext();
150 callLocation = CallLocationComponent.get(this.context).getCallLocation();
Eric Erfanianccca3152017-02-22 16:32:36 -0800151 }
152
153 private static boolean hasCallSubject(DialerCall call) {
154 return !TextUtils.isEmpty(call.getCallSubject());
155 }
156
Android Dialer3855a6b2017-11-21 14:34:47 -0800157 private void addCallFeedbackListener(Context context) {
158 LogUtil.d("CallCardPresenter.addCallFeedbackListener", "Adding call feedback listener");
159 CallList.getInstance().addListener(FeedbackComponent.get(context).getCallFeedbackListener());
160 }
161
Eric Erfanianccca3152017-02-22 16:32:36 -0800162 @Override
163 public void onInCallScreenDelegateInit(InCallScreen inCallScreen) {
164 Assert.isNotNull(inCallScreen);
linyuh183cb712017-12-27 17:02:37 -0800165 this.inCallScreen = inCallScreen;
166 contactsPreferences = ContactsPreferencesFactory.newContactsPreferences(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800167
168 // Call may be null if disconnect happened already.
169 DialerCall call = CallList.getInstance().getFirstCall();
170 if (call != null) {
linyuh183cb712017-12-27 17:02:37 -0800171 primary = call;
172 if (shouldShowNoteSentToast(primary)) {
173 this.inCallScreen.showNoteSentToast();
Eric Erfanianccca3152017-02-22 16:32:36 -0800174 }
175 call.addListener(this);
linyuh183cb712017-12-27 17:02:37 -0800176 addCallFeedbackListener(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800177 // start processing lookups right away.
178 if (!call.isConferenceCall()) {
179 startContactInfoSearch(call, true, call.getState() == DialerCall.State.INCOMING);
180 } else {
181 updateContactEntry(null, true);
182 }
183 }
184
185 onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
186 }
187
188 @Override
189 public void onInCallScreenReady() {
wangqi385a5a12017-09-28 10:44:54 -0700190 LogUtil.i("CallCardPresenter.onInCallScreenReady", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800191 Assert.checkState(!isInCallScreenReady);
linyuh183cb712017-12-27 17:02:37 -0800192 if (contactsPreferences != null) {
193 contactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
Eric Erfanianccca3152017-02-22 16:32:36 -0800194 }
195
Eric Erfanianccca3152017-02-22 16:32:36 -0800196 // Contact search may have completed before ui is ready.
linyuh183cb712017-12-27 17:02:37 -0800197 if (primaryContactInfo != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800198 updatePrimaryDisplayInfo();
199 }
200
201 // Register for call state changes last
202 InCallPresenter.getInstance().addListener(this);
203 InCallPresenter.getInstance().addIncomingCallListener(this);
204 InCallPresenter.getInstance().addDetailsListener(this);
205 InCallPresenter.getInstance().addInCallEventListener(this);
206 isInCallScreenReady = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700207
Eric Erfaniand8046e52017-04-06 09:41:50 -0700208 // Log location impressions
linyuh183cb712017-12-27 17:02:37 -0800209 if (isOutgoingEmergencyCall(primary)) {
210 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_NEW_EMERGENCY_CALL);
211 } else if (isIncomingEmergencyCall(primary) || isIncomingEmergencyCall(secondary)) {
212 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_CALLBACK);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700213 }
214
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700215 // Showing the location may have been skipped if the UI wasn't ready during previous layout.
216 if (shouldShowLocation()) {
linyuh183cb712017-12-27 17:02:37 -0800217 inCallScreen.showLocationUi(getLocationFragment());
Eric Erfaniand8046e52017-04-06 09:41:50 -0700218
219 // Log location impressions
220 if (!hasLocationPermission()) {
linyuh183cb712017-12-27 17:02:37 -0800221 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_NO_LOCATION_PERMISSION);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700222 } else if (isBatteryTooLowForEmergencyLocation()) {
linyuh183cb712017-12-27 17:02:37 -0800223 Logger.get(context)
Eric Erfaniand8046e52017-04-06 09:41:50 -0700224 .logImpression(DialerImpression.Type.EMERGENCY_BATTERY_TOO_LOW_TO_GET_LOCATION);
linyuh183cb712017-12-27 17:02:37 -0800225 } else if (!callLocation.canGetLocation(context)) {
226 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_CANT_GET_LOCATION);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700227 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700228 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800229 }
230
231 @Override
232 public void onInCallScreenUnready() {
wangqi385a5a12017-09-28 10:44:54 -0700233 LogUtil.i("CallCardPresenter.onInCallScreenUnready", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800234 Assert.checkState(isInCallScreenReady);
235
Eric Erfanianccca3152017-02-22 16:32:36 -0800236 // stop getting call state changes
237 InCallPresenter.getInstance().removeListener(this);
238 InCallPresenter.getInstance().removeIncomingCallListener(this);
239 InCallPresenter.getInstance().removeDetailsListener(this);
240 InCallPresenter.getInstance().removeInCallEventListener(this);
linyuh183cb712017-12-27 17:02:37 -0800241 if (primary != null) {
242 primary.removeListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800243 }
244
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700245 callLocation.close();
246
linyuh183cb712017-12-27 17:02:37 -0800247 primary = null;
248 primaryContactInfo = null;
249 secondaryContactInfo = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800250 isInCallScreenReady = false;
251 }
252
253 @Override
254 public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
255 // same logic should happen as with onStateChange()
256 onStateChange(oldState, newState, CallList.getInstance());
257 }
258
259 @Override
260 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700261 Trace.beginSection("CallCardPresenter.onStateChange");
262 LogUtil.v("CallCardPresenter.onStateChange", "oldState: %s, newState: %s", oldState, newState);
linyuh183cb712017-12-27 17:02:37 -0800263 if (inCallScreen == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700264 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800265 return;
266 }
267
268 DialerCall primary = null;
269 DialerCall secondary = null;
270
271 if (newState == InCallState.INCOMING) {
272 primary = callList.getIncomingCall();
273 } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
274 primary = callList.getOutgoingCall();
275 if (primary == null) {
276 primary = callList.getPendingOutgoingCall();
277 }
278
279 // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
280 // highest priority call to display as the secondary call.
281 secondary = getCallToDisplay(callList, null, true);
282 } else if (newState == InCallState.INCALL) {
283 primary = getCallToDisplay(callList, null, false);
284 secondary = getCallToDisplay(callList, primary, true);
285 }
286
287 LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
288 LogUtil.v("CallCardPresenter.onStateChange", "secondary call: " + secondary);
wangqidd5f1a52018-01-23 18:01:00 -0800289 String primaryNumber = null;
290 String secondaryNumber = null;
291 if (primary != null) {
292 primaryNumber = primary.getNumber();
293 }
294 if (secondary != null) {
295 secondaryNumber = secondary.getNumber();
296 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800297
298 final boolean primaryChanged =
linyuh183cb712017-12-27 17:02:37 -0800299 !(DialerCall.areSame(this.primary, primary)
wangqidd5f1a52018-01-23 18:01:00 -0800300 && TextUtils.equals(this.primaryNumber, primaryNumber));
Eric Erfanianccca3152017-02-22 16:32:36 -0800301 final boolean secondaryChanged =
linyuh183cb712017-12-27 17:02:37 -0800302 !(DialerCall.areSame(this.secondary, secondary)
wangqidd5f1a52018-01-23 18:01:00 -0800303 && TextUtils.equals(this.secondaryNumber, secondaryNumber));
Eric Erfanianccca3152017-02-22 16:32:36 -0800304
linyuh183cb712017-12-27 17:02:37 -0800305 this.secondary = secondary;
wangqidd5f1a52018-01-23 18:01:00 -0800306 this.secondaryNumber = secondaryNumber;
linyuh183cb712017-12-27 17:02:37 -0800307 DialerCall previousPrimary = this.primary;
308 this.primary = primary;
wangqidd5f1a52018-01-23 18:01:00 -0800309 this.primaryNumber = primaryNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800310
linyuh183cb712017-12-27 17:02:37 -0800311 if (this.primary != null) {
312 InCallPresenter.getInstance().onForegroundCallChanged(this.primary);
313 inCallScreen.updateInCallScreenColors();
Eric Erfanianccca3152017-02-22 16:32:36 -0800314 }
315
316 if (primaryChanged && shouldShowNoteSentToast(primary)) {
linyuh183cb712017-12-27 17:02:37 -0800317 inCallScreen.showNoteSentToast();
Eric Erfanianccca3152017-02-22 16:32:36 -0800318 }
319
320 // Refresh primary call information if either:
321 // 1. Primary call changed.
322 // 2. The call's ability to manage conference has changed.
323 if (shouldRefreshPrimaryInfo(primaryChanged)) {
324 // primary call has changed
325 if (previousPrimary != null) {
326 previousPrimary.removeListener(this);
327 }
linyuh183cb712017-12-27 17:02:37 -0800328 this.primary.addListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800329
linyuh183cb712017-12-27 17:02:37 -0800330 primaryContactInfo =
Eric Erfanianccca3152017-02-22 16:32:36 -0800331 ContactInfoCache.buildCacheEntryFromCall(
linyuh183cb712017-12-27 17:02:37 -0800332 context, this.primary, this.primary.getState() == DialerCall.State.INCOMING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800333 updatePrimaryDisplayInfo();
linyuh183cb712017-12-27 17:02:37 -0800334 maybeStartSearch(this.primary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800335 }
336
linyuh183cb712017-12-27 17:02:37 -0800337 if (previousPrimary != null && this.primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800338 previousPrimary.removeListener(this);
339 }
340
wangqic8cf79e2017-10-17 09:21:00 -0700341 if (secondaryChanged) {
linyuh183cb712017-12-27 17:02:37 -0800342 if (this.secondary == null) {
wangqic8cf79e2017-10-17 09:21:00 -0700343 // Secondary call may have ended. Update the ui.
linyuh183cb712017-12-27 17:02:37 -0800344 secondaryContactInfo = null;
wangqic8cf79e2017-10-17 09:21:00 -0700345 updateSecondaryDisplayInfo();
346 } else {
347 // secondary call has changed
linyuh183cb712017-12-27 17:02:37 -0800348 secondaryContactInfo =
wangqic8cf79e2017-10-17 09:21:00 -0700349 ContactInfoCache.buildCacheEntryFromCall(
linyuh183cb712017-12-27 17:02:37 -0800350 context, this.secondary, this.secondary.getState() == DialerCall.State.INCOMING);
wangqic8cf79e2017-10-17 09:21:00 -0700351 updateSecondaryDisplayInfo();
linyuh183cb712017-12-27 17:02:37 -0800352 maybeStartSearch(this.secondary, false);
wangqic8cf79e2017-10-17 09:21:00 -0700353 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800354 }
355
356 // Set the call state
357 int callState = DialerCall.State.IDLE;
linyuh183cb712017-12-27 17:02:37 -0800358 if (this.primary != null) {
359 callState = this.primary.getState();
Eric Erfanianccca3152017-02-22 16:32:36 -0800360 updatePrimaryCallState();
361 } else {
362 getUi().setCallState(PrimaryCallState.createEmptyPrimaryCallState());
363 }
364
365 maybeShowManageConferenceCallButton();
366
367 // Hide the end call button instantly if we're receiving an incoming call.
368 getUi()
369 .setEndCallButtonEnabled(
linyuh183cb712017-12-27 17:02:37 -0800370 shouldShowEndCallButton(this.primary, callState),
Eric Erfanianccca3152017-02-22 16:32:36 -0800371 callState != DialerCall.State.INCOMING /* animate */);
372
373 maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700374 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800375 }
376
377 @Override
378 public void onDetailsChanged(DialerCall call, Details details) {
379 updatePrimaryCallState();
380
381 if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
382 != details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
383 maybeShowManageConferenceCallButton();
384 }
385 }
386
387 @Override
388 public void onDialerCallDisconnect() {}
389
390 @Override
391 public void onDialerCallUpdate() {
392 // No-op; specific call updates handled elsewhere.
393 }
394
395 @Override
396 public void onWiFiToLteHandover() {}
397
398 @Override
399 public void onHandoverToWifiFailure() {}
400
Eric Erfanianc857f902017-05-15 14:05:33 -0700401 @Override
402 public void onInternationalCallOnWifi() {}
403
Eric Erfanian2ca43182017-08-31 06:57:16 -0700404 @Override
405 public void onEnrichedCallSessionUpdate() {
406 LogUtil.enterBlock("CallCardPresenter.onEnrichedCallSessionUpdate");
407 updatePrimaryDisplayInfo();
408 }
409
Eric Erfanianccca3152017-02-22 16:32:36 -0800410 /** Handles a change to the child number by refreshing the primary call info. */
411 @Override
412 public void onDialerCallChildNumberChange() {
413 LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
414
linyuh183cb712017-12-27 17:02:37 -0800415 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800416 return;
417 }
418 updatePrimaryDisplayInfo();
419 }
420
421 /** Handles a change to the last forwarding number by refreshing the primary call info. */
422 @Override
423 public void onDialerCallLastForwardedNumberChange() {
424 LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
425
linyuh183cb712017-12-27 17:02:37 -0800426 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800427 return;
428 }
429 updatePrimaryDisplayInfo();
430 updatePrimaryCallState();
431 }
432
433 @Override
434 public void onDialerCallUpgradeToVideo() {}
435
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700436 /** Handles a change to the session modification state for a call. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800437 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700438 public void onDialerCallSessionModificationStateChange() {
439 LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
Eric Erfanianccca3152017-02-22 16:32:36 -0800440
linyuh183cb712017-12-27 17:02:37 -0800441 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800442 return;
443 }
444 getUi()
445 .setEndCallButtonEnabled(
linyuh183cb712017-12-27 17:02:37 -0800446 primary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700447 != SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800448 true /* shouldAnimate */);
449 updatePrimaryCallState();
450 }
451
Eric Erfanianccca3152017-02-22 16:32:36 -0800452 private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
linyuh183cb712017-12-27 17:02:37 -0800453 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800454 return false;
455 }
456 return primaryChanged
linyuh183cb712017-12-27 17:02:37 -0800457 || inCallScreen.isManageConferenceVisible() != shouldShowManageConference();
Eric Erfanianccca3152017-02-22 16:32:36 -0800458 }
459
460 private void updatePrimaryCallState() {
linyuh183cb712017-12-27 17:02:37 -0800461 if (getUi() != null && primary != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800462 boolean isWorkCall =
linyuh183cb712017-12-27 17:02:37 -0800463 primary.hasProperty(PROPERTY_ENTERPRISE_CALL)
464 || (primaryContactInfo != null
465 && primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
Eric Erfanianccca3152017-02-22 16:32:36 -0800466 boolean isHdAudioCall =
linyuh183cb712017-12-27 17:02:37 -0800467 isPrimaryCallActive() && primary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700468 boolean isAttemptingHdAudioCall =
469 !isHdAudioCall
linyuh183cb712017-12-27 17:02:37 -0800470 && !primary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
471 && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(context);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700472
linyuh183cb712017-12-27 17:02:37 -0800473 boolean isBusiness = primaryContactInfo != null && primaryContactInfo.isBusiness;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700474
Eric Erfanianccca3152017-02-22 16:32:36 -0800475 // Check for video state change and update the visibility of the contact photo. The contact
476 // photo is hidden when the incoming video surface is shown.
477 // The contact photo visibility can also change in setPrimary().
478 boolean shouldShowContactPhoto =
linyuh183cb712017-12-27 17:02:37 -0800479 !VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800480 getUi()
481 .setCallState(
482 new PrimaryCallState(
linyuh183cb712017-12-27 17:02:37 -0800483 primary.getState(),
484 primary.isVideoCall(),
485 primary.getVideoTech().getSessionModificationState(),
486 primary.getDisconnectCause(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800487 getConnectionLabel(),
488 getCallStateIcon(),
489 getGatewayNumber(),
linyuh183cb712017-12-27 17:02:37 -0800490 shouldShowCallSubject(primary) ? primary.getCallSubject() : null,
wangqi97539352017-09-25 11:15:16 -0700491 PhoneNumberHelper.formatNumber(
linyuh183cb712017-12-27 17:02:37 -0800492 primary.getCallbackNumber(), primary.getSimCountryIso()),
493 primary.hasProperty(Details.PROPERTY_WIFI),
494 primary.isConferenceCall()
495 && !primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE),
Eric Erfanianccca3152017-02-22 16:32:36 -0800496 isWorkCall,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700497 isAttemptingHdAudioCall,
Eric Erfanianccca3152017-02-22 16:32:36 -0800498 isHdAudioCall,
wangqif4ba3452018-01-09 11:26:29 -0800499 !TextUtils.isEmpty(primary.getLastForwardedNumber()) || primary.isCallForwarded(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800500 shouldShowContactPhoto,
linyuh183cb712017-12-27 17:02:37 -0800501 primary.getConnectTimeMillis(),
502 primary.isVoiceMailNumber(),
503 primary.isRemotelyHeld(),
Eric Erfanian2ca43182017-08-31 06:57:16 -0700504 isBusiness,
505 supports2ndCallOnHold(),
506 getSwapToSecondaryButtonState(),
linyuh183cb712017-12-27 17:02:37 -0800507 primary.isAssistedDialed(),
erfaniand0f207f2017-10-11 12:23:29 -0700508 null,
linyuh183cb712017-12-27 17:02:37 -0800509 primary.getAssistedDialingExtras()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800510
511 InCallActivity activity =
linyuh183cb712017-12-27 17:02:37 -0800512 (InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
Eric Erfanianccca3152017-02-22 16:32:36 -0800513 if (activity != null) {
514 activity.onPrimaryCallStateChanged();
515 }
516 }
517 }
518
Eric Erfanian2ca43182017-08-31 06:57:16 -0700519 private @ButtonState int getSwapToSecondaryButtonState() {
linyuh183cb712017-12-27 17:02:37 -0800520 if (secondary == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700521 return ButtonState.NOT_SUPPORT;
522 }
linyuh183cb712017-12-27 17:02:37 -0800523 if (primary.getState() == State.ACTIVE) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700524 return ButtonState.ENABLED;
525 }
526 return ButtonState.DISABLED;
527 }
528
Eric Erfanianccca3152017-02-22 16:32:36 -0800529 /** Only show the conference call button if we can manage the conference. */
530 private void maybeShowManageConferenceCallButton() {
531 getUi().showManageConferenceCallButton(shouldShowManageConference());
532 }
533
534 /**
535 * Determines if the manage conference button should be visible, based on the current primary
536 * call.
537 *
538 * @return {@code True} if the manage conference button should be visible.
539 */
540 private boolean shouldShowManageConference() {
linyuh183cb712017-12-27 17:02:37 -0800541 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800542 return false;
543 }
544
linyuh183cb712017-12-27 17:02:37 -0800545 return primary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) && !isFullscreen;
Eric Erfanianccca3152017-02-22 16:32:36 -0800546 }
547
Eric Erfanian2ca43182017-08-31 06:57:16 -0700548 private boolean supports2ndCallOnHold() {
549 DialerCall firstCall = CallList.getInstance().getActiveOrBackgroundCall();
550 DialerCall incomingCall = CallList.getInstance().getIncomingCall();
551 if (firstCall != null && incomingCall != null && firstCall != incomingCall) {
552 return incomingCall.can(Details.CAPABILITY_HOLD);
553 }
554 return true;
555 }
556
Eric Erfanianccca3152017-02-22 16:32:36 -0800557 @Override
558 public void onCallStateButtonClicked() {
linyuh183cb712017-12-27 17:02:37 -0800559 Intent broadcastIntent = Bindings.get(context).getCallStateButtonBroadcastIntent(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800560 if (broadcastIntent != null) {
561 LogUtil.v(
562 "CallCardPresenter.onCallStateButtonClicked",
563 "sending call state button broadcast: " + broadcastIntent);
linyuh183cb712017-12-27 17:02:37 -0800564 context.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
Eric Erfanianccca3152017-02-22 16:32:36 -0800565 }
566 }
567
568 @Override
569 public void onManageConferenceClicked() {
570 InCallActivity activity =
linyuh183cb712017-12-27 17:02:37 -0800571 (InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
Eric Erfanianccca3152017-02-22 16:32:36 -0800572 activity.showConferenceFragment(true);
573 }
574
575 @Override
576 public void onShrinkAnimationComplete() {
577 InCallPresenter.getInstance().onShrinkAnimationComplete();
578 }
579
Eric Erfanianccca3152017-02-22 16:32:36 -0800580 private void maybeStartSearch(DialerCall call, boolean isPrimary) {
581 // no need to start search for conference calls which show generic info.
582 if (call != null && !call.isConferenceCall()) {
583 startContactInfoSearch(call, isPrimary, call.getState() == DialerCall.State.INCOMING);
584 }
585 }
586
Eric Erfanianccca3152017-02-22 16:32:36 -0800587 /** Starts a query for more contact data for the save primary and secondary calls. */
588 private void startContactInfoSearch(
589 final DialerCall call, final boolean isPrimary, boolean isIncoming) {
linyuh183cb712017-12-27 17:02:37 -0800590 final ContactInfoCache cache = ContactInfoCache.getInstance(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800591
592 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
593 }
594
595 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
596 final boolean entryMatchesExistingCall =
linyuh183cb712017-12-27 17:02:37 -0800597 (isPrimary && primary != null && TextUtils.equals(callId, primary.getId()))
598 || (!isPrimary && secondary != null && TextUtils.equals(callId, secondary.getId()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800599 if (entryMatchesExistingCall) {
600 updateContactEntry(entry, isPrimary);
601 } else {
602 LogUtil.e(
603 "CallCardPresenter.onContactInfoComplete",
604 "dropping stale contact lookup info for " + callId);
605 }
606
607 final DialerCall call = CallList.getInstance().getCallById(callId);
608 if (call != null) {
609 call.getLogState().contactLookupResult = entry.contactLookupResult;
610 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700611 if (entry.lookupUri != null) {
linyuh183cb712017-12-27 17:02:37 -0800612 CallerInfoUtils.sendViewNotification(context, entry.lookupUri);
Eric Erfanianccca3152017-02-22 16:32:36 -0800613 }
614 }
615
616 private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
617 if (getUi() == null) {
618 return;
619 }
620
621 if (entry.photo != null) {
linyuh183cb712017-12-27 17:02:37 -0800622 if (primary != null && callId.equals(primary.getId())) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800623 updateContactEntry(entry, true /* isPrimary */);
linyuh183cb712017-12-27 17:02:37 -0800624 } else if (secondary != null && callId.equals(secondary.getId())) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800625 updateContactEntry(entry, false /* isPrimary */);
626 }
627 }
628 }
629
630 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
631 if (isPrimary) {
linyuh183cb712017-12-27 17:02:37 -0800632 primaryContactInfo = entry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800633 updatePrimaryDisplayInfo();
634 } else {
linyuh183cb712017-12-27 17:02:37 -0800635 secondaryContactInfo = entry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800636 updateSecondaryDisplayInfo();
637 }
638 }
639
640 /**
641 * Get the highest priority call to display. Goes through the calls and chooses which to return
642 * based on priority of which type of call to display to the user. Callers can use the "ignore"
643 * feature to get the second best call by passing a previously found primary call as ignore.
644 *
645 * @param ignore A call to ignore if found.
646 */
647 private DialerCall getCallToDisplay(
648 CallList callList, DialerCall ignore, boolean skipDisconnected) {
649 // Active calls come second. An active call always gets precedent.
650 DialerCall retval = callList.getActiveCall();
651 if (retval != null && retval != ignore) {
652 return retval;
653 }
654
655 // Sometimes there is intemediate state that two calls are in active even one is about
656 // to be on hold.
657 retval = callList.getSecondActiveCall();
658 if (retval != null && retval != ignore) {
659 return retval;
660 }
661
662 // Disconnected calls get primary position if there are no active calls
663 // to let user know quickly what call has disconnected. Disconnected
664 // calls are very short lived.
665 if (!skipDisconnected) {
666 retval = callList.getDisconnectingCall();
667 if (retval != null && retval != ignore) {
668 return retval;
669 }
670 retval = callList.getDisconnectedCall();
671 if (retval != null && retval != ignore) {
672 return retval;
673 }
674 }
675
676 // Then we go to background call (calls on hold)
677 retval = callList.getBackgroundCall();
678 if (retval != null && retval != ignore) {
679 return retval;
680 }
681
682 // Lastly, we go to a second background call.
683 retval = callList.getSecondBackgroundCall();
684
685 return retval;
686 }
687
688 private void updatePrimaryDisplayInfo() {
linyuh183cb712017-12-27 17:02:37 -0800689 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800690 // TODO: May also occur if search result comes back after ui is destroyed. Look into
691 // removing that case completely.
692 LogUtil.v(
693 "CallCardPresenter.updatePrimaryDisplayInfo",
694 "updatePrimaryDisplayInfo called but ui is null!");
695 return;
696 }
697
linyuh183cb712017-12-27 17:02:37 -0800698 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800699 // Clear the primary display info.
linyuh183cb712017-12-27 17:02:37 -0800700 inCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
Eric Erfanianccca3152017-02-22 16:32:36 -0800701 return;
702 }
703
704 // Hide the contact photo if we are in a video call and the incoming video surface is
705 // showing.
706 boolean showContactPhoto =
linyuh183cb712017-12-27 17:02:37 -0800707 !VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800708
709 // DialerCall placed through a work phone account.
linyuh183cb712017-12-27 17:02:37 -0800710 boolean hasWorkCallProperty = primary.hasProperty(PROPERTY_ENTERPRISE_CALL);
Eric Erfanianccca3152017-02-22 16:32:36 -0800711
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700712 MultimediaData multimediaData = null;
linyuh183cb712017-12-27 17:02:37 -0800713 if (primary.getEnrichedCallSession() != null) {
714 multimediaData = primary.getEnrichedCallSession().getMultimediaData();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700715 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800716
linyuh183cb712017-12-27 17:02:37 -0800717 if (primary.isConferenceCall()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800718 LogUtil.v(
719 "CallCardPresenter.updatePrimaryDisplayInfo",
720 "update primary display info for conference call.");
721
linyuh183cb712017-12-27 17:02:37 -0800722 inCallScreen.setPrimary(
Eric Erfanianccca3152017-02-22 16:32:36 -0800723 new PrimaryInfo(
724 null /* number */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700725 CallerInfoUtils.getConferenceString(
linyuh183cb712017-12-27 17:02:37 -0800726 context, primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800727 false /* nameIsNumber */,
728 null /* location */,
729 null /* label */,
Eric Erfanian83b20212017-05-31 08:53:10 -0700730 null /* photo */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800731 ContactPhotoType.DEFAULT_PLACEHOLDER,
732 false /* isSipCall */,
733 showContactPhoto,
734 hasWorkCallProperty,
735 false /* isSpam */,
wangqiae6c8ec2017-09-28 17:39:40 -0700736 false /* isLocalContact */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800737 false /* answeringDisconnectsOngoingCall */,
738 shouldShowLocation(),
739 null /* contactInfoLookupKey */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700740 null /* enrichedCallMultimediaData */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700741 true /* showInCallButtonGrid */,
linyuh183cb712017-12-27 17:02:37 -0800742 primary.getNumberPresentation()));
743 } else if (primaryContactInfo != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800744 LogUtil.v(
745 "CallCardPresenter.updatePrimaryDisplayInfo",
linyuh183cb712017-12-27 17:02:37 -0800746 "update primary display info for " + primaryContactInfo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800747
linyuh183cb712017-12-27 17:02:37 -0800748 String name = getNameForCall(primaryContactInfo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800749 String number;
750
linyuh183cb712017-12-27 17:02:37 -0800751 boolean isChildNumberShown = !TextUtils.isEmpty(primary.getChildNumber());
752 boolean isForwardedNumberShown = !TextUtils.isEmpty(primary.getLastForwardedNumber());
753 boolean isCallSubjectShown = shouldShowCallSubject(primary);
Eric Erfanianccca3152017-02-22 16:32:36 -0800754
755 if (isCallSubjectShown) {
756 number = null;
757 } else if (isChildNumberShown) {
linyuh183cb712017-12-27 17:02:37 -0800758 number = context.getString(R.string.child_number, primary.getChildNumber());
Eric Erfanianccca3152017-02-22 16:32:36 -0800759 } else if (isForwardedNumberShown) {
760 // Use last forwarded number instead of second line, if present.
linyuh183cb712017-12-27 17:02:37 -0800761 number = primary.getLastForwardedNumber();
Eric Erfanianccca3152017-02-22 16:32:36 -0800762 } else {
linyuh183cb712017-12-27 17:02:37 -0800763 number = primaryContactInfo.number;
Eric Erfanianccca3152017-02-22 16:32:36 -0800764 }
765
linyuh183cb712017-12-27 17:02:37 -0800766 boolean nameIsNumber = name != null && name.equals(primaryContactInfo.number);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700767
Eric Erfanianccca3152017-02-22 16:32:36 -0800768 // DialerCall with caller that is a work contact.
linyuh183cb712017-12-27 17:02:37 -0800769 boolean isWorkContact = (primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
770 inCallScreen.setPrimary(
Eric Erfanianccca3152017-02-22 16:32:36 -0800771 new PrimaryInfo(
772 number,
linyuh183cb712017-12-27 17:02:37 -0800773 primary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800774 nameIsNumber,
linyuh183cb712017-12-27 17:02:37 -0800775 shouldShowLocationAsLabel(nameIsNumber, primaryContactInfo.shouldShowLocation)
776 ? primaryContactInfo.location
Eric Erfaniand8046e52017-04-06 09:41:50 -0700777 : null,
linyuh183cb712017-12-27 17:02:37 -0800778 isChildNumberShown || isCallSubjectShown ? null : primaryContactInfo.label,
779 primaryContactInfo.photo,
780 primaryContactInfo.photoType,
781 primaryContactInfo.isSipCall,
Eric Erfanianccca3152017-02-22 16:32:36 -0800782 showContactPhoto,
783 hasWorkCallProperty || isWorkContact,
linyuh183cb712017-12-27 17:02:37 -0800784 primary.isSpam(),
785 primaryContactInfo.isLocalContact(),
786 primary.answeringDisconnectsForegroundVideoCall(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800787 shouldShowLocation(),
linyuh183cb712017-12-27 17:02:37 -0800788 primaryContactInfo.lookupKey,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700789 multimediaData,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700790 true /* showInCallButtonGrid */,
linyuh183cb712017-12-27 17:02:37 -0800791 primary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800792 } else {
793 // Clear the primary display info.
linyuh183cb712017-12-27 17:02:37 -0800794 inCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
Eric Erfanianccca3152017-02-22 16:32:36 -0800795 }
796
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700797 if (isInCallScreenReady) {
linyuh183cb712017-12-27 17:02:37 -0800798 inCallScreen.showLocationUi(getLocationFragment());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700799 } else {
800 LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
801 }
802 }
803
Eric Erfaniand8046e52017-04-06 09:41:50 -0700804 private static boolean shouldShowLocationAsLabel(
805 boolean nameIsNumber, boolean shouldShowLocation) {
806 if (nameIsNumber) {
807 return true;
808 }
809 if (shouldShowLocation) {
810 return true;
811 }
812 return false;
813 }
814
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700815 private Fragment getLocationFragment() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700816 if (!shouldShowLocation()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700817 return null;
818 }
819 LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
linyuh183cb712017-12-27 17:02:37 -0800820 return callLocation.getLocationFragment(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800821 }
822
823 private boolean shouldShowLocation() {
linyuh183cb712017-12-27 17:02:37 -0800824 if (!ConfigProviderBindings.get(context)
wangqi1d62ab22017-11-29 14:29:31 -0800825 .getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
826 LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
827 return false;
828 }
829 if (!isPotentialEmergencyCall()) {
830 LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
831 return false;
832 }
833 if (!hasLocationPermission()) {
834 LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
835 return false;
836 }
837 if (isBatteryTooLowForEmergencyLocation()) {
838 LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
839 return false;
840 }
linyuh183cb712017-12-27 17:02:37 -0800841 if (ActivityCompat.isInMultiWindowMode(inCallScreen.getInCallScreenFragment().getActivity())) {
wangqi1d62ab22017-11-29 14:29:31 -0800842 LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
843 return false;
844 }
linyuh183cb712017-12-27 17:02:37 -0800845 if (primary.isVideoCall()) {
wangqi1d62ab22017-11-29 14:29:31 -0800846 LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
847 return false;
848 }
linyuh183cb712017-12-27 17:02:37 -0800849 if (!callLocation.canGetLocation(context)) {
wangqi1d62ab22017-11-29 14:29:31 -0800850 LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
851 return false;
852 }
853 return true;
854 }
855
856 private boolean isPotentialEmergencyCall() {
linyuh183cb712017-12-27 17:02:37 -0800857 if (isOutgoingEmergencyCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800858 LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
859 return true;
linyuh183cb712017-12-27 17:02:37 -0800860 } else if (isIncomingEmergencyCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800861 LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
862 return true;
linyuh183cb712017-12-27 17:02:37 -0800863 } else if (isIncomingEmergencyCall(secondary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800864 LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
865 return true;
866 }
867 return false;
868 }
869
870 private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
871 return call != null && !call.isIncoming() && call.isEmergencyCall();
872 }
873
874 private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
875 return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
876 }
877
878 private boolean hasLocationPermission() {
linyuh183cb712017-12-27 17:02:37 -0800879 return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
Eric Erfanianccca3152017-02-22 16:32:36 -0800880 == PackageManager.PERMISSION_GRANTED;
881 }
882
883 private boolean isBatteryTooLowForEmergencyLocation() {
884 Intent batteryStatus =
linyuh183cb712017-12-27 17:02:37 -0800885 context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Eric Erfanianccca3152017-02-22 16:32:36 -0800886 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
887 if (status == BatteryManager.BATTERY_STATUS_CHARGING
888 || status == BatteryManager.BATTERY_STATUS_FULL) {
889 // Plugged in or full battery
890 return false;
891 }
892 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
893 int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
894 float batteryPercent = (100f * level) / scale;
895 long threshold =
linyuh183cb712017-12-27 17:02:37 -0800896 ConfigProviderBindings.get(context)
Eric Erfanianccca3152017-02-22 16:32:36 -0800897 .getLong(
898 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
899 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
900 LogUtil.i(
901 "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
902 "percent charged: " + batteryPercent + ", min required charge: " + threshold);
903 return batteryPercent < threshold;
904 }
905
906 private void updateSecondaryDisplayInfo() {
linyuh183cb712017-12-27 17:02:37 -0800907 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800908 return;
909 }
910
linyuh183cb712017-12-27 17:02:37 -0800911 if (secondary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800912 // Clear the secondary display info.
linyuh183cb712017-12-27 17:02:37 -0800913 inCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(isFullscreen));
Eric Erfanianccca3152017-02-22 16:32:36 -0800914 return;
915 }
916
linyuh183cb712017-12-27 17:02:37 -0800917 if (secondary.isMergeInProcess()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700918 LogUtil.i(
919 "CallCardPresenter.updateSecondaryDisplayInfo",
920 "secondary call is merge in process, clearing info");
linyuh183cb712017-12-27 17:02:37 -0800921 inCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(isFullscreen));
Eric Erfanian2ca43182017-08-31 06:57:16 -0700922 return;
923 }
924
linyuh183cb712017-12-27 17:02:37 -0800925 if (secondary.isConferenceCall()) {
926 inCallScreen.setSecondary(
Eric Erfanianccca3152017-02-22 16:32:36 -0800927 new SecondaryInfo(
928 true /* show */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700929 CallerInfoUtils.getConferenceString(
linyuh183cb712017-12-27 17:02:37 -0800930 context, secondary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800931 false /* nameIsNumber */,
932 null /* label */,
linyuh183cb712017-12-27 17:02:37 -0800933 secondary.getCallProviderLabel(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800934 true /* isConference */,
linyuh183cb712017-12-27 17:02:37 -0800935 secondary.isVideoCall(),
936 isFullscreen));
937 } else if (secondaryContactInfo != null) {
938 LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + secondaryContactInfo);
939 String name = getNameForCall(secondaryContactInfo);
940 boolean nameIsNumber = name != null && name.equals(secondaryContactInfo.number);
941 inCallScreen.setSecondary(
Eric Erfanianccca3152017-02-22 16:32:36 -0800942 new SecondaryInfo(
943 true /* show */,
linyuh183cb712017-12-27 17:02:37 -0800944 secondary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800945 nameIsNumber,
linyuh183cb712017-12-27 17:02:37 -0800946 secondaryContactInfo.label,
947 secondary.getCallProviderLabel(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800948 false /* isConference */,
linyuh183cb712017-12-27 17:02:37 -0800949 secondary.isVideoCall(),
950 isFullscreen));
Eric Erfanianccca3152017-02-22 16:32:36 -0800951 } else {
952 // Clear the secondary display info.
linyuh183cb712017-12-27 17:02:37 -0800953 inCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(isFullscreen));
Eric Erfanianccca3152017-02-22 16:32:36 -0800954 }
955 }
956
957 /** Returns the gateway number for any existing outgoing call. */
958 private String getGatewayNumber() {
959 if (hasOutgoingGatewayCall()) {
linyuh183cb712017-12-27 17:02:37 -0800960 return DialerCall.getNumberFromHandle(primary.getGatewayInfo().getGatewayAddress());
Eric Erfanianccca3152017-02-22 16:32:36 -0800961 }
962 return null;
963 }
964
965 /**
966 * Returns the label (line of text above the number/name) for any given call. For example,
967 * "calling via [Account/Google Voice]" for outgoing calls.
968 */
969 private String getConnectionLabel() {
linyuh183cb712017-12-27 17:02:37 -0800970 if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800971 != PackageManager.PERMISSION_GRANTED) {
972 return null;
973 }
linyuh183cb712017-12-27 17:02:37 -0800974 StatusHints statusHints = primary.getStatusHints();
Eric Erfanianccca3152017-02-22 16:32:36 -0800975 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
976 return statusHints.getLabel().toString();
977 }
978
979 if (hasOutgoingGatewayCall() && getUi() != null) {
980 // Return the label for the gateway app on outgoing calls.
linyuh183cb712017-12-27 17:02:37 -0800981 final PackageManager pm = context.getPackageManager();
Eric Erfanianccca3152017-02-22 16:32:36 -0800982 try {
983 ApplicationInfo info =
linyuh183cb712017-12-27 17:02:37 -0800984 pm.getApplicationInfo(primary.getGatewayInfo().getGatewayProviderPackageName(), 0);
Eric Erfanianccca3152017-02-22 16:32:36 -0800985 return pm.getApplicationLabel(info).toString();
986 } catch (PackageManager.NameNotFoundException e) {
987 LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
988 return null;
989 }
990 }
linyuh183cb712017-12-27 17:02:37 -0800991 return primary.getCallProviderLabel();
Eric Erfanianccca3152017-02-22 16:32:36 -0800992 }
993
994 private Drawable getCallStateIcon() {
995 // Return connection icon if one exists.
linyuh183cb712017-12-27 17:02:37 -0800996 StatusHints statusHints = primary.getStatusHints();
Eric Erfanianccca3152017-02-22 16:32:36 -0800997 if (statusHints != null && statusHints.getIcon() != null) {
linyuh183cb712017-12-27 17:02:37 -0800998 Drawable icon = statusHints.getIcon().loadDrawable(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800999 if (icon != null) {
1000 return icon;
1001 }
1002 }
1003
1004 return null;
1005 }
1006
1007 private boolean hasOutgoingGatewayCall() {
1008 // We only display the gateway information while STATE_DIALING so return false for any other
1009 // call state.
1010 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
1011 // is also called after a contact search completes (call is not present yet). Split the
1012 // UI update so it can receive independent updates.
linyuh183cb712017-12-27 17:02:37 -08001013 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001014 return false;
1015 }
linyuh183cb712017-12-27 17:02:37 -08001016 return DialerCall.State.isDialing(primary.getState())
1017 && primary.getGatewayInfo() != null
1018 && !primary.getGatewayInfo().isEmpty();
Eric Erfanianccca3152017-02-22 16:32:36 -08001019 }
1020
1021 /** Gets the name to display for the call. */
Eric Erfanian2ca43182017-08-31 06:57:16 -07001022 private String getNameForCall(ContactCacheEntry contactInfo) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001023 String preferredName =
1024 ContactDisplayUtils.getPreferredDisplayName(
linyuh183cb712017-12-27 17:02:37 -08001025 contactInfo.namePrimary, contactInfo.nameAlternative, contactsPreferences);
Eric Erfanianccca3152017-02-22 16:32:36 -08001026 if (TextUtils.isEmpty(preferredName)) {
1027 return contactInfo.number;
1028 }
1029 return preferredName;
1030 }
1031
Eric Erfanianccca3152017-02-22 16:32:36 -08001032 @Override
1033 public void onSecondaryInfoClicked() {
linyuh183cb712017-12-27 17:02:37 -08001034 if (secondary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001035 LogUtil.e(
1036 "CallCardPresenter.onSecondaryInfoClicked",
1037 "secondary info clicked but no secondary call.");
1038 return;
1039 }
1040
linyuh183cb712017-12-27 17:02:37 -08001041 Logger.get(context)
Eric Erfanian2ca43182017-08-31 06:57:16 -07001042 .logCallImpression(
1043 DialerImpression.Type.IN_CALL_SWAP_SECONDARY_BUTTON_PRESSED,
linyuh183cb712017-12-27 17:02:37 -08001044 primary.getUniqueCallId(),
1045 primary.getTimeAddedMs());
Eric Erfanianccca3152017-02-22 16:32:36 -08001046 LogUtil.i(
linyuh183cb712017-12-27 17:02:37 -08001047 "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + secondary);
1048 secondary.unhold();
Eric Erfanianccca3152017-02-22 16:32:36 -08001049 }
1050
1051 @Override
1052 public void onEndCallClicked() {
linyuh183cb712017-12-27 17:02:37 -08001053 LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + primary);
1054 if (primary != null) {
1055 primary.disconnect();
Eric Erfanianccca3152017-02-22 16:32:36 -08001056 }
linyuh183cb712017-12-27 17:02:37 -08001057 PostCall.onDisconnectPressed(context);
Eric Erfanianccca3152017-02-22 16:32:36 -08001058 }
1059
1060 /**
1061 * Handles a change to the fullscreen mode of the in-call UI.
1062 *
1063 * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
1064 */
1065 @Override
1066 public void onFullscreenModeChanged(boolean isFullscreenMode) {
linyuh183cb712017-12-27 17:02:37 -08001067 isFullscreen = isFullscreenMode;
1068 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001069 return;
1070 }
1071 maybeShowManageConferenceCallButton();
1072 }
1073
1074 private boolean isPrimaryCallActive() {
linyuh183cb712017-12-27 17:02:37 -08001075 return primary != null && primary.getState() == DialerCall.State.ACTIVE;
Eric Erfanianccca3152017-02-22 16:32:36 -08001076 }
1077
Eric Erfanianccca3152017-02-22 16:32:36 -08001078 private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
1079 if (primary == null) {
1080 return false;
1081 }
1082 if ((!DialerCall.State.isConnectingOrConnected(callState)
1083 && callState != DialerCall.State.DISCONNECTING
1084 && callState != DialerCall.State.DISCONNECTED)
1085 || callState == DialerCall.State.INCOMING) {
1086 return false;
1087 }
linyuh183cb712017-12-27 17:02:37 -08001088 if (this.primary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001089 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001090 return false;
1091 }
1092 return true;
1093 }
1094
1095 @Override
1096 public void onInCallScreenResumed() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001097 updatePrimaryDisplayInfo();
1098
Eric Erfanianccca3152017-02-22 16:32:36 -08001099 if (shouldSendAccessibilityEvent) {
1100 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1101 }
1102 }
1103
Eric Erfaniand8046e52017-04-06 09:41:50 -07001104 @Override
Eric Erfanian2ca43182017-08-31 06:57:16 -07001105 public void onInCallScreenPaused() {}
Eric Erfaniand8046e52017-04-06 09:41:50 -07001106
Eric Erfanianccca3152017-02-22 16:32:36 -08001107 static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
1108 AccessibilityManager am =
1109 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
1110 if (!am.isEnabled()) {
1111 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
1112 return false;
1113 }
1114 if (inCallScreen == null) {
1115 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
1116 return false;
1117 }
1118 Fragment fragment = inCallScreen.getInCallScreenFragment();
1119 if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
1120 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
1121 return false;
1122 }
1123
1124 DisplayManager displayManager =
1125 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1126 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1127 boolean screenIsOn = display.getState() == Display.STATE_ON;
1128 LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
1129 if (!screenIsOn) {
1130 return false;
1131 }
1132
1133 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1134 inCallScreen.dispatchPopulateAccessibilityEvent(event);
1135 View view = inCallScreen.getInCallScreenFragment().getView();
1136 view.getParent().requestSendAccessibilityEvent(view, event);
1137 return true;
1138 }
1139
1140 private void maybeSendAccessibilityEvent(
1141 InCallState oldState, final InCallState newState, boolean primaryChanged) {
1142 shouldSendAccessibilityEvent = false;
linyuh183cb712017-12-27 17:02:37 -08001143 if (context == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001144 return;
1145 }
1146 final AccessibilityManager am =
linyuh183cb712017-12-27 17:02:37 -08001147 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
Eric Erfanianccca3152017-02-22 16:32:36 -08001148 if (!am.isEnabled()) {
1149 return;
1150 }
1151 // Announce the current call if it's new incoming/outgoing call or primary call is changed
1152 // due to switching calls between two ongoing calls (one is on hold).
1153 if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
1154 || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
1155 || primaryChanged) {
1156 LogUtil.i(
1157 "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
1158 shouldSendAccessibilityEvent = true;
1159 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1160 }
1161 }
1162
1163 /**
1164 * Determines whether the call subject should be visible on the UI. For the call subject to be
1165 * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
1166 *
1167 * @param call The call.
1168 * @return {@code true} if the subject should be shown, {@code false} otherwise.
1169 */
1170 private boolean shouldShowCallSubject(DialerCall call) {
1171 if (call == null) {
1172 return false;
1173 }
1174
1175 boolean isIncomingOrWaiting =
linyuh183cb712017-12-27 17:02:37 -08001176 primary.getState() == DialerCall.State.INCOMING
1177 || primary.getState() == DialerCall.State.CALL_WAITING;
Eric Erfanianccca3152017-02-22 16:32:36 -08001178 return isIncomingOrWaiting
1179 && !TextUtils.isEmpty(call.getCallSubject())
1180 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
1181 && call.isCallSubjectSupported();
1182 }
1183
1184 /**
1185 * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
1186 * call with a subject.
1187 *
1188 * @param call The call
1189 * @return {@code true} if the toast should be shown, {@code false} otherwise.
1190 */
1191 private boolean shouldShowNoteSentToast(DialerCall call) {
1192 return call != null
1193 && hasCallSubject(call)
1194 && (call.getState() == DialerCall.State.DIALING
1195 || call.getState() == DialerCall.State.CONNECTING);
1196 }
1197
1198 private InCallScreen getUi() {
linyuh183cb712017-12-27 17:02:37 -08001199 return inCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -08001200 }
1201
1202 public static class ContactLookupCallback implements ContactInfoCacheCallback {
1203
linyuh183cb712017-12-27 17:02:37 -08001204 private final WeakReference<CallCardPresenter> callCardPresenter;
1205 private final boolean isPrimary;
Eric Erfanianccca3152017-02-22 16:32:36 -08001206
1207 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
linyuh183cb712017-12-27 17:02:37 -08001208 this.callCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
1209 this.isPrimary = isPrimary;
Eric Erfanianccca3152017-02-22 16:32:36 -08001210 }
1211
1212 @Override
1213 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
linyuh183cb712017-12-27 17:02:37 -08001214 CallCardPresenter presenter = callCardPresenter.get();
Eric Erfanianccca3152017-02-22 16:32:36 -08001215 if (presenter != null) {
linyuh183cb712017-12-27 17:02:37 -08001216 presenter.onContactInfoComplete(callId, entry, isPrimary);
Eric Erfanianccca3152017-02-22 16:32:36 -08001217 }
1218 }
1219
1220 @Override
1221 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
linyuh183cb712017-12-27 17:02:37 -08001222 CallCardPresenter presenter = callCardPresenter.get();
Eric Erfanianccca3152017-02-22 16:32:36 -08001223 if (presenter != null) {
1224 presenter.onImageLoadComplete(callId, entry);
1225 }
1226 }
1227 }
1228}