blob: da5d1a8dd9a7be075e6068b68308c70d2f9eb721 [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;
Eric Erfanian8369df02017-05-03 10:27:13 -070051import com.android.dialer.logging.DialerImpression;
Eric Erfaniand8046e52017-04-06 09:41:50 -070052import com.android.dialer.logging.Logger;
Eric Erfanianccca3152017-02-22 16:32:36 -080053import com.android.dialer.multimedia.MultimediaData;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070054import com.android.dialer.oem.MotorolaUtils;
wangqi97539352017-09-25 11:15:16 -070055import com.android.dialer.phonenumberutil.PhoneNumberHelper;
Eric Erfanian2ca43182017-08-31 06:57:16 -070056import com.android.dialer.postcall.PostCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080057import com.android.incallui.ContactInfoCache.ContactCacheEntry;
58import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
59import com.android.incallui.InCallPresenter.InCallDetailsListener;
60import com.android.incallui.InCallPresenter.InCallEventListener;
61import com.android.incallui.InCallPresenter.InCallState;
62import com.android.incallui.InCallPresenter.InCallStateListener;
63import com.android.incallui.InCallPresenter.IncomingCallListener;
64import com.android.incallui.call.CallList;
65import com.android.incallui.call.DialerCall;
Eric Erfanian2ca43182017-08-31 06:57:16 -070066import com.android.incallui.call.DialerCall.State;
Eric Erfanianccca3152017-02-22 16:32:36 -080067import com.android.incallui.call.DialerCallListener;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070068import com.android.incallui.calllocation.CallLocation;
69import com.android.incallui.calllocation.CallLocationComponent;
Eric Erfanianccca3152017-02-22 16:32:36 -080070import com.android.incallui.incall.protocol.ContactPhotoType;
71import com.android.incallui.incall.protocol.InCallScreen;
72import com.android.incallui.incall.protocol.InCallScreenDelegate;
73import com.android.incallui.incall.protocol.PrimaryCallState;
Eric Erfanian2ca43182017-08-31 06:57:16 -070074import com.android.incallui.incall.protocol.PrimaryCallState.ButtonState;
Eric Erfanianccca3152017-02-22 16:32:36 -080075import com.android.incallui.incall.protocol.PrimaryInfo;
76import com.android.incallui.incall.protocol.SecondaryInfo;
Eric Erfanian90508232017-03-24 09:31:16 -070077import com.android.incallui.videotech.utils.SessionModificationState;
Eric Erfanianccca3152017-02-22 16:32:36 -080078import java.lang.ref.WeakReference;
79
80/**
81 * Controller for the Call Card Fragment. This class listens for changes to InCallState and passes
82 * it along to the fragment.
83 */
84public class CallCardPresenter
85 implements InCallStateListener,
86 IncomingCallListener,
87 InCallDetailsListener,
88 InCallEventListener,
89 InCallScreenDelegate,
Eric Erfanian2ca43182017-08-31 06:57:16 -070090 DialerCallListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080091
92 /**
93 * Amount of time to wait before sending an announcement via the accessibility manager. When the
94 * call state changes to an outgoing or incoming state for the first time, the UI can often be
95 * changing due to call updates or contact lookup. This allows the UI to settle to a stable state
96 * to ensure that the correct information is announced.
97 */
98 private static final long ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS = 500;
99
100 /** Flag to allow the user's current location to be shown during emergency calls. */
101 private static final String CONFIG_ENABLE_EMERGENCY_LOCATION = "config_enable_emergency_location";
102
103 private static final boolean CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT = true;
104
105 /**
106 * Make it possible to not get location during an emergency call if the battery is too low, since
107 * doing so could trigger gps and thus potentially cause the phone to die in the middle of the
108 * call.
109 */
110 private static final String CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION =
111 "min_battery_percent_for_emergency_location";
112
113 private static final long CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT = 10;
114
linyuh183cb712017-12-27 17:02:37 -0800115 private final Context context;
Eric Erfanianccca3152017-02-22 16:32:36 -0800116 private final Handler handler = new Handler();
117
linyuh183cb712017-12-27 17:02:37 -0800118 private DialerCall primary;
wangqidd5f1a52018-01-23 18:01:00 -0800119 private String primaryNumber;
linyuh183cb712017-12-27 17:02:37 -0800120 private DialerCall secondary;
wangqidd5f1a52018-01-23 18:01:00 -0800121 private String secondaryNumber;
linyuh183cb712017-12-27 17:02:37 -0800122 private ContactCacheEntry primaryContactInfo;
123 private ContactCacheEntry secondaryContactInfo;
124 @Nullable private ContactsPreferences contactsPreferences;
125 private boolean isFullscreen = false;
126 private InCallScreen inCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -0800127 private boolean isInCallScreenReady;
128 private boolean shouldSendAccessibilityEvent;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700129
130 @NonNull private final CallLocation callLocation;
Eric Erfanianccca3152017-02-22 16:32:36 -0800131 private final Runnable sendAccessibilityEventRunnable =
132 new Runnable() {
133 @Override
134 public void run() {
linyuh183cb712017-12-27 17:02:37 -0800135 shouldSendAccessibilityEvent = !sendAccessibilityEvent(context, getUi());
Eric Erfanianccca3152017-02-22 16:32:36 -0800136 LogUtil.i(
137 "CallCardPresenter.sendAccessibilityEventRunnable",
138 "still should send: %b",
139 shouldSendAccessibilityEvent);
140 if (!shouldSendAccessibilityEvent) {
141 handler.removeCallbacks(this);
142 }
143 }
144 };
145
146 public CallCardPresenter(Context context) {
wangqi385a5a12017-09-28 10:44:54 -0700147 LogUtil.i("CallCardPresenter.constructor", null);
linyuh183cb712017-12-27 17:02:37 -0800148 this.context = Assert.isNotNull(context).getApplicationContext();
149 callLocation = CallLocationComponent.get(this.context).getCallLocation();
Eric Erfanianccca3152017-02-22 16:32:36 -0800150 }
151
152 private static boolean hasCallSubject(DialerCall call) {
153 return !TextUtils.isEmpty(call.getCallSubject());
154 }
155
156 @Override
157 public void onInCallScreenDelegateInit(InCallScreen inCallScreen) {
158 Assert.isNotNull(inCallScreen);
linyuh183cb712017-12-27 17:02:37 -0800159 this.inCallScreen = inCallScreen;
160 contactsPreferences = ContactsPreferencesFactory.newContactsPreferences(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800161
162 // Call may be null if disconnect happened already.
163 DialerCall call = CallList.getInstance().getFirstCall();
164 if (call != null) {
linyuh183cb712017-12-27 17:02:37 -0800165 primary = call;
166 if (shouldShowNoteSentToast(primary)) {
167 this.inCallScreen.showNoteSentToast();
Eric Erfanianccca3152017-02-22 16:32:36 -0800168 }
169 call.addListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800170 // start processing lookups right away.
171 if (!call.isConferenceCall()) {
172 startContactInfoSearch(call, true, call.getState() == DialerCall.State.INCOMING);
173 } else {
174 updateContactEntry(null, true);
175 }
176 }
177
178 onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
179 }
180
181 @Override
182 public void onInCallScreenReady() {
wangqi385a5a12017-09-28 10:44:54 -0700183 LogUtil.i("CallCardPresenter.onInCallScreenReady", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800184 Assert.checkState(!isInCallScreenReady);
linyuh183cb712017-12-27 17:02:37 -0800185 if (contactsPreferences != null) {
186 contactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
Eric Erfanianccca3152017-02-22 16:32:36 -0800187 }
188
Eric Erfanianccca3152017-02-22 16:32:36 -0800189 // Contact search may have completed before ui is ready.
linyuh183cb712017-12-27 17:02:37 -0800190 if (primaryContactInfo != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800191 updatePrimaryDisplayInfo();
192 }
193
194 // Register for call state changes last
195 InCallPresenter.getInstance().addListener(this);
196 InCallPresenter.getInstance().addIncomingCallListener(this);
197 InCallPresenter.getInstance().addDetailsListener(this);
198 InCallPresenter.getInstance().addInCallEventListener(this);
199 isInCallScreenReady = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700200
Eric Erfaniand8046e52017-04-06 09:41:50 -0700201 // Log location impressions
linyuh183cb712017-12-27 17:02:37 -0800202 if (isOutgoingEmergencyCall(primary)) {
203 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_NEW_EMERGENCY_CALL);
204 } else if (isIncomingEmergencyCall(primary) || isIncomingEmergencyCall(secondary)) {
205 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_CALLBACK);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700206 }
207
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700208 // Showing the location may have been skipped if the UI wasn't ready during previous layout.
209 if (shouldShowLocation()) {
linyuh183cb712017-12-27 17:02:37 -0800210 inCallScreen.showLocationUi(getLocationFragment());
Eric Erfaniand8046e52017-04-06 09:41:50 -0700211
212 // Log location impressions
213 if (!hasLocationPermission()) {
linyuh183cb712017-12-27 17:02:37 -0800214 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_NO_LOCATION_PERMISSION);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700215 } else if (isBatteryTooLowForEmergencyLocation()) {
linyuh183cb712017-12-27 17:02:37 -0800216 Logger.get(context)
Eric Erfaniand8046e52017-04-06 09:41:50 -0700217 .logImpression(DialerImpression.Type.EMERGENCY_BATTERY_TOO_LOW_TO_GET_LOCATION);
linyuh183cb712017-12-27 17:02:37 -0800218 } else if (!callLocation.canGetLocation(context)) {
219 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_CANT_GET_LOCATION);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700220 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700221 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800222 }
223
224 @Override
225 public void onInCallScreenUnready() {
wangqi385a5a12017-09-28 10:44:54 -0700226 LogUtil.i("CallCardPresenter.onInCallScreenUnready", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800227 Assert.checkState(isInCallScreenReady);
228
Eric Erfanianccca3152017-02-22 16:32:36 -0800229 // stop getting call state changes
230 InCallPresenter.getInstance().removeListener(this);
231 InCallPresenter.getInstance().removeIncomingCallListener(this);
232 InCallPresenter.getInstance().removeDetailsListener(this);
233 InCallPresenter.getInstance().removeInCallEventListener(this);
linyuh183cb712017-12-27 17:02:37 -0800234 if (primary != null) {
235 primary.removeListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800236 }
237
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700238 callLocation.close();
239
linyuh183cb712017-12-27 17:02:37 -0800240 primary = null;
241 primaryContactInfo = null;
242 secondaryContactInfo = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800243 isInCallScreenReady = false;
244 }
245
246 @Override
247 public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
248 // same logic should happen as with onStateChange()
249 onStateChange(oldState, newState, CallList.getInstance());
250 }
251
252 @Override
253 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700254 Trace.beginSection("CallCardPresenter.onStateChange");
255 LogUtil.v("CallCardPresenter.onStateChange", "oldState: %s, newState: %s", oldState, newState);
linyuh183cb712017-12-27 17:02:37 -0800256 if (inCallScreen == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700257 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800258 return;
259 }
260
261 DialerCall primary = null;
262 DialerCall secondary = null;
263
264 if (newState == InCallState.INCOMING) {
265 primary = callList.getIncomingCall();
266 } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
267 primary = callList.getOutgoingCall();
268 if (primary == null) {
269 primary = callList.getPendingOutgoingCall();
270 }
271
272 // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
273 // highest priority call to display as the secondary call.
274 secondary = getCallToDisplay(callList, null, true);
275 } else if (newState == InCallState.INCALL) {
276 primary = getCallToDisplay(callList, null, false);
277 secondary = getCallToDisplay(callList, primary, true);
278 }
279
280 LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
281 LogUtil.v("CallCardPresenter.onStateChange", "secondary call: " + secondary);
wangqidd5f1a52018-01-23 18:01:00 -0800282 String primaryNumber = null;
283 String secondaryNumber = null;
284 if (primary != null) {
285 primaryNumber = primary.getNumber();
286 }
287 if (secondary != null) {
288 secondaryNumber = secondary.getNumber();
289 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800290
291 final boolean primaryChanged =
linyuh183cb712017-12-27 17:02:37 -0800292 !(DialerCall.areSame(this.primary, primary)
wangqidd5f1a52018-01-23 18:01:00 -0800293 && TextUtils.equals(this.primaryNumber, primaryNumber));
Eric Erfanianccca3152017-02-22 16:32:36 -0800294 final boolean secondaryChanged =
linyuh183cb712017-12-27 17:02:37 -0800295 !(DialerCall.areSame(this.secondary, secondary)
wangqidd5f1a52018-01-23 18:01:00 -0800296 && TextUtils.equals(this.secondaryNumber, secondaryNumber));
Eric Erfanianccca3152017-02-22 16:32:36 -0800297
linyuh183cb712017-12-27 17:02:37 -0800298 this.secondary = secondary;
wangqidd5f1a52018-01-23 18:01:00 -0800299 this.secondaryNumber = secondaryNumber;
linyuh183cb712017-12-27 17:02:37 -0800300 DialerCall previousPrimary = this.primary;
301 this.primary = primary;
wangqidd5f1a52018-01-23 18:01:00 -0800302 this.primaryNumber = primaryNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800303
linyuh183cb712017-12-27 17:02:37 -0800304 if (this.primary != null) {
305 InCallPresenter.getInstance().onForegroundCallChanged(this.primary);
306 inCallScreen.updateInCallScreenColors();
Eric Erfanianccca3152017-02-22 16:32:36 -0800307 }
308
309 if (primaryChanged && shouldShowNoteSentToast(primary)) {
linyuh183cb712017-12-27 17:02:37 -0800310 inCallScreen.showNoteSentToast();
Eric Erfanianccca3152017-02-22 16:32:36 -0800311 }
312
313 // Refresh primary call information if either:
314 // 1. Primary call changed.
315 // 2. The call's ability to manage conference has changed.
316 if (shouldRefreshPrimaryInfo(primaryChanged)) {
317 // primary call has changed
318 if (previousPrimary != null) {
319 previousPrimary.removeListener(this);
320 }
linyuh183cb712017-12-27 17:02:37 -0800321 this.primary.addListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800322
linyuh183cb712017-12-27 17:02:37 -0800323 primaryContactInfo =
Eric Erfanianccca3152017-02-22 16:32:36 -0800324 ContactInfoCache.buildCacheEntryFromCall(
linyuh183cb712017-12-27 17:02:37 -0800325 context, this.primary, this.primary.getState() == DialerCall.State.INCOMING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800326 updatePrimaryDisplayInfo();
linyuh183cb712017-12-27 17:02:37 -0800327 maybeStartSearch(this.primary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800328 }
329
linyuh183cb712017-12-27 17:02:37 -0800330 if (previousPrimary != null && this.primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800331 previousPrimary.removeListener(this);
332 }
333
wangqic8cf79e2017-10-17 09:21:00 -0700334 if (secondaryChanged) {
linyuh183cb712017-12-27 17:02:37 -0800335 if (this.secondary == null) {
wangqic8cf79e2017-10-17 09:21:00 -0700336 // Secondary call may have ended. Update the ui.
linyuh183cb712017-12-27 17:02:37 -0800337 secondaryContactInfo = null;
wangqic8cf79e2017-10-17 09:21:00 -0700338 updateSecondaryDisplayInfo();
339 } else {
340 // secondary call has changed
linyuh183cb712017-12-27 17:02:37 -0800341 secondaryContactInfo =
wangqic8cf79e2017-10-17 09:21:00 -0700342 ContactInfoCache.buildCacheEntryFromCall(
linyuh183cb712017-12-27 17:02:37 -0800343 context, this.secondary, this.secondary.getState() == DialerCall.State.INCOMING);
wangqic8cf79e2017-10-17 09:21:00 -0700344 updateSecondaryDisplayInfo();
linyuh183cb712017-12-27 17:02:37 -0800345 maybeStartSearch(this.secondary, false);
wangqic8cf79e2017-10-17 09:21:00 -0700346 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800347 }
348
349 // Set the call state
350 int callState = DialerCall.State.IDLE;
linyuh183cb712017-12-27 17:02:37 -0800351 if (this.primary != null) {
352 callState = this.primary.getState();
Eric Erfanianccca3152017-02-22 16:32:36 -0800353 updatePrimaryCallState();
354 } else {
355 getUi().setCallState(PrimaryCallState.createEmptyPrimaryCallState());
356 }
357
358 maybeShowManageConferenceCallButton();
359
360 // Hide the end call button instantly if we're receiving an incoming call.
361 getUi()
362 .setEndCallButtonEnabled(
linyuh183cb712017-12-27 17:02:37 -0800363 shouldShowEndCallButton(this.primary, callState),
Eric Erfanianccca3152017-02-22 16:32:36 -0800364 callState != DialerCall.State.INCOMING /* animate */);
365
366 maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700367 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800368 }
369
370 @Override
371 public void onDetailsChanged(DialerCall call, Details details) {
372 updatePrimaryCallState();
373
374 if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
375 != details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
376 maybeShowManageConferenceCallButton();
377 }
378 }
379
380 @Override
381 public void onDialerCallDisconnect() {}
382
383 @Override
384 public void onDialerCallUpdate() {
385 // No-op; specific call updates handled elsewhere.
386 }
387
388 @Override
389 public void onWiFiToLteHandover() {}
390
391 @Override
392 public void onHandoverToWifiFailure() {}
393
Eric Erfanianc857f902017-05-15 14:05:33 -0700394 @Override
395 public void onInternationalCallOnWifi() {}
396
Eric Erfanian2ca43182017-08-31 06:57:16 -0700397 @Override
398 public void onEnrichedCallSessionUpdate() {
399 LogUtil.enterBlock("CallCardPresenter.onEnrichedCallSessionUpdate");
400 updatePrimaryDisplayInfo();
401 }
402
Eric Erfanianccca3152017-02-22 16:32:36 -0800403 /** Handles a change to the child number by refreshing the primary call info. */
404 @Override
405 public void onDialerCallChildNumberChange() {
406 LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
407
linyuh183cb712017-12-27 17:02:37 -0800408 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800409 return;
410 }
411 updatePrimaryDisplayInfo();
412 }
413
414 /** Handles a change to the last forwarding number by refreshing the primary call info. */
415 @Override
416 public void onDialerCallLastForwardedNumberChange() {
417 LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
418
linyuh183cb712017-12-27 17:02:37 -0800419 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800420 return;
421 }
422 updatePrimaryDisplayInfo();
423 updatePrimaryCallState();
424 }
425
426 @Override
427 public void onDialerCallUpgradeToVideo() {}
428
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700429 /** Handles a change to the session modification state for a call. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800430 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700431 public void onDialerCallSessionModificationStateChange() {
432 LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
Eric Erfanianccca3152017-02-22 16:32:36 -0800433
linyuh183cb712017-12-27 17:02:37 -0800434 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800435 return;
436 }
437 getUi()
438 .setEndCallButtonEnabled(
linyuh183cb712017-12-27 17:02:37 -0800439 primary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700440 != SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800441 true /* shouldAnimate */);
442 updatePrimaryCallState();
443 }
444
Eric Erfanianccca3152017-02-22 16:32:36 -0800445 private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
linyuh183cb712017-12-27 17:02:37 -0800446 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800447 return false;
448 }
449 return primaryChanged
linyuh183cb712017-12-27 17:02:37 -0800450 || inCallScreen.isManageConferenceVisible() != shouldShowManageConference();
Eric Erfanianccca3152017-02-22 16:32:36 -0800451 }
452
453 private void updatePrimaryCallState() {
linyuh183cb712017-12-27 17:02:37 -0800454 if (getUi() != null && primary != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800455 boolean isWorkCall =
linyuh183cb712017-12-27 17:02:37 -0800456 primary.hasProperty(PROPERTY_ENTERPRISE_CALL)
457 || (primaryContactInfo != null
458 && primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
Eric Erfanianccca3152017-02-22 16:32:36 -0800459 boolean isHdAudioCall =
linyuh183cb712017-12-27 17:02:37 -0800460 isPrimaryCallActive() && primary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700461 boolean isAttemptingHdAudioCall =
462 !isHdAudioCall
linyuh183cb712017-12-27 17:02:37 -0800463 && !primary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
464 && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(context);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700465
linyuh183cb712017-12-27 17:02:37 -0800466 boolean isBusiness = primaryContactInfo != null && primaryContactInfo.isBusiness;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700467
Eric Erfanianccca3152017-02-22 16:32:36 -0800468 // Check for video state change and update the visibility of the contact photo. The contact
469 // photo is hidden when the incoming video surface is shown.
470 // The contact photo visibility can also change in setPrimary().
471 boolean shouldShowContactPhoto =
linyuh183cb712017-12-27 17:02:37 -0800472 !VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800473 getUi()
474 .setCallState(
475 new PrimaryCallState(
linyuh183cb712017-12-27 17:02:37 -0800476 primary.getState(),
477 primary.isVideoCall(),
478 primary.getVideoTech().getSessionModificationState(),
479 primary.getDisconnectCause(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800480 getConnectionLabel(),
481 getCallStateIcon(),
482 getGatewayNumber(),
linyuh183cb712017-12-27 17:02:37 -0800483 shouldShowCallSubject(primary) ? primary.getCallSubject() : null,
wangqi97539352017-09-25 11:15:16 -0700484 PhoneNumberHelper.formatNumber(
linyuh183cb712017-12-27 17:02:37 -0800485 primary.getCallbackNumber(), primary.getSimCountryIso()),
486 primary.hasProperty(Details.PROPERTY_WIFI),
487 primary.isConferenceCall()
488 && !primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE),
Eric Erfanianccca3152017-02-22 16:32:36 -0800489 isWorkCall,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700490 isAttemptingHdAudioCall,
Eric Erfanianccca3152017-02-22 16:32:36 -0800491 isHdAudioCall,
wangqif4ba3452018-01-09 11:26:29 -0800492 !TextUtils.isEmpty(primary.getLastForwardedNumber()) || primary.isCallForwarded(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800493 shouldShowContactPhoto,
linyuh183cb712017-12-27 17:02:37 -0800494 primary.getConnectTimeMillis(),
495 primary.isVoiceMailNumber(),
496 primary.isRemotelyHeld(),
Eric Erfanian2ca43182017-08-31 06:57:16 -0700497 isBusiness,
498 supports2ndCallOnHold(),
499 getSwapToSecondaryButtonState(),
linyuh183cb712017-12-27 17:02:37 -0800500 primary.isAssistedDialed(),
erfaniand0f207f2017-10-11 12:23:29 -0700501 null,
linyuh183cb712017-12-27 17:02:37 -0800502 primary.getAssistedDialingExtras()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800503
504 InCallActivity activity =
linyuh183cb712017-12-27 17:02:37 -0800505 (InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
Eric Erfanianccca3152017-02-22 16:32:36 -0800506 if (activity != null) {
507 activity.onPrimaryCallStateChanged();
508 }
509 }
510 }
511
Eric Erfanian2ca43182017-08-31 06:57:16 -0700512 private @ButtonState int getSwapToSecondaryButtonState() {
linyuh183cb712017-12-27 17:02:37 -0800513 if (secondary == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700514 return ButtonState.NOT_SUPPORT;
515 }
linyuh183cb712017-12-27 17:02:37 -0800516 if (primary.getState() == State.ACTIVE) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700517 return ButtonState.ENABLED;
518 }
519 return ButtonState.DISABLED;
520 }
521
Eric Erfanianccca3152017-02-22 16:32:36 -0800522 /** Only show the conference call button if we can manage the conference. */
523 private void maybeShowManageConferenceCallButton() {
524 getUi().showManageConferenceCallButton(shouldShowManageConference());
525 }
526
527 /**
528 * Determines if the manage conference button should be visible, based on the current primary
529 * call.
530 *
531 * @return {@code True} if the manage conference button should be visible.
532 */
533 private boolean shouldShowManageConference() {
linyuh183cb712017-12-27 17:02:37 -0800534 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800535 return false;
536 }
537
linyuh183cb712017-12-27 17:02:37 -0800538 return primary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) && !isFullscreen;
Eric Erfanianccca3152017-02-22 16:32:36 -0800539 }
540
Eric Erfanian2ca43182017-08-31 06:57:16 -0700541 private boolean supports2ndCallOnHold() {
542 DialerCall firstCall = CallList.getInstance().getActiveOrBackgroundCall();
543 DialerCall incomingCall = CallList.getInstance().getIncomingCall();
544 if (firstCall != null && incomingCall != null && firstCall != incomingCall) {
545 return incomingCall.can(Details.CAPABILITY_HOLD);
546 }
547 return true;
548 }
549
Eric Erfanianccca3152017-02-22 16:32:36 -0800550 @Override
551 public void onCallStateButtonClicked() {
linyuh183cb712017-12-27 17:02:37 -0800552 Intent broadcastIntent = Bindings.get(context).getCallStateButtonBroadcastIntent(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800553 if (broadcastIntent != null) {
554 LogUtil.v(
555 "CallCardPresenter.onCallStateButtonClicked",
556 "sending call state button broadcast: " + broadcastIntent);
linyuh183cb712017-12-27 17:02:37 -0800557 context.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
Eric Erfanianccca3152017-02-22 16:32:36 -0800558 }
559 }
560
561 @Override
562 public void onManageConferenceClicked() {
563 InCallActivity activity =
linyuh183cb712017-12-27 17:02:37 -0800564 (InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
Eric Erfanianccca3152017-02-22 16:32:36 -0800565 activity.showConferenceFragment(true);
566 }
567
568 @Override
569 public void onShrinkAnimationComplete() {
570 InCallPresenter.getInstance().onShrinkAnimationComplete();
571 }
572
Eric Erfanianccca3152017-02-22 16:32:36 -0800573 private void maybeStartSearch(DialerCall call, boolean isPrimary) {
574 // no need to start search for conference calls which show generic info.
575 if (call != null && !call.isConferenceCall()) {
576 startContactInfoSearch(call, isPrimary, call.getState() == DialerCall.State.INCOMING);
577 }
578 }
579
Eric Erfanianccca3152017-02-22 16:32:36 -0800580 /** Starts a query for more contact data for the save primary and secondary calls. */
581 private void startContactInfoSearch(
582 final DialerCall call, final boolean isPrimary, boolean isIncoming) {
linyuh183cb712017-12-27 17:02:37 -0800583 final ContactInfoCache cache = ContactInfoCache.getInstance(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800584
585 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
586 }
587
588 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
589 final boolean entryMatchesExistingCall =
linyuh183cb712017-12-27 17:02:37 -0800590 (isPrimary && primary != null && TextUtils.equals(callId, primary.getId()))
591 || (!isPrimary && secondary != null && TextUtils.equals(callId, secondary.getId()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800592 if (entryMatchesExistingCall) {
593 updateContactEntry(entry, isPrimary);
594 } else {
595 LogUtil.e(
596 "CallCardPresenter.onContactInfoComplete",
597 "dropping stale contact lookup info for " + callId);
598 }
599
600 final DialerCall call = CallList.getInstance().getCallById(callId);
601 if (call != null) {
602 call.getLogState().contactLookupResult = entry.contactLookupResult;
603 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700604 if (entry.lookupUri != null) {
linyuh183cb712017-12-27 17:02:37 -0800605 CallerInfoUtils.sendViewNotification(context, entry.lookupUri);
Eric Erfanianccca3152017-02-22 16:32:36 -0800606 }
607 }
608
609 private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
610 if (getUi() == null) {
611 return;
612 }
613
614 if (entry.photo != null) {
linyuh183cb712017-12-27 17:02:37 -0800615 if (primary != null && callId.equals(primary.getId())) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800616 updateContactEntry(entry, true /* isPrimary */);
linyuh183cb712017-12-27 17:02:37 -0800617 } else if (secondary != null && callId.equals(secondary.getId())) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800618 updateContactEntry(entry, false /* isPrimary */);
619 }
620 }
621 }
622
623 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
624 if (isPrimary) {
linyuh183cb712017-12-27 17:02:37 -0800625 primaryContactInfo = entry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800626 updatePrimaryDisplayInfo();
627 } else {
linyuh183cb712017-12-27 17:02:37 -0800628 secondaryContactInfo = entry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800629 updateSecondaryDisplayInfo();
630 }
631 }
632
633 /**
634 * Get the highest priority call to display. Goes through the calls and chooses which to return
635 * based on priority of which type of call to display to the user. Callers can use the "ignore"
636 * feature to get the second best call by passing a previously found primary call as ignore.
637 *
638 * @param ignore A call to ignore if found.
639 */
640 private DialerCall getCallToDisplay(
641 CallList callList, DialerCall ignore, boolean skipDisconnected) {
642 // Active calls come second. An active call always gets precedent.
643 DialerCall retval = callList.getActiveCall();
644 if (retval != null && retval != ignore) {
645 return retval;
646 }
647
648 // Sometimes there is intemediate state that two calls are in active even one is about
649 // to be on hold.
650 retval = callList.getSecondActiveCall();
651 if (retval != null && retval != ignore) {
652 return retval;
653 }
654
655 // Disconnected calls get primary position if there are no active calls
656 // to let user know quickly what call has disconnected. Disconnected
657 // calls are very short lived.
658 if (!skipDisconnected) {
659 retval = callList.getDisconnectingCall();
660 if (retval != null && retval != ignore) {
661 return retval;
662 }
663 retval = callList.getDisconnectedCall();
664 if (retval != null && retval != ignore) {
665 return retval;
666 }
667 }
668
669 // Then we go to background call (calls on hold)
670 retval = callList.getBackgroundCall();
671 if (retval != null && retval != ignore) {
672 return retval;
673 }
674
675 // Lastly, we go to a second background call.
676 retval = callList.getSecondBackgroundCall();
677
678 return retval;
679 }
680
681 private void updatePrimaryDisplayInfo() {
linyuh183cb712017-12-27 17:02:37 -0800682 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800683 // TODO: May also occur if search result comes back after ui is destroyed. Look into
684 // removing that case completely.
685 LogUtil.v(
686 "CallCardPresenter.updatePrimaryDisplayInfo",
687 "updatePrimaryDisplayInfo called but ui is null!");
688 return;
689 }
690
linyuh183cb712017-12-27 17:02:37 -0800691 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800692 // Clear the primary display info.
linyuh183cb712017-12-27 17:02:37 -0800693 inCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
Eric Erfanianccca3152017-02-22 16:32:36 -0800694 return;
695 }
696
697 // Hide the contact photo if we are in a video call and the incoming video surface is
698 // showing.
699 boolean showContactPhoto =
linyuh183cb712017-12-27 17:02:37 -0800700 !VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800701
702 // DialerCall placed through a work phone account.
linyuh183cb712017-12-27 17:02:37 -0800703 boolean hasWorkCallProperty = primary.hasProperty(PROPERTY_ENTERPRISE_CALL);
Eric Erfanianccca3152017-02-22 16:32:36 -0800704
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700705 MultimediaData multimediaData = null;
linyuh183cb712017-12-27 17:02:37 -0800706 if (primary.getEnrichedCallSession() != null) {
707 multimediaData = primary.getEnrichedCallSession().getMultimediaData();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700708 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800709
linyuh183cb712017-12-27 17:02:37 -0800710 if (primary.isConferenceCall()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800711 LogUtil.v(
712 "CallCardPresenter.updatePrimaryDisplayInfo",
713 "update primary display info for conference call.");
714
linyuh183cb712017-12-27 17:02:37 -0800715 inCallScreen.setPrimary(
Eric Erfanianccca3152017-02-22 16:32:36 -0800716 new PrimaryInfo(
717 null /* number */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700718 CallerInfoUtils.getConferenceString(
linyuh183cb712017-12-27 17:02:37 -0800719 context, primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800720 false /* nameIsNumber */,
721 null /* location */,
722 null /* label */,
Eric Erfanian83b20212017-05-31 08:53:10 -0700723 null /* photo */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800724 ContactPhotoType.DEFAULT_PLACEHOLDER,
725 false /* isSipCall */,
726 showContactPhoto,
727 hasWorkCallProperty,
728 false /* isSpam */,
wangqiae6c8ec2017-09-28 17:39:40 -0700729 false /* isLocalContact */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800730 false /* answeringDisconnectsOngoingCall */,
731 shouldShowLocation(),
732 null /* contactInfoLookupKey */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700733 null /* enrichedCallMultimediaData */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700734 true /* showInCallButtonGrid */,
linyuh183cb712017-12-27 17:02:37 -0800735 primary.getNumberPresentation()));
736 } else if (primaryContactInfo != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800737 LogUtil.v(
738 "CallCardPresenter.updatePrimaryDisplayInfo",
linyuh183cb712017-12-27 17:02:37 -0800739 "update primary display info for " + primaryContactInfo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800740
linyuh183cb712017-12-27 17:02:37 -0800741 String name = getNameForCall(primaryContactInfo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800742 String number;
743
linyuh183cb712017-12-27 17:02:37 -0800744 boolean isChildNumberShown = !TextUtils.isEmpty(primary.getChildNumber());
745 boolean isForwardedNumberShown = !TextUtils.isEmpty(primary.getLastForwardedNumber());
746 boolean isCallSubjectShown = shouldShowCallSubject(primary);
Eric Erfanianccca3152017-02-22 16:32:36 -0800747
748 if (isCallSubjectShown) {
749 number = null;
750 } else if (isChildNumberShown) {
linyuh183cb712017-12-27 17:02:37 -0800751 number = context.getString(R.string.child_number, primary.getChildNumber());
Eric Erfanianccca3152017-02-22 16:32:36 -0800752 } else if (isForwardedNumberShown) {
753 // Use last forwarded number instead of second line, if present.
linyuh183cb712017-12-27 17:02:37 -0800754 number = primary.getLastForwardedNumber();
Eric Erfanianccca3152017-02-22 16:32:36 -0800755 } else {
linyuh183cb712017-12-27 17:02:37 -0800756 number = primaryContactInfo.number;
Eric Erfanianccca3152017-02-22 16:32:36 -0800757 }
758
linyuh183cb712017-12-27 17:02:37 -0800759 boolean nameIsNumber = name != null && name.equals(primaryContactInfo.number);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700760
Eric Erfanianccca3152017-02-22 16:32:36 -0800761 // DialerCall with caller that is a work contact.
linyuh183cb712017-12-27 17:02:37 -0800762 boolean isWorkContact = (primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
763 inCallScreen.setPrimary(
Eric Erfanianccca3152017-02-22 16:32:36 -0800764 new PrimaryInfo(
765 number,
linyuh183cb712017-12-27 17:02:37 -0800766 primary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800767 nameIsNumber,
linyuh183cb712017-12-27 17:02:37 -0800768 shouldShowLocationAsLabel(nameIsNumber, primaryContactInfo.shouldShowLocation)
769 ? primaryContactInfo.location
Eric Erfaniand8046e52017-04-06 09:41:50 -0700770 : null,
linyuh183cb712017-12-27 17:02:37 -0800771 isChildNumberShown || isCallSubjectShown ? null : primaryContactInfo.label,
772 primaryContactInfo.photo,
773 primaryContactInfo.photoType,
774 primaryContactInfo.isSipCall,
Eric Erfanianccca3152017-02-22 16:32:36 -0800775 showContactPhoto,
776 hasWorkCallProperty || isWorkContact,
linyuh183cb712017-12-27 17:02:37 -0800777 primary.isSpam(),
778 primaryContactInfo.isLocalContact(),
779 primary.answeringDisconnectsForegroundVideoCall(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800780 shouldShowLocation(),
linyuh183cb712017-12-27 17:02:37 -0800781 primaryContactInfo.lookupKey,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700782 multimediaData,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700783 true /* showInCallButtonGrid */,
linyuh183cb712017-12-27 17:02:37 -0800784 primary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800785 } else {
786 // Clear the primary display info.
linyuh183cb712017-12-27 17:02:37 -0800787 inCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
Eric Erfanianccca3152017-02-22 16:32:36 -0800788 }
789
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700790 if (isInCallScreenReady) {
linyuh183cb712017-12-27 17:02:37 -0800791 inCallScreen.showLocationUi(getLocationFragment());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700792 } else {
793 LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
794 }
795 }
796
Eric Erfaniand8046e52017-04-06 09:41:50 -0700797 private static boolean shouldShowLocationAsLabel(
798 boolean nameIsNumber, boolean shouldShowLocation) {
799 if (nameIsNumber) {
800 return true;
801 }
802 if (shouldShowLocation) {
803 return true;
804 }
805 return false;
806 }
807
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700808 private Fragment getLocationFragment() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700809 if (!shouldShowLocation()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700810 return null;
811 }
812 LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
linyuh183cb712017-12-27 17:02:37 -0800813 return callLocation.getLocationFragment(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800814 }
815
816 private boolean shouldShowLocation() {
linyuh183cb712017-12-27 17:02:37 -0800817 if (!ConfigProviderBindings.get(context)
wangqi1d62ab22017-11-29 14:29:31 -0800818 .getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
819 LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
820 return false;
821 }
822 if (!isPotentialEmergencyCall()) {
823 LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
824 return false;
825 }
826 if (!hasLocationPermission()) {
827 LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
828 return false;
829 }
830 if (isBatteryTooLowForEmergencyLocation()) {
831 LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
832 return false;
833 }
linyuh183cb712017-12-27 17:02:37 -0800834 if (ActivityCompat.isInMultiWindowMode(inCallScreen.getInCallScreenFragment().getActivity())) {
wangqi1d62ab22017-11-29 14:29:31 -0800835 LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
836 return false;
837 }
linyuh183cb712017-12-27 17:02:37 -0800838 if (primary.isVideoCall()) {
wangqi1d62ab22017-11-29 14:29:31 -0800839 LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
840 return false;
841 }
linyuh183cb712017-12-27 17:02:37 -0800842 if (!callLocation.canGetLocation(context)) {
wangqi1d62ab22017-11-29 14:29:31 -0800843 LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
844 return false;
845 }
846 return true;
847 }
848
849 private boolean isPotentialEmergencyCall() {
linyuh183cb712017-12-27 17:02:37 -0800850 if (isOutgoingEmergencyCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800851 LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
852 return true;
linyuh183cb712017-12-27 17:02:37 -0800853 } else if (isIncomingEmergencyCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800854 LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
855 return true;
linyuh183cb712017-12-27 17:02:37 -0800856 } else if (isIncomingEmergencyCall(secondary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800857 LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
858 return true;
859 }
860 return false;
861 }
862
863 private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
864 return call != null && !call.isIncoming() && call.isEmergencyCall();
865 }
866
867 private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
868 return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
869 }
870
871 private boolean hasLocationPermission() {
linyuh183cb712017-12-27 17:02:37 -0800872 return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
Eric Erfanianccca3152017-02-22 16:32:36 -0800873 == PackageManager.PERMISSION_GRANTED;
874 }
875
876 private boolean isBatteryTooLowForEmergencyLocation() {
877 Intent batteryStatus =
linyuh183cb712017-12-27 17:02:37 -0800878 context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Eric Erfanianccca3152017-02-22 16:32:36 -0800879 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
880 if (status == BatteryManager.BATTERY_STATUS_CHARGING
881 || status == BatteryManager.BATTERY_STATUS_FULL) {
882 // Plugged in or full battery
883 return false;
884 }
885 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
886 int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
887 float batteryPercent = (100f * level) / scale;
888 long threshold =
linyuh183cb712017-12-27 17:02:37 -0800889 ConfigProviderBindings.get(context)
Eric Erfanianccca3152017-02-22 16:32:36 -0800890 .getLong(
891 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
892 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
893 LogUtil.i(
894 "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
895 "percent charged: " + batteryPercent + ", min required charge: " + threshold);
896 return batteryPercent < threshold;
897 }
898
899 private void updateSecondaryDisplayInfo() {
linyuh183cb712017-12-27 17:02:37 -0800900 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800901 return;
902 }
903
linyuh183cb712017-12-27 17:02:37 -0800904 if (secondary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800905 // Clear the secondary display info.
linyuh183cb712017-12-27 17:02:37 -0800906 inCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(isFullscreen));
Eric Erfanianccca3152017-02-22 16:32:36 -0800907 return;
908 }
909
linyuh183cb712017-12-27 17:02:37 -0800910 if (secondary.isMergeInProcess()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700911 LogUtil.i(
912 "CallCardPresenter.updateSecondaryDisplayInfo",
913 "secondary call is merge in process, clearing info");
linyuh183cb712017-12-27 17:02:37 -0800914 inCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(isFullscreen));
Eric Erfanian2ca43182017-08-31 06:57:16 -0700915 return;
916 }
917
linyuh183cb712017-12-27 17:02:37 -0800918 if (secondary.isConferenceCall()) {
919 inCallScreen.setSecondary(
Eric Erfanianccca3152017-02-22 16:32:36 -0800920 new SecondaryInfo(
921 true /* show */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700922 CallerInfoUtils.getConferenceString(
linyuh183cb712017-12-27 17:02:37 -0800923 context, secondary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800924 false /* nameIsNumber */,
925 null /* label */,
linyuh183cb712017-12-27 17:02:37 -0800926 secondary.getCallProviderLabel(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800927 true /* isConference */,
linyuh183cb712017-12-27 17:02:37 -0800928 secondary.isVideoCall(),
929 isFullscreen));
930 } else if (secondaryContactInfo != null) {
931 LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + secondaryContactInfo);
932 String name = getNameForCall(secondaryContactInfo);
933 boolean nameIsNumber = name != null && name.equals(secondaryContactInfo.number);
934 inCallScreen.setSecondary(
Eric Erfanianccca3152017-02-22 16:32:36 -0800935 new SecondaryInfo(
936 true /* show */,
linyuh183cb712017-12-27 17:02:37 -0800937 secondary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800938 nameIsNumber,
linyuh183cb712017-12-27 17:02:37 -0800939 secondaryContactInfo.label,
940 secondary.getCallProviderLabel(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800941 false /* isConference */,
linyuh183cb712017-12-27 17:02:37 -0800942 secondary.isVideoCall(),
943 isFullscreen));
Eric Erfanianccca3152017-02-22 16:32:36 -0800944 } else {
945 // Clear the secondary display info.
linyuh183cb712017-12-27 17:02:37 -0800946 inCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(isFullscreen));
Eric Erfanianccca3152017-02-22 16:32:36 -0800947 }
948 }
949
950 /** Returns the gateway number for any existing outgoing call. */
951 private String getGatewayNumber() {
952 if (hasOutgoingGatewayCall()) {
linyuh183cb712017-12-27 17:02:37 -0800953 return DialerCall.getNumberFromHandle(primary.getGatewayInfo().getGatewayAddress());
Eric Erfanianccca3152017-02-22 16:32:36 -0800954 }
955 return null;
956 }
957
958 /**
959 * Returns the label (line of text above the number/name) for any given call. For example,
960 * "calling via [Account/Google Voice]" for outgoing calls.
961 */
962 private String getConnectionLabel() {
linyuh183cb712017-12-27 17:02:37 -0800963 if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800964 != PackageManager.PERMISSION_GRANTED) {
965 return null;
966 }
linyuh183cb712017-12-27 17:02:37 -0800967 StatusHints statusHints = primary.getStatusHints();
Eric Erfanianccca3152017-02-22 16:32:36 -0800968 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
969 return statusHints.getLabel().toString();
970 }
971
972 if (hasOutgoingGatewayCall() && getUi() != null) {
973 // Return the label for the gateway app on outgoing calls.
linyuh183cb712017-12-27 17:02:37 -0800974 final PackageManager pm = context.getPackageManager();
Eric Erfanianccca3152017-02-22 16:32:36 -0800975 try {
976 ApplicationInfo info =
linyuh183cb712017-12-27 17:02:37 -0800977 pm.getApplicationInfo(primary.getGatewayInfo().getGatewayProviderPackageName(), 0);
Eric Erfanianccca3152017-02-22 16:32:36 -0800978 return pm.getApplicationLabel(info).toString();
979 } catch (PackageManager.NameNotFoundException e) {
980 LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
981 return null;
982 }
983 }
linyuh183cb712017-12-27 17:02:37 -0800984 return primary.getCallProviderLabel();
Eric Erfanianccca3152017-02-22 16:32:36 -0800985 }
986
987 private Drawable getCallStateIcon() {
988 // Return connection icon if one exists.
linyuh183cb712017-12-27 17:02:37 -0800989 StatusHints statusHints = primary.getStatusHints();
Eric Erfanianccca3152017-02-22 16:32:36 -0800990 if (statusHints != null && statusHints.getIcon() != null) {
linyuh183cb712017-12-27 17:02:37 -0800991 Drawable icon = statusHints.getIcon().loadDrawable(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800992 if (icon != null) {
993 return icon;
994 }
995 }
996
997 return null;
998 }
999
1000 private boolean hasOutgoingGatewayCall() {
1001 // We only display the gateway information while STATE_DIALING so return false for any other
1002 // call state.
1003 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
1004 // is also called after a contact search completes (call is not present yet). Split the
1005 // UI update so it can receive independent updates.
linyuh183cb712017-12-27 17:02:37 -08001006 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001007 return false;
1008 }
linyuh183cb712017-12-27 17:02:37 -08001009 return DialerCall.State.isDialing(primary.getState())
1010 && primary.getGatewayInfo() != null
1011 && !primary.getGatewayInfo().isEmpty();
Eric Erfanianccca3152017-02-22 16:32:36 -08001012 }
1013
1014 /** Gets the name to display for the call. */
Eric Erfanian2ca43182017-08-31 06:57:16 -07001015 private String getNameForCall(ContactCacheEntry contactInfo) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001016 String preferredName =
1017 ContactDisplayUtils.getPreferredDisplayName(
linyuh183cb712017-12-27 17:02:37 -08001018 contactInfo.namePrimary, contactInfo.nameAlternative, contactsPreferences);
Eric Erfanianccca3152017-02-22 16:32:36 -08001019 if (TextUtils.isEmpty(preferredName)) {
1020 return contactInfo.number;
1021 }
1022 return preferredName;
1023 }
1024
Eric Erfanianccca3152017-02-22 16:32:36 -08001025 @Override
1026 public void onSecondaryInfoClicked() {
linyuh183cb712017-12-27 17:02:37 -08001027 if (secondary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001028 LogUtil.e(
1029 "CallCardPresenter.onSecondaryInfoClicked",
1030 "secondary info clicked but no secondary call.");
1031 return;
1032 }
1033
linyuh183cb712017-12-27 17:02:37 -08001034 Logger.get(context)
Eric Erfanian2ca43182017-08-31 06:57:16 -07001035 .logCallImpression(
1036 DialerImpression.Type.IN_CALL_SWAP_SECONDARY_BUTTON_PRESSED,
linyuh183cb712017-12-27 17:02:37 -08001037 primary.getUniqueCallId(),
1038 primary.getTimeAddedMs());
Eric Erfanianccca3152017-02-22 16:32:36 -08001039 LogUtil.i(
linyuh183cb712017-12-27 17:02:37 -08001040 "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + secondary);
1041 secondary.unhold();
Eric Erfanianccca3152017-02-22 16:32:36 -08001042 }
1043
1044 @Override
1045 public void onEndCallClicked() {
linyuh183cb712017-12-27 17:02:37 -08001046 LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + primary);
1047 if (primary != null) {
1048 primary.disconnect();
Eric Erfanianccca3152017-02-22 16:32:36 -08001049 }
linyuh183cb712017-12-27 17:02:37 -08001050 PostCall.onDisconnectPressed(context);
Eric Erfanianccca3152017-02-22 16:32:36 -08001051 }
1052
1053 /**
1054 * Handles a change to the fullscreen mode of the in-call UI.
1055 *
1056 * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
1057 */
1058 @Override
1059 public void onFullscreenModeChanged(boolean isFullscreenMode) {
linyuh183cb712017-12-27 17:02:37 -08001060 isFullscreen = isFullscreenMode;
1061 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001062 return;
1063 }
1064 maybeShowManageConferenceCallButton();
1065 }
1066
1067 private boolean isPrimaryCallActive() {
linyuh183cb712017-12-27 17:02:37 -08001068 return primary != null && primary.getState() == DialerCall.State.ACTIVE;
Eric Erfanianccca3152017-02-22 16:32:36 -08001069 }
1070
Eric Erfanianccca3152017-02-22 16:32:36 -08001071 private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
1072 if (primary == null) {
1073 return false;
1074 }
1075 if ((!DialerCall.State.isConnectingOrConnected(callState)
1076 && callState != DialerCall.State.DISCONNECTING
1077 && callState != DialerCall.State.DISCONNECTED)
1078 || callState == DialerCall.State.INCOMING) {
1079 return false;
1080 }
linyuh183cb712017-12-27 17:02:37 -08001081 if (this.primary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001082 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001083 return false;
1084 }
1085 return true;
1086 }
1087
1088 @Override
1089 public void onInCallScreenResumed() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001090 updatePrimaryDisplayInfo();
1091
Eric Erfanianccca3152017-02-22 16:32:36 -08001092 if (shouldSendAccessibilityEvent) {
1093 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1094 }
1095 }
1096
Eric Erfaniand8046e52017-04-06 09:41:50 -07001097 @Override
Eric Erfanian2ca43182017-08-31 06:57:16 -07001098 public void onInCallScreenPaused() {}
Eric Erfaniand8046e52017-04-06 09:41:50 -07001099
Eric Erfanianccca3152017-02-22 16:32:36 -08001100 static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
1101 AccessibilityManager am =
1102 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
1103 if (!am.isEnabled()) {
1104 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
1105 return false;
1106 }
1107 if (inCallScreen == null) {
1108 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
1109 return false;
1110 }
1111 Fragment fragment = inCallScreen.getInCallScreenFragment();
1112 if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
1113 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
1114 return false;
1115 }
1116
1117 DisplayManager displayManager =
1118 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1119 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1120 boolean screenIsOn = display.getState() == Display.STATE_ON;
1121 LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
1122 if (!screenIsOn) {
1123 return false;
1124 }
1125
1126 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1127 inCallScreen.dispatchPopulateAccessibilityEvent(event);
1128 View view = inCallScreen.getInCallScreenFragment().getView();
1129 view.getParent().requestSendAccessibilityEvent(view, event);
1130 return true;
1131 }
1132
1133 private void maybeSendAccessibilityEvent(
1134 InCallState oldState, final InCallState newState, boolean primaryChanged) {
1135 shouldSendAccessibilityEvent = false;
linyuh183cb712017-12-27 17:02:37 -08001136 if (context == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001137 return;
1138 }
1139 final AccessibilityManager am =
linyuh183cb712017-12-27 17:02:37 -08001140 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
Eric Erfanianccca3152017-02-22 16:32:36 -08001141 if (!am.isEnabled()) {
1142 return;
1143 }
1144 // Announce the current call if it's new incoming/outgoing call or primary call is changed
1145 // due to switching calls between two ongoing calls (one is on hold).
1146 if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
1147 || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
1148 || primaryChanged) {
1149 LogUtil.i(
1150 "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
1151 shouldSendAccessibilityEvent = true;
1152 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1153 }
1154 }
1155
1156 /**
1157 * Determines whether the call subject should be visible on the UI. For the call subject to be
1158 * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
1159 *
1160 * @param call The call.
1161 * @return {@code true} if the subject should be shown, {@code false} otherwise.
1162 */
1163 private boolean shouldShowCallSubject(DialerCall call) {
1164 if (call == null) {
1165 return false;
1166 }
1167
1168 boolean isIncomingOrWaiting =
linyuh183cb712017-12-27 17:02:37 -08001169 primary.getState() == DialerCall.State.INCOMING
1170 || primary.getState() == DialerCall.State.CALL_WAITING;
Eric Erfanianccca3152017-02-22 16:32:36 -08001171 return isIncomingOrWaiting
1172 && !TextUtils.isEmpty(call.getCallSubject())
1173 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
1174 && call.isCallSubjectSupported();
1175 }
1176
1177 /**
1178 * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
1179 * call with a subject.
1180 *
1181 * @param call The call
1182 * @return {@code true} if the toast should be shown, {@code false} otherwise.
1183 */
1184 private boolean shouldShowNoteSentToast(DialerCall call) {
1185 return call != null
1186 && hasCallSubject(call)
1187 && (call.getState() == DialerCall.State.DIALING
1188 || call.getState() == DialerCall.State.CONNECTING);
1189 }
1190
1191 private InCallScreen getUi() {
linyuh183cb712017-12-27 17:02:37 -08001192 return inCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -08001193 }
1194
1195 public static class ContactLookupCallback implements ContactInfoCacheCallback {
1196
linyuh183cb712017-12-27 17:02:37 -08001197 private final WeakReference<CallCardPresenter> callCardPresenter;
1198 private final boolean isPrimary;
Eric Erfanianccca3152017-02-22 16:32:36 -08001199
1200 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
linyuh183cb712017-12-27 17:02:37 -08001201 this.callCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
1202 this.isPrimary = isPrimary;
Eric Erfanianccca3152017-02-22 16:32:36 -08001203 }
1204
1205 @Override
1206 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
linyuh183cb712017-12-27 17:02:37 -08001207 CallCardPresenter presenter = callCardPresenter.get();
Eric Erfanianccca3152017-02-22 16:32:36 -08001208 if (presenter != null) {
linyuh183cb712017-12-27 17:02:37 -08001209 presenter.onContactInfoComplete(callId, entry, isPrimary);
Eric Erfanianccca3152017-02-22 16:32:36 -08001210 }
1211 }
1212
1213 @Override
1214 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
linyuh183cb712017-12-27 17:02:37 -08001215 CallCardPresenter presenter = callCardPresenter.get();
Eric Erfanianccca3152017-02-22 16:32:36 -08001216 if (presenter != null) {
1217 presenter.onImageLoadComplete(callId, entry);
1218 }
1219 }
1220 }
1221}