blob: 6614e7c746296e0f7af8bb2f49f41423ebd7b9d5 [file] [log] [blame]
Eric Erfanianccca3152017-02-22 16:32:36 -08001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.incallui;
18
19import static com.android.contacts.common.compat.CallCompat.Details.PROPERTY_ENTERPRISE_CALL;
20
21import android.Manifest;
Eric Erfanianccca3152017-02-22 16:32:36 -080022import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.PackageManager;
27import android.graphics.drawable.Drawable;
28import android.hardware.display.DisplayManager;
29import android.os.BatteryManager;
30import android.os.Handler;
Eric Erfanian2ca43182017-08-31 06:57:16 -070031import android.os.Trace;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070032import android.support.annotation.NonNull;
Eric Erfanianccca3152017-02-22 16:32:36 -080033import android.support.annotation.Nullable;
34import android.support.v4.app.Fragment;
35import android.support.v4.content.ContextCompat;
36import android.telecom.Call.Details;
37import android.telecom.StatusHints;
38import android.telecom.TelecomManager;
wangqia4623702018-04-02 14:23:38 -070039import android.text.BidiFormatter;
40import android.text.TextDirectionHeuristics;
Eric Erfanianccca3152017-02-22 16:32:36 -080041import android.text.TextUtils;
42import android.view.Display;
43import android.view.View;
44import android.view.accessibility.AccessibilityEvent;
45import android.view.accessibility.AccessibilityManager;
46import com.android.contacts.common.ContactsUtils;
47import com.android.contacts.common.preference.ContactsPreferences;
48import com.android.contacts.common.util.ContactDisplayUtils;
49import com.android.dialer.common.Assert;
Eric Erfanianccca3152017-02-22 16:32:36 -080050import com.android.dialer.common.LogUtil;
Eric Erfanian2ca43182017-08-31 06:57:16 -070051import com.android.dialer.configprovider.ConfigProviderBindings;
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;
twyen4fde0ac2018-03-22 18:04:23 -070058import com.android.dialer.preferredsim.suggestion.SuggestionProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080059import com.android.incallui.ContactInfoCache.ContactCacheEntry;
60import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
61import com.android.incallui.InCallPresenter.InCallDetailsListener;
62import com.android.incallui.InCallPresenter.InCallEventListener;
63import com.android.incallui.InCallPresenter.InCallState;
64import com.android.incallui.InCallPresenter.InCallStateListener;
65import com.android.incallui.InCallPresenter.IncomingCallListener;
66import com.android.incallui.call.CallList;
67import com.android.incallui.call.DialerCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080068import com.android.incallui.call.DialerCallListener;
wangqibb94ca62018-04-27 14:34:04 -070069import com.android.incallui.call.state.DialerCallState;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070070import com.android.incallui.calllocation.CallLocation;
71import com.android.incallui.calllocation.CallLocationComponent;
Eric Erfanianccca3152017-02-22 16:32:36 -080072import com.android.incallui.incall.protocol.ContactPhotoType;
73import com.android.incallui.incall.protocol.InCallScreen;
74import com.android.incallui.incall.protocol.InCallScreenDelegate;
75import com.android.incallui.incall.protocol.PrimaryCallState;
Eric Erfanian2ca43182017-08-31 06:57:16 -070076import com.android.incallui.incall.protocol.PrimaryCallState.ButtonState;
Eric Erfanianccca3152017-02-22 16:32:36 -080077import com.android.incallui.incall.protocol.PrimaryInfo;
78import com.android.incallui.incall.protocol.SecondaryInfo;
Eric Erfanian90508232017-03-24 09:31:16 -070079import com.android.incallui.videotech.utils.SessionModificationState;
Eric Erfanianccca3152017-02-22 16:32:36 -080080import java.lang.ref.WeakReference;
81
82/**
83 * Controller for the Call Card Fragment. This class listens for changes to InCallState and passes
84 * it along to the fragment.
85 */
86public class CallCardPresenter
87 implements InCallStateListener,
88 IncomingCallListener,
89 InCallDetailsListener,
90 InCallEventListener,
91 InCallScreenDelegate,
Eric Erfanian2ca43182017-08-31 06:57:16 -070092 DialerCallListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080093
94 /**
95 * Amount of time to wait before sending an announcement via the accessibility manager. When the
96 * call state changes to an outgoing or incoming state for the first time, the UI can often be
97 * changing due to call updates or contact lookup. This allows the UI to settle to a stable state
98 * to ensure that the correct information is announced.
99 */
100 private static final long ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS = 500;
101
102 /** Flag to allow the user's current location to be shown during emergency calls. */
103 private static final String CONFIG_ENABLE_EMERGENCY_LOCATION = "config_enable_emergency_location";
104
105 private static final boolean CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT = true;
106
107 /**
108 * Make it possible to not get location during an emergency call if the battery is too low, since
109 * doing so could trigger gps and thus potentially cause the phone to die in the middle of the
110 * call.
111 */
112 private static final String CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION =
113 "min_battery_percent_for_emergency_location";
114
115 private static final long CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT = 10;
116
linyuh183cb712017-12-27 17:02:37 -0800117 private final Context context;
Eric Erfanianccca3152017-02-22 16:32:36 -0800118 private final Handler handler = new Handler();
119
linyuh183cb712017-12-27 17:02:37 -0800120 private DialerCall primary;
wangqidd5f1a52018-01-23 18:01:00 -0800121 private String primaryNumber;
linyuh183cb712017-12-27 17:02:37 -0800122 private DialerCall secondary;
wangqidd5f1a52018-01-23 18:01:00 -0800123 private String secondaryNumber;
linyuh183cb712017-12-27 17:02:37 -0800124 private ContactCacheEntry primaryContactInfo;
125 private ContactCacheEntry secondaryContactInfo;
126 @Nullable private ContactsPreferences contactsPreferences;
127 private boolean isFullscreen = false;
128 private InCallScreen inCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -0800129 private boolean isInCallScreenReady;
130 private boolean shouldSendAccessibilityEvent;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700131
132 @NonNull private final CallLocation callLocation;
Eric Erfanianccca3152017-02-22 16:32:36 -0800133 private final Runnable sendAccessibilityEventRunnable =
134 new Runnable() {
135 @Override
136 public void run() {
linyuh183cb712017-12-27 17:02:37 -0800137 shouldSendAccessibilityEvent = !sendAccessibilityEvent(context, getUi());
Eric Erfanianccca3152017-02-22 16:32:36 -0800138 LogUtil.i(
139 "CallCardPresenter.sendAccessibilityEventRunnable",
140 "still should send: %b",
141 shouldSendAccessibilityEvent);
142 if (!shouldSendAccessibilityEvent) {
143 handler.removeCallbacks(this);
144 }
145 }
146 };
147
148 public CallCardPresenter(Context context) {
wangqi385a5a12017-09-28 10:44:54 -0700149 LogUtil.i("CallCardPresenter.constructor", null);
linyuh183cb712017-12-27 17:02:37 -0800150 this.context = Assert.isNotNull(context).getApplicationContext();
151 callLocation = CallLocationComponent.get(this.context).getCallLocation();
Eric Erfanianccca3152017-02-22 16:32:36 -0800152 }
153
154 private static boolean hasCallSubject(DialerCall call) {
155 return !TextUtils.isEmpty(call.getCallSubject());
156 }
157
158 @Override
159 public void onInCallScreenDelegateInit(InCallScreen inCallScreen) {
160 Assert.isNotNull(inCallScreen);
linyuh183cb712017-12-27 17:02:37 -0800161 this.inCallScreen = inCallScreen;
162 contactsPreferences = ContactsPreferencesFactory.newContactsPreferences(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800163
164 // Call may be null if disconnect happened already.
165 DialerCall call = CallList.getInstance().getFirstCall();
166 if (call != null) {
linyuh183cb712017-12-27 17:02:37 -0800167 primary = call;
168 if (shouldShowNoteSentToast(primary)) {
169 this.inCallScreen.showNoteSentToast();
Eric Erfanianccca3152017-02-22 16:32:36 -0800170 }
171 call.addListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800172 // start processing lookups right away.
173 if (!call.isConferenceCall()) {
wangqibb94ca62018-04-27 14:34:04 -0700174 startContactInfoSearch(call, true, call.getState() == DialerCallState.INCOMING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800175 } else {
176 updateContactEntry(null, true);
177 }
178 }
179
180 onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
181 }
182
183 @Override
184 public void onInCallScreenReady() {
wangqi385a5a12017-09-28 10:44:54 -0700185 LogUtil.i("CallCardPresenter.onInCallScreenReady", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800186 Assert.checkState(!isInCallScreenReady);
linyuh183cb712017-12-27 17:02:37 -0800187 if (contactsPreferences != null) {
188 contactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
Eric Erfanianccca3152017-02-22 16:32:36 -0800189 }
190
Eric Erfanianccca3152017-02-22 16:32:36 -0800191 // Contact search may have completed before ui is ready.
linyuh183cb712017-12-27 17:02:37 -0800192 if (primaryContactInfo != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800193 updatePrimaryDisplayInfo();
194 }
195
196 // Register for call state changes last
197 InCallPresenter.getInstance().addListener(this);
198 InCallPresenter.getInstance().addIncomingCallListener(this);
199 InCallPresenter.getInstance().addDetailsListener(this);
200 InCallPresenter.getInstance().addInCallEventListener(this);
201 isInCallScreenReady = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700202
Eric Erfaniand8046e52017-04-06 09:41:50 -0700203 // Log location impressions
linyuh183cb712017-12-27 17:02:37 -0800204 if (isOutgoingEmergencyCall(primary)) {
205 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_NEW_EMERGENCY_CALL);
206 } else if (isIncomingEmergencyCall(primary) || isIncomingEmergencyCall(secondary)) {
207 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_CALLBACK);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700208 }
209
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700210 // Showing the location may have been skipped if the UI wasn't ready during previous layout.
211 if (shouldShowLocation()) {
linyuh183cb712017-12-27 17:02:37 -0800212 inCallScreen.showLocationUi(getLocationFragment());
Eric Erfaniand8046e52017-04-06 09:41:50 -0700213
214 // Log location impressions
215 if (!hasLocationPermission()) {
linyuh183cb712017-12-27 17:02:37 -0800216 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_NO_LOCATION_PERMISSION);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700217 } else if (isBatteryTooLowForEmergencyLocation()) {
linyuh183cb712017-12-27 17:02:37 -0800218 Logger.get(context)
Eric Erfaniand8046e52017-04-06 09:41:50 -0700219 .logImpression(DialerImpression.Type.EMERGENCY_BATTERY_TOO_LOW_TO_GET_LOCATION);
linyuh183cb712017-12-27 17:02:37 -0800220 } else if (!callLocation.canGetLocation(context)) {
221 Logger.get(context).logImpression(DialerImpression.Type.EMERGENCY_CANT_GET_LOCATION);
Eric Erfaniand8046e52017-04-06 09:41:50 -0700222 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700223 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800224 }
225
226 @Override
227 public void onInCallScreenUnready() {
wangqi385a5a12017-09-28 10:44:54 -0700228 LogUtil.i("CallCardPresenter.onInCallScreenUnready", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800229 Assert.checkState(isInCallScreenReady);
230
Eric Erfanianccca3152017-02-22 16:32:36 -0800231 // stop getting call state changes
232 InCallPresenter.getInstance().removeListener(this);
233 InCallPresenter.getInstance().removeIncomingCallListener(this);
234 InCallPresenter.getInstance().removeDetailsListener(this);
235 InCallPresenter.getInstance().removeInCallEventListener(this);
linyuh183cb712017-12-27 17:02:37 -0800236 if (primary != null) {
237 primary.removeListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800238 }
239
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700240 callLocation.close();
241
linyuh183cb712017-12-27 17:02:37 -0800242 primary = null;
243 primaryContactInfo = null;
244 secondaryContactInfo = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800245 isInCallScreenReady = false;
246 }
247
248 @Override
249 public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
250 // same logic should happen as with onStateChange()
251 onStateChange(oldState, newState, CallList.getInstance());
252 }
253
254 @Override
255 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700256 Trace.beginSection("CallCardPresenter.onStateChange");
257 LogUtil.v("CallCardPresenter.onStateChange", "oldState: %s, newState: %s", oldState, newState);
linyuh183cb712017-12-27 17:02:37 -0800258 if (inCallScreen == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700259 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800260 return;
261 }
262
263 DialerCall primary = null;
264 DialerCall secondary = null;
265
266 if (newState == InCallState.INCOMING) {
267 primary = callList.getIncomingCall();
268 } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
269 primary = callList.getOutgoingCall();
270 if (primary == null) {
271 primary = callList.getPendingOutgoingCall();
272 }
273
274 // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
275 // highest priority call to display as the secondary call.
yuegb9103042018-03-30 12:12:25 -0700276 secondary = InCallPresenter.getCallToDisplay(callList, null, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800277 } else if (newState == InCallState.INCALL) {
yuegb9103042018-03-30 12:12:25 -0700278 primary = InCallPresenter.getCallToDisplay(callList, null, false);
279 secondary = InCallPresenter.getCallToDisplay(callList, primary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800280 }
281
282 LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
283 LogUtil.v("CallCardPresenter.onStateChange", "secondary call: " + secondary);
wangqidd5f1a52018-01-23 18:01:00 -0800284 String primaryNumber = null;
285 String secondaryNumber = null;
286 if (primary != null) {
287 primaryNumber = primary.getNumber();
288 }
289 if (secondary != null) {
290 secondaryNumber = secondary.getNumber();
291 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800292
293 final boolean primaryChanged =
linyuh183cb712017-12-27 17:02:37 -0800294 !(DialerCall.areSame(this.primary, primary)
wangqidd5f1a52018-01-23 18:01:00 -0800295 && TextUtils.equals(this.primaryNumber, primaryNumber));
Eric Erfanianccca3152017-02-22 16:32:36 -0800296 final boolean secondaryChanged =
linyuh183cb712017-12-27 17:02:37 -0800297 !(DialerCall.areSame(this.secondary, secondary)
wangqidd5f1a52018-01-23 18:01:00 -0800298 && TextUtils.equals(this.secondaryNumber, secondaryNumber));
Eric Erfanianccca3152017-02-22 16:32:36 -0800299
linyuh183cb712017-12-27 17:02:37 -0800300 this.secondary = secondary;
wangqidd5f1a52018-01-23 18:01:00 -0800301 this.secondaryNumber = secondaryNumber;
linyuh183cb712017-12-27 17:02:37 -0800302 DialerCall previousPrimary = this.primary;
303 this.primary = primary;
wangqidd5f1a52018-01-23 18:01:00 -0800304 this.primaryNumber = primaryNumber;
Eric Erfanianccca3152017-02-22 16:32:36 -0800305
linyuh183cb712017-12-27 17:02:37 -0800306 if (this.primary != null) {
linyuh183cb712017-12-27 17:02:37 -0800307 inCallScreen.updateInCallScreenColors();
Eric Erfanianccca3152017-02-22 16:32:36 -0800308 }
309
310 if (primaryChanged && shouldShowNoteSentToast(primary)) {
linyuh183cb712017-12-27 17:02:37 -0800311 inCallScreen.showNoteSentToast();
Eric Erfanianccca3152017-02-22 16:32:36 -0800312 }
313
314 // Refresh primary call information if either:
315 // 1. Primary call changed.
316 // 2. The call's ability to manage conference has changed.
317 if (shouldRefreshPrimaryInfo(primaryChanged)) {
318 // primary call has changed
319 if (previousPrimary != null) {
320 previousPrimary.removeListener(this);
321 }
linyuh183cb712017-12-27 17:02:37 -0800322 this.primary.addListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800323
twyena40a9ef2018-05-22 17:35:59 -0700324 primaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(context, this.primary);
Eric Erfanianccca3152017-02-22 16:32:36 -0800325 updatePrimaryDisplayInfo();
linyuh183cb712017-12-27 17:02:37 -0800326 maybeStartSearch(this.primary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800327 }
328
linyuh183cb712017-12-27 17:02:37 -0800329 if (previousPrimary != null && this.primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800330 previousPrimary.removeListener(this);
331 }
332
wangqic8cf79e2017-10-17 09:21:00 -0700333 if (secondaryChanged) {
linyuh183cb712017-12-27 17:02:37 -0800334 if (this.secondary == null) {
wangqic8cf79e2017-10-17 09:21:00 -0700335 // Secondary call may have ended. Update the ui.
linyuh183cb712017-12-27 17:02:37 -0800336 secondaryContactInfo = null;
wangqic8cf79e2017-10-17 09:21:00 -0700337 updateSecondaryDisplayInfo();
338 } else {
339 // secondary call has changed
twyena40a9ef2018-05-22 17:35:59 -0700340 secondaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(context, this.secondary);
wangqic8cf79e2017-10-17 09:21:00 -0700341 updateSecondaryDisplayInfo();
linyuh183cb712017-12-27 17:02:37 -0800342 maybeStartSearch(this.secondary, false);
wangqic8cf79e2017-10-17 09:21:00 -0700343 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800344 }
345
346 // Set the call state
wangqibb94ca62018-04-27 14:34:04 -0700347 int callState = DialerCallState.IDLE;
linyuh183cb712017-12-27 17:02:37 -0800348 if (this.primary != null) {
349 callState = this.primary.getState();
Eric Erfanianccca3152017-02-22 16:32:36 -0800350 updatePrimaryCallState();
351 } else {
Android Dialer31fe9982018-02-26 13:29:09 -0800352 getUi().setCallState(PrimaryCallState.empty());
Eric Erfanianccca3152017-02-22 16:32:36 -0800353 }
354
355 maybeShowManageConferenceCallButton();
356
357 // Hide the end call button instantly if we're receiving an incoming call.
358 getUi()
359 .setEndCallButtonEnabled(
linyuh183cb712017-12-27 17:02:37 -0800360 shouldShowEndCallButton(this.primary, callState),
wangqibb94ca62018-04-27 14:34:04 -0700361 callState != DialerCallState.INCOMING /* animate */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800362
363 maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700364 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800365 }
366
367 @Override
368 public void onDetailsChanged(DialerCall call, Details details) {
369 updatePrimaryCallState();
370
371 if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
372 != details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
373 maybeShowManageConferenceCallButton();
374 }
375 }
376
377 @Override
378 public void onDialerCallDisconnect() {}
379
380 @Override
381 public void onDialerCallUpdate() {
382 // No-op; specific call updates handled elsewhere.
383 }
384
385 @Override
386 public void onWiFiToLteHandover() {}
387
388 @Override
389 public void onHandoverToWifiFailure() {}
390
Eric Erfanianc857f902017-05-15 14:05:33 -0700391 @Override
392 public void onInternationalCallOnWifi() {}
393
Eric Erfanian2ca43182017-08-31 06:57:16 -0700394 @Override
395 public void onEnrichedCallSessionUpdate() {
396 LogUtil.enterBlock("CallCardPresenter.onEnrichedCallSessionUpdate");
397 updatePrimaryDisplayInfo();
398 }
399
Eric Erfanianccca3152017-02-22 16:32:36 -0800400 /** Handles a change to the child number by refreshing the primary call info. */
401 @Override
402 public void onDialerCallChildNumberChange() {
403 LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
404
linyuh183cb712017-12-27 17:02:37 -0800405 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800406 return;
407 }
408 updatePrimaryDisplayInfo();
409 }
410
411 /** Handles a change to the last forwarding number by refreshing the primary call info. */
412 @Override
413 public void onDialerCallLastForwardedNumberChange() {
414 LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
415
linyuh183cb712017-12-27 17:02:37 -0800416 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800417 return;
418 }
419 updatePrimaryDisplayInfo();
420 updatePrimaryCallState();
421 }
422
423 @Override
424 public void onDialerCallUpgradeToVideo() {}
425
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700426 /** Handles a change to the session modification state for a call. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800427 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700428 public void onDialerCallSessionModificationStateChange() {
429 LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
Eric Erfanianccca3152017-02-22 16:32:36 -0800430
linyuh183cb712017-12-27 17:02:37 -0800431 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800432 return;
433 }
434 getUi()
435 .setEndCallButtonEnabled(
linyuh183cb712017-12-27 17:02:37 -0800436 primary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700437 != SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800438 true /* shouldAnimate */);
439 updatePrimaryCallState();
440 }
441
Eric Erfanianccca3152017-02-22 16:32:36 -0800442 private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
linyuh183cb712017-12-27 17:02:37 -0800443 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800444 return false;
445 }
446 return primaryChanged
linyuh183cb712017-12-27 17:02:37 -0800447 || inCallScreen.isManageConferenceVisible() != shouldShowManageConference();
Eric Erfanianccca3152017-02-22 16:32:36 -0800448 }
449
450 private void updatePrimaryCallState() {
linyuh183cb712017-12-27 17:02:37 -0800451 if (getUi() != null && primary != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800452 boolean isWorkCall =
linyuh183cb712017-12-27 17:02:37 -0800453 primary.hasProperty(PROPERTY_ENTERPRISE_CALL)
454 || (primaryContactInfo != null
455 && primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
Eric Erfanianccca3152017-02-22 16:32:36 -0800456 boolean isHdAudioCall =
linyuh183cb712017-12-27 17:02:37 -0800457 isPrimaryCallActive() && primary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700458 boolean isAttemptingHdAudioCall =
459 !isHdAudioCall
linyuh183cb712017-12-27 17:02:37 -0800460 && !primary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
461 && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(context);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700462
linyuh183cb712017-12-27 17:02:37 -0800463 boolean isBusiness = primaryContactInfo != null && primaryContactInfo.isBusiness;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700464
Eric Erfanianccca3152017-02-22 16:32:36 -0800465 // Check for video state change and update the visibility of the contact photo. The contact
466 // photo is hidden when the incoming video surface is shown.
467 // The contact photo visibility can also change in setPrimary().
468 boolean shouldShowContactPhoto =
linyuh183cb712017-12-27 17:02:37 -0800469 !VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800470 getUi()
471 .setCallState(
Android Dialer31fe9982018-02-26 13:29:09 -0800472 PrimaryCallState.builder()
473 .setState(primary.getState())
474 .setIsVideoCall(primary.isVideoCall())
475 .setSessionModificationState(primary.getVideoTech().getSessionModificationState())
476 .setDisconnectCause(primary.getDisconnectCause())
477 .setConnectionLabel(getConnectionLabel())
twyen4fde0ac2018-03-22 18:04:23 -0700478 .setSimSuggestionReason(getSimSuggestionReason())
Android Dialer31fe9982018-02-26 13:29:09 -0800479 .setConnectionIcon(getCallStateIcon())
480 .setGatewayNumber(getGatewayNumber())
481 .setCallSubject(shouldShowCallSubject(primary) ? primary.getCallSubject() : null)
482 .setCallbackNumber(
483 PhoneNumberHelper.formatNumber(
linyuhb06d0092018-03-01 15:05:36 -0800484 context, primary.getCallbackNumber(), primary.getSimCountryIso()))
Android Dialer31fe9982018-02-26 13:29:09 -0800485 .setIsWifi(primary.hasProperty(Details.PROPERTY_WIFI))
486 .setIsConference(
487 primary.isConferenceCall()
488 && !primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE))
489 .setIsWorkCall(isWorkCall)
490 .setIsHdAttempting(isAttemptingHdAudioCall)
491 .setIsHdAudioCall(isHdAudioCall)
492 .setIsForwardedNumber(
493 !TextUtils.isEmpty(primary.getLastForwardedNumber())
494 || primary.isCallForwarded())
495 .setShouldShowContactPhoto(shouldShowContactPhoto)
496 .setConnectTimeMillis(primary.getConnectTimeMillis())
497 .setIsVoiceMailNumber(primary.isVoiceMailNumber())
498 .setIsRemotelyHeld(primary.isRemotelyHeld())
499 .setIsBusinessNumber(isBusiness)
500 .setSupportsCallOnHold(supports2ndCallOnHold())
501 .setSwapToSecondaryButtonState(getSwapToSecondaryButtonState())
502 .setIsAssistedDialed(primary.isAssistedDialed())
503 .setCustomLabel(null)
504 .setAssistedDialingExtras(primary.getAssistedDialingExtras())
505 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800506
507 InCallActivity activity =
linyuh183cb712017-12-27 17:02:37 -0800508 (InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
Eric Erfanianccca3152017-02-22 16:32:36 -0800509 if (activity != null) {
510 activity.onPrimaryCallStateChanged();
511 }
512 }
513 }
514
Eric Erfanian2ca43182017-08-31 06:57:16 -0700515 private @ButtonState int getSwapToSecondaryButtonState() {
linyuh183cb712017-12-27 17:02:37 -0800516 if (secondary == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700517 return ButtonState.NOT_SUPPORT;
518 }
wangqibb94ca62018-04-27 14:34:04 -0700519 if (primary.getState() == DialerCallState.ACTIVE) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700520 return ButtonState.ENABLED;
521 }
522 return ButtonState.DISABLED;
523 }
524
Eric Erfanianccca3152017-02-22 16:32:36 -0800525 /** Only show the conference call button if we can manage the conference. */
526 private void maybeShowManageConferenceCallButton() {
527 getUi().showManageConferenceCallButton(shouldShowManageConference());
528 }
529
530 /**
531 * Determines if the manage conference button should be visible, based on the current primary
532 * call.
533 *
534 * @return {@code True} if the manage conference button should be visible.
535 */
536 private boolean shouldShowManageConference() {
linyuh183cb712017-12-27 17:02:37 -0800537 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800538 return false;
539 }
540
linyuh183cb712017-12-27 17:02:37 -0800541 return primary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) && !isFullscreen;
Eric Erfanianccca3152017-02-22 16:32:36 -0800542 }
543
Eric Erfanian2ca43182017-08-31 06:57:16 -0700544 private boolean supports2ndCallOnHold() {
545 DialerCall firstCall = CallList.getInstance().getActiveOrBackgroundCall();
546 DialerCall incomingCall = CallList.getInstance().getIncomingCall();
547 if (firstCall != null && incomingCall != null && firstCall != incomingCall) {
548 return incomingCall.can(Details.CAPABILITY_HOLD);
549 }
550 return true;
551 }
552
Eric Erfanianccca3152017-02-22 16:32:36 -0800553 @Override
554 public void onCallStateButtonClicked() {
linyuh183cb712017-12-27 17:02:37 -0800555 Intent broadcastIntent = Bindings.get(context).getCallStateButtonBroadcastIntent(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800556 if (broadcastIntent != null) {
557 LogUtil.v(
558 "CallCardPresenter.onCallStateButtonClicked",
559 "sending call state button broadcast: " + broadcastIntent);
linyuh183cb712017-12-27 17:02:37 -0800560 context.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
Eric Erfanianccca3152017-02-22 16:32:36 -0800561 }
562 }
563
564 @Override
565 public void onManageConferenceClicked() {
566 InCallActivity activity =
linyuh183cb712017-12-27 17:02:37 -0800567 (InCallActivity) (inCallScreen.getInCallScreenFragment().getActivity());
Eric Erfanianccca3152017-02-22 16:32:36 -0800568 activity.showConferenceFragment(true);
569 }
570
571 @Override
572 public void onShrinkAnimationComplete() {
573 InCallPresenter.getInstance().onShrinkAnimationComplete();
574 }
575
Eric Erfanianccca3152017-02-22 16:32:36 -0800576 private void maybeStartSearch(DialerCall call, boolean isPrimary) {
577 // no need to start search for conference calls which show generic info.
578 if (call != null && !call.isConferenceCall()) {
wangqibb94ca62018-04-27 14:34:04 -0700579 startContactInfoSearch(call, isPrimary, call.getState() == DialerCallState.INCOMING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800580 }
581 }
582
Eric Erfanianccca3152017-02-22 16:32:36 -0800583 /** Starts a query for more contact data for the save primary and secondary calls. */
584 private void startContactInfoSearch(
585 final DialerCall call, final boolean isPrimary, boolean isIncoming) {
linyuh183cb712017-12-27 17:02:37 -0800586 final ContactInfoCache cache = ContactInfoCache.getInstance(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800587
588 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
589 }
590
591 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
592 final boolean entryMatchesExistingCall =
linyuh183cb712017-12-27 17:02:37 -0800593 (isPrimary && primary != null && TextUtils.equals(callId, primary.getId()))
594 || (!isPrimary && secondary != null && TextUtils.equals(callId, secondary.getId()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800595 if (entryMatchesExistingCall) {
596 updateContactEntry(entry, isPrimary);
597 } else {
598 LogUtil.e(
599 "CallCardPresenter.onContactInfoComplete",
600 "dropping stale contact lookup info for " + callId);
601 }
602
603 final DialerCall call = CallList.getInstance().getCallById(callId);
604 if (call != null) {
605 call.getLogState().contactLookupResult = entry.contactLookupResult;
606 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700607 if (entry.lookupUri != null) {
linyuh183cb712017-12-27 17:02:37 -0800608 CallerInfoUtils.sendViewNotification(context, entry.lookupUri);
Eric Erfanianccca3152017-02-22 16:32:36 -0800609 }
610 }
611
612 private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
613 if (getUi() == null) {
614 return;
615 }
616
617 if (entry.photo != null) {
linyuh183cb712017-12-27 17:02:37 -0800618 if (primary != null && callId.equals(primary.getId())) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800619 updateContactEntry(entry, true /* isPrimary */);
linyuh183cb712017-12-27 17:02:37 -0800620 } else if (secondary != null && callId.equals(secondary.getId())) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800621 updateContactEntry(entry, false /* isPrimary */);
622 }
623 }
624 }
625
626 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
627 if (isPrimary) {
linyuh183cb712017-12-27 17:02:37 -0800628 primaryContactInfo = entry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800629 updatePrimaryDisplayInfo();
630 } else {
linyuh183cb712017-12-27 17:02:37 -0800631 secondaryContactInfo = entry;
Eric Erfanianccca3152017-02-22 16:32:36 -0800632 updateSecondaryDisplayInfo();
633 }
634 }
635
Eric Erfanianccca3152017-02-22 16:32:36 -0800636 private void updatePrimaryDisplayInfo() {
linyuh183cb712017-12-27 17:02:37 -0800637 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800638 // TODO: May also occur if search result comes back after ui is destroyed. Look into
639 // removing that case completely.
640 LogUtil.v(
641 "CallCardPresenter.updatePrimaryDisplayInfo",
642 "updatePrimaryDisplayInfo called but ui is null!");
643 return;
644 }
645
linyuh183cb712017-12-27 17:02:37 -0800646 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800647 // Clear the primary display info.
Android Dialerf9439102018-02-21 08:05:14 -0800648 inCallScreen.setPrimary(PrimaryInfo.empty());
Eric Erfanianccca3152017-02-22 16:32:36 -0800649 return;
650 }
651
652 // Hide the contact photo if we are in a video call and the incoming video surface is
653 // showing.
654 boolean showContactPhoto =
linyuh183cb712017-12-27 17:02:37 -0800655 !VideoCallPresenter.showIncomingVideo(primary.getVideoState(), primary.getState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800656
657 // DialerCall placed through a work phone account.
linyuh183cb712017-12-27 17:02:37 -0800658 boolean hasWorkCallProperty = primary.hasProperty(PROPERTY_ENTERPRISE_CALL);
Eric Erfanianccca3152017-02-22 16:32:36 -0800659
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700660 MultimediaData multimediaData = null;
linyuh183cb712017-12-27 17:02:37 -0800661 if (primary.getEnrichedCallSession() != null) {
662 multimediaData = primary.getEnrichedCallSession().getMultimediaData();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700663 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800664
linyuh183cb712017-12-27 17:02:37 -0800665 if (primary.isConferenceCall()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800666 LogUtil.v(
667 "CallCardPresenter.updatePrimaryDisplayInfo",
668 "update primary display info for conference call.");
669
linyuh183cb712017-12-27 17:02:37 -0800670 inCallScreen.setPrimary(
Android Dialerf9439102018-02-21 08:05:14 -0800671 PrimaryInfo.builder()
672 .setName(
673 CallerInfoUtils.getConferenceString(
674 context, primary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)))
675 .setNameIsNumber(false)
676 .setPhotoType(ContactPhotoType.DEFAULT_PLACEHOLDER)
677 .setIsSipCall(false)
678 .setIsContactPhotoShown(showContactPhoto)
679 .setIsWorkCall(hasWorkCallProperty)
680 .setIsSpam(false)
681 .setIsLocalContact(false)
682 .setAnsweringDisconnectsOngoingCall(false)
683 .setShouldShowLocation(shouldShowLocation())
684 .setShowInCallButtonGrid(true)
685 .setNumberPresentation(primary.getNumberPresentation())
686 .build());
linyuh183cb712017-12-27 17:02:37 -0800687 } else if (primaryContactInfo != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800688 LogUtil.v(
689 "CallCardPresenter.updatePrimaryDisplayInfo",
linyuh183cb712017-12-27 17:02:37 -0800690 "update primary display info for " + primaryContactInfo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800691
linyuh183cb712017-12-27 17:02:37 -0800692 String name = getNameForCall(primaryContactInfo);
Eric Erfanianccca3152017-02-22 16:32:36 -0800693 String number;
694
linyuh183cb712017-12-27 17:02:37 -0800695 boolean isChildNumberShown = !TextUtils.isEmpty(primary.getChildNumber());
696 boolean isForwardedNumberShown = !TextUtils.isEmpty(primary.getLastForwardedNumber());
697 boolean isCallSubjectShown = shouldShowCallSubject(primary);
Eric Erfanianccca3152017-02-22 16:32:36 -0800698
699 if (isCallSubjectShown) {
700 number = null;
701 } else if (isChildNumberShown) {
linyuh183cb712017-12-27 17:02:37 -0800702 number = context.getString(R.string.child_number, primary.getChildNumber());
Eric Erfanianccca3152017-02-22 16:32:36 -0800703 } else if (isForwardedNumberShown) {
704 // Use last forwarded number instead of second line, if present.
linyuh183cb712017-12-27 17:02:37 -0800705 number = primary.getLastForwardedNumber();
Eric Erfanianccca3152017-02-22 16:32:36 -0800706 } else {
linyuh183cb712017-12-27 17:02:37 -0800707 number = primaryContactInfo.number;
Eric Erfanianccca3152017-02-22 16:32:36 -0800708 }
709
linyuh183cb712017-12-27 17:02:37 -0800710 boolean nameIsNumber = name != null && name.equals(primaryContactInfo.number);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700711
Eric Erfanianccca3152017-02-22 16:32:36 -0800712 // DialerCall with caller that is a work contact.
linyuh183cb712017-12-27 17:02:37 -0800713 boolean isWorkContact = (primaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
714 inCallScreen.setPrimary(
Android Dialerf9439102018-02-21 08:05:14 -0800715 PrimaryInfo.builder()
716 .setNumber(number)
717 .setName(primary.updateNameIfRestricted(name))
718 .setNameIsNumber(nameIsNumber)
twyencb2a9812018-03-23 18:21:13 -0700719 .setLocation(
Android Dialerf9439102018-02-21 08:05:14 -0800720 shouldShowLocationAsLabel(nameIsNumber, primaryContactInfo.shouldShowLocation)
721 ? primaryContactInfo.location
722 : null)
twyencb2a9812018-03-23 18:21:13 -0700723 .setLabel(isChildNumberShown || isCallSubjectShown ? null : primaryContactInfo.label)
Android Dialerf9439102018-02-21 08:05:14 -0800724 .setPhoto(primaryContactInfo.photo)
Android Dialerf13f0b82018-05-23 14:05:34 -0700725 .setPhotoUri(primaryContactInfo.displayPhotoUri)
Android Dialerf9439102018-02-21 08:05:14 -0800726 .setPhotoType(primaryContactInfo.photoType)
727 .setIsSipCall(primaryContactInfo.isSipCall)
728 .setIsContactPhotoShown(showContactPhoto)
729 .setIsWorkCall(hasWorkCallProperty || isWorkContact)
730 .setIsSpam(primary.isSpam())
731 .setIsLocalContact(primaryContactInfo.isLocalContact())
732 .setAnsweringDisconnectsOngoingCall(primary.answeringDisconnectsForegroundVideoCall())
733 .setShouldShowLocation(shouldShowLocation())
734 .setContactInfoLookupKey(primaryContactInfo.lookupKey)
735 .setMultimediaData(multimediaData)
736 .setShowInCallButtonGrid(true)
737 .setNumberPresentation(primary.getNumberPresentation())
738 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800739 } else {
740 // Clear the primary display info.
Android Dialerf9439102018-02-21 08:05:14 -0800741 inCallScreen.setPrimary(PrimaryInfo.empty());
Eric Erfanianccca3152017-02-22 16:32:36 -0800742 }
743
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700744 if (isInCallScreenReady) {
linyuh183cb712017-12-27 17:02:37 -0800745 inCallScreen.showLocationUi(getLocationFragment());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700746 } else {
747 LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
748 }
749 }
750
Eric Erfaniand8046e52017-04-06 09:41:50 -0700751 private static boolean shouldShowLocationAsLabel(
752 boolean nameIsNumber, boolean shouldShowLocation) {
753 if (nameIsNumber) {
754 return true;
755 }
756 if (shouldShowLocation) {
757 return true;
758 }
759 return false;
760 }
761
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700762 private Fragment getLocationFragment() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700763 if (!shouldShowLocation()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700764 return null;
765 }
766 LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
linyuh183cb712017-12-27 17:02:37 -0800767 return callLocation.getLocationFragment(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800768 }
769
770 private boolean shouldShowLocation() {
linyuh183cb712017-12-27 17:02:37 -0800771 if (!ConfigProviderBindings.get(context)
wangqi1d62ab22017-11-29 14:29:31 -0800772 .getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
773 LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
774 return false;
775 }
776 if (!isPotentialEmergencyCall()) {
777 LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
778 return false;
779 }
780 if (!hasLocationPermission()) {
781 LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
782 return false;
783 }
784 if (isBatteryTooLowForEmergencyLocation()) {
785 LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
786 return false;
787 }
linyuh437ae952018-03-26 12:46:18 -0700788 if (inCallScreen.getInCallScreenFragment().getActivity().isInMultiWindowMode()) {
wangqi1d62ab22017-11-29 14:29:31 -0800789 LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
790 return false;
791 }
linyuh183cb712017-12-27 17:02:37 -0800792 if (primary.isVideoCall()) {
wangqi1d62ab22017-11-29 14:29:31 -0800793 LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
794 return false;
795 }
linyuh183cb712017-12-27 17:02:37 -0800796 if (!callLocation.canGetLocation(context)) {
wangqi1d62ab22017-11-29 14:29:31 -0800797 LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
798 return false;
799 }
800 return true;
801 }
802
803 private boolean isPotentialEmergencyCall() {
linyuh183cb712017-12-27 17:02:37 -0800804 if (isOutgoingEmergencyCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800805 LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
806 return true;
linyuh183cb712017-12-27 17:02:37 -0800807 } else if (isIncomingEmergencyCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800808 LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
809 return true;
linyuh183cb712017-12-27 17:02:37 -0800810 } else if (isIncomingEmergencyCall(secondary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800811 LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
812 return true;
813 }
814 return false;
815 }
816
817 private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
818 return call != null && !call.isIncoming() && call.isEmergencyCall();
819 }
820
821 private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
822 return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
823 }
824
825 private boolean hasLocationPermission() {
linyuh183cb712017-12-27 17:02:37 -0800826 return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
Eric Erfanianccca3152017-02-22 16:32:36 -0800827 == PackageManager.PERMISSION_GRANTED;
828 }
829
830 private boolean isBatteryTooLowForEmergencyLocation() {
831 Intent batteryStatus =
linyuh183cb712017-12-27 17:02:37 -0800832 context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
Eric Erfanianccca3152017-02-22 16:32:36 -0800833 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
834 if (status == BatteryManager.BATTERY_STATUS_CHARGING
835 || status == BatteryManager.BATTERY_STATUS_FULL) {
836 // Plugged in or full battery
837 return false;
838 }
839 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
840 int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
841 float batteryPercent = (100f * level) / scale;
842 long threshold =
linyuh183cb712017-12-27 17:02:37 -0800843 ConfigProviderBindings.get(context)
Eric Erfanianccca3152017-02-22 16:32:36 -0800844 .getLong(
845 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
846 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
847 LogUtil.i(
848 "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
849 "percent charged: " + batteryPercent + ", min required charge: " + threshold);
850 return batteryPercent < threshold;
851 }
852
853 private void updateSecondaryDisplayInfo() {
linyuh183cb712017-12-27 17:02:37 -0800854 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800855 return;
856 }
857
linyuh183cb712017-12-27 17:02:37 -0800858 if (secondary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800859 // Clear the secondary display info.
Android Dialer9e335e22018-03-01 08:27:39 -0800860 inCallScreen.setSecondary(SecondaryInfo.builder().setIsFullscreen(isFullscreen).build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800861 return;
862 }
863
linyuh183cb712017-12-27 17:02:37 -0800864 if (secondary.isMergeInProcess()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700865 LogUtil.i(
866 "CallCardPresenter.updateSecondaryDisplayInfo",
867 "secondary call is merge in process, clearing info");
Android Dialer9e335e22018-03-01 08:27:39 -0800868 inCallScreen.setSecondary(SecondaryInfo.builder().setIsFullscreen(isFullscreen).build());
Eric Erfanian2ca43182017-08-31 06:57:16 -0700869 return;
870 }
871
linyuh183cb712017-12-27 17:02:37 -0800872 if (secondary.isConferenceCall()) {
873 inCallScreen.setSecondary(
Android Dialer9e335e22018-03-01 08:27:39 -0800874 SecondaryInfo.builder()
875 .setShouldShow(true)
876 .setName(
877 CallerInfoUtils.getConferenceString(
878 context, secondary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)))
879 .setProviderLabel(secondary.getCallProviderLabel())
880 .setIsConference(true)
881 .setIsVideoCall(secondary.isVideoCall())
882 .setIsFullscreen(isFullscreen)
883 .build());
linyuh183cb712017-12-27 17:02:37 -0800884 } else if (secondaryContactInfo != null) {
885 LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + secondaryContactInfo);
886 String name = getNameForCall(secondaryContactInfo);
887 boolean nameIsNumber = name != null && name.equals(secondaryContactInfo.number);
888 inCallScreen.setSecondary(
Android Dialer9e335e22018-03-01 08:27:39 -0800889 SecondaryInfo.builder()
890 .setShouldShow(true)
891 .setName(secondary.updateNameIfRestricted(name))
892 .setNameIsNumber(nameIsNumber)
893 .setLabel(secondaryContactInfo.label)
894 .setProviderLabel(secondary.getCallProviderLabel())
895 .setIsVideoCall(secondary.isVideoCall())
896 .setIsFullscreen(isFullscreen)
897 .build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800898 } else {
899 // Clear the secondary display info.
Android Dialer9e335e22018-03-01 08:27:39 -0800900 inCallScreen.setSecondary(SecondaryInfo.builder().setIsFullscreen(isFullscreen).build());
Eric Erfanianccca3152017-02-22 16:32:36 -0800901 }
902 }
903
904 /** Returns the gateway number for any existing outgoing call. */
905 private String getGatewayNumber() {
906 if (hasOutgoingGatewayCall()) {
linyuh183cb712017-12-27 17:02:37 -0800907 return DialerCall.getNumberFromHandle(primary.getGatewayInfo().getGatewayAddress());
Eric Erfanianccca3152017-02-22 16:32:36 -0800908 }
909 return null;
910 }
911
912 /**
913 * Returns the label (line of text above the number/name) for any given call. For example,
914 * "calling via [Account/Google Voice]" for outgoing calls.
915 */
916 private String getConnectionLabel() {
linyuh183cb712017-12-27 17:02:37 -0800917 if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)
Eric Erfanianccca3152017-02-22 16:32:36 -0800918 != PackageManager.PERMISSION_GRANTED) {
919 return null;
920 }
linyuh183cb712017-12-27 17:02:37 -0800921 StatusHints statusHints = primary.getStatusHints();
Eric Erfanianccca3152017-02-22 16:32:36 -0800922 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
923 return statusHints.getLabel().toString();
924 }
925
926 if (hasOutgoingGatewayCall() && getUi() != null) {
927 // Return the label for the gateway app on outgoing calls.
linyuh183cb712017-12-27 17:02:37 -0800928 final PackageManager pm = context.getPackageManager();
Eric Erfanianccca3152017-02-22 16:32:36 -0800929 try {
930 ApplicationInfo info =
linyuh183cb712017-12-27 17:02:37 -0800931 pm.getApplicationInfo(primary.getGatewayInfo().getGatewayProviderPackageName(), 0);
Eric Erfanianccca3152017-02-22 16:32:36 -0800932 return pm.getApplicationLabel(info).toString();
933 } catch (PackageManager.NameNotFoundException e) {
934 LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
935 return null;
936 }
937 }
linyuh183cb712017-12-27 17:02:37 -0800938 return primary.getCallProviderLabel();
Eric Erfanianccca3152017-02-22 16:32:36 -0800939 }
940
twyen4fde0ac2018-03-22 18:04:23 -0700941 @Nullable
942 private SuggestionProvider.Reason getSimSuggestionReason() {
943 String value =
944 primary.getIntentExtras().getString(SuggestionProvider.EXTRA_SIM_SUGGESTION_REASON);
945 if (value == null) {
946 return null;
947 }
948 try {
949 return SuggestionProvider.Reason.valueOf(value);
950 } catch (IllegalArgumentException e) {
951 LogUtil.e("CallCardPresenter.getConnectionLabel", "unknown reason " + value);
952 return null;
953 }
954 }
955
Eric Erfanianccca3152017-02-22 16:32:36 -0800956 private Drawable getCallStateIcon() {
957 // Return connection icon if one exists.
linyuh183cb712017-12-27 17:02:37 -0800958 StatusHints statusHints = primary.getStatusHints();
Eric Erfanianccca3152017-02-22 16:32:36 -0800959 if (statusHints != null && statusHints.getIcon() != null) {
linyuh183cb712017-12-27 17:02:37 -0800960 Drawable icon = statusHints.getIcon().loadDrawable(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800961 if (icon != null) {
962 return icon;
963 }
964 }
965
966 return null;
967 }
968
969 private boolean hasOutgoingGatewayCall() {
970 // We only display the gateway information while STATE_DIALING so return false for any other
971 // call state.
972 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
973 // is also called after a contact search completes (call is not present yet). Split the
974 // UI update so it can receive independent updates.
linyuh183cb712017-12-27 17:02:37 -0800975 if (primary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800976 return false;
977 }
wangqibb94ca62018-04-27 14:34:04 -0700978 return DialerCallState.isDialing(primary.getState())
linyuh183cb712017-12-27 17:02:37 -0800979 && primary.getGatewayInfo() != null
980 && !primary.getGatewayInfo().isEmpty();
Eric Erfanianccca3152017-02-22 16:32:36 -0800981 }
982
983 /** Gets the name to display for the call. */
Eric Erfanian2ca43182017-08-31 06:57:16 -0700984 private String getNameForCall(ContactCacheEntry contactInfo) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800985 String preferredName =
986 ContactDisplayUtils.getPreferredDisplayName(
linyuh183cb712017-12-27 17:02:37 -0800987 contactInfo.namePrimary, contactInfo.nameAlternative, contactsPreferences);
Eric Erfanianccca3152017-02-22 16:32:36 -0800988 if (TextUtils.isEmpty(preferredName)) {
wangqia4623702018-04-02 14:23:38 -0700989 return TextUtils.isEmpty(contactInfo.number)
990 ? null
991 : BidiFormatter.getInstance()
992 .unicodeWrap(contactInfo.number, TextDirectionHeuristics.LTR);
Eric Erfanianccca3152017-02-22 16:32:36 -0800993 }
994 return preferredName;
995 }
996
Eric Erfanianccca3152017-02-22 16:32:36 -0800997 @Override
998 public void onSecondaryInfoClicked() {
linyuh183cb712017-12-27 17:02:37 -0800999 if (secondary == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001000 LogUtil.e(
1001 "CallCardPresenter.onSecondaryInfoClicked",
1002 "secondary info clicked but no secondary call.");
1003 return;
1004 }
1005
linyuh183cb712017-12-27 17:02:37 -08001006 Logger.get(context)
Eric Erfanian2ca43182017-08-31 06:57:16 -07001007 .logCallImpression(
1008 DialerImpression.Type.IN_CALL_SWAP_SECONDARY_BUTTON_PRESSED,
linyuh183cb712017-12-27 17:02:37 -08001009 primary.getUniqueCallId(),
1010 primary.getTimeAddedMs());
Eric Erfanianccca3152017-02-22 16:32:36 -08001011 LogUtil.i(
linyuh183cb712017-12-27 17:02:37 -08001012 "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + secondary);
1013 secondary.unhold();
Eric Erfanianccca3152017-02-22 16:32:36 -08001014 }
1015
1016 @Override
1017 public void onEndCallClicked() {
linyuh183cb712017-12-27 17:02:37 -08001018 LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + primary);
1019 if (primary != null) {
1020 primary.disconnect();
Eric Erfanianccca3152017-02-22 16:32:36 -08001021 }
linyuh183cb712017-12-27 17:02:37 -08001022 PostCall.onDisconnectPressed(context);
Eric Erfanianccca3152017-02-22 16:32:36 -08001023 }
1024
1025 /**
1026 * Handles a change to the fullscreen mode of the in-call UI.
1027 *
1028 * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
1029 */
1030 @Override
1031 public void onFullscreenModeChanged(boolean isFullscreenMode) {
linyuh183cb712017-12-27 17:02:37 -08001032 isFullscreen = isFullscreenMode;
1033 if (inCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001034 return;
1035 }
1036 maybeShowManageConferenceCallButton();
1037 }
1038
1039 private boolean isPrimaryCallActive() {
wangqibb94ca62018-04-27 14:34:04 -07001040 return primary != null && primary.getState() == DialerCallState.ACTIVE;
Eric Erfanianccca3152017-02-22 16:32:36 -08001041 }
1042
Eric Erfanianccca3152017-02-22 16:32:36 -08001043 private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
1044 if (primary == null) {
1045 return false;
1046 }
wangqibb94ca62018-04-27 14:34:04 -07001047 if ((!DialerCallState.isConnectingOrConnected(callState)
1048 && callState != DialerCallState.DISCONNECTING
1049 && callState != DialerCallState.DISCONNECTED)
1050 || callState == DialerCallState.INCOMING) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001051 return false;
1052 }
linyuh183cb712017-12-27 17:02:37 -08001053 if (this.primary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001054 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001055 return false;
1056 }
1057 return true;
1058 }
1059
1060 @Override
1061 public void onInCallScreenResumed() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001062 updatePrimaryDisplayInfo();
1063
Eric Erfanianccca3152017-02-22 16:32:36 -08001064 if (shouldSendAccessibilityEvent) {
1065 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1066 }
1067 }
1068
Eric Erfaniand8046e52017-04-06 09:41:50 -07001069 @Override
Eric Erfanian2ca43182017-08-31 06:57:16 -07001070 public void onInCallScreenPaused() {}
Eric Erfaniand8046e52017-04-06 09:41:50 -07001071
Eric Erfanianccca3152017-02-22 16:32:36 -08001072 static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
1073 AccessibilityManager am =
1074 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
1075 if (!am.isEnabled()) {
1076 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
1077 return false;
1078 }
1079 if (inCallScreen == null) {
1080 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
1081 return false;
1082 }
1083 Fragment fragment = inCallScreen.getInCallScreenFragment();
1084 if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
1085 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
1086 return false;
1087 }
1088
1089 DisplayManager displayManager =
1090 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1091 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1092 boolean screenIsOn = display.getState() == Display.STATE_ON;
1093 LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
1094 if (!screenIsOn) {
1095 return false;
1096 }
1097
1098 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1099 inCallScreen.dispatchPopulateAccessibilityEvent(event);
1100 View view = inCallScreen.getInCallScreenFragment().getView();
1101 view.getParent().requestSendAccessibilityEvent(view, event);
1102 return true;
1103 }
1104
1105 private void maybeSendAccessibilityEvent(
1106 InCallState oldState, final InCallState newState, boolean primaryChanged) {
1107 shouldSendAccessibilityEvent = false;
linyuh183cb712017-12-27 17:02:37 -08001108 if (context == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001109 return;
1110 }
1111 final AccessibilityManager am =
linyuh183cb712017-12-27 17:02:37 -08001112 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
Eric Erfanianccca3152017-02-22 16:32:36 -08001113 if (!am.isEnabled()) {
1114 return;
1115 }
1116 // Announce the current call if it's new incoming/outgoing call or primary call is changed
1117 // due to switching calls between two ongoing calls (one is on hold).
1118 if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
1119 || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
1120 || primaryChanged) {
1121 LogUtil.i(
1122 "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
1123 shouldSendAccessibilityEvent = true;
1124 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1125 }
1126 }
1127
1128 /**
1129 * Determines whether the call subject should be visible on the UI. For the call subject to be
1130 * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
1131 *
1132 * @param call The call.
1133 * @return {@code true} if the subject should be shown, {@code false} otherwise.
1134 */
1135 private boolean shouldShowCallSubject(DialerCall call) {
1136 if (call == null) {
1137 return false;
1138 }
1139
1140 boolean isIncomingOrWaiting =
wangqibb94ca62018-04-27 14:34:04 -07001141 primary.getState() == DialerCallState.INCOMING
1142 || primary.getState() == DialerCallState.CALL_WAITING;
Eric Erfanianccca3152017-02-22 16:32:36 -08001143 return isIncomingOrWaiting
1144 && !TextUtils.isEmpty(call.getCallSubject())
1145 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
1146 && call.isCallSubjectSupported();
1147 }
1148
1149 /**
1150 * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
1151 * call with a subject.
1152 *
1153 * @param call The call
1154 * @return {@code true} if the toast should be shown, {@code false} otherwise.
1155 */
1156 private boolean shouldShowNoteSentToast(DialerCall call) {
1157 return call != null
1158 && hasCallSubject(call)
wangqibb94ca62018-04-27 14:34:04 -07001159 && (call.getState() == DialerCallState.DIALING
1160 || call.getState() == DialerCallState.CONNECTING);
Eric Erfanianccca3152017-02-22 16:32:36 -08001161 }
1162
1163 private InCallScreen getUi() {
linyuh183cb712017-12-27 17:02:37 -08001164 return inCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -08001165 }
1166
Android Dialerf9439102018-02-21 08:05:14 -08001167 /** Callback for contact lookup. */
Eric Erfanianccca3152017-02-22 16:32:36 -08001168 public static class ContactLookupCallback implements ContactInfoCacheCallback {
1169
linyuh183cb712017-12-27 17:02:37 -08001170 private final WeakReference<CallCardPresenter> callCardPresenter;
1171 private final boolean isPrimary;
Eric Erfanianccca3152017-02-22 16:32:36 -08001172
1173 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
linyuh183cb712017-12-27 17:02:37 -08001174 this.callCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
1175 this.isPrimary = isPrimary;
Eric Erfanianccca3152017-02-22 16:32:36 -08001176 }
1177
1178 @Override
1179 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
linyuh183cb712017-12-27 17:02:37 -08001180 CallCardPresenter presenter = callCardPresenter.get();
Eric Erfanianccca3152017-02-22 16:32:36 -08001181 if (presenter != null) {
linyuh183cb712017-12-27 17:02:37 -08001182 presenter.onContactInfoComplete(callId, entry, isPrimary);
Eric Erfanianccca3152017-02-22 16:32:36 -08001183 }
1184 }
1185
1186 @Override
1187 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
linyuh183cb712017-12-27 17:02:37 -08001188 CallCardPresenter presenter = callCardPresenter.get();
Eric Erfanianccca3152017-02-22 16:32:36 -08001189 if (presenter != null) {
1190 presenter.onImageLoadComplete(callId, entry);
1191 }
1192 }
1193 }
1194}