blob: eb5221669bfecefab18c463d7f9cdcb0b9419a10 [file] [log] [blame]
Eric Erfanianccca3152017-02-22 16:32:36 -08001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.incallui;
18
19import static com.android.contacts.common.compat.CallCompat.Details.PROPERTY_ENTERPRISE_CALL;
20
21import android.Manifest;
Eric Erfanianccca3152017-02-22 16:32:36 -080022import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.PackageManager;
27import android.graphics.drawable.Drawable;
28import android.hardware.display.DisplayManager;
29import android.os.BatteryManager;
30import android.os.Handler;
Eric Erfanian2ca43182017-08-31 06:57:16 -070031import android.os.Trace;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070032import android.support.annotation.NonNull;
Eric Erfanianccca3152017-02-22 16:32:36 -080033import android.support.annotation.Nullable;
34import android.support.v4.app.Fragment;
35import android.support.v4.content.ContextCompat;
36import android.telecom.Call.Details;
37import android.telecom.StatusHints;
38import android.telecom.TelecomManager;
39import android.text.TextUtils;
40import android.view.Display;
41import android.view.View;
42import android.view.accessibility.AccessibilityEvent;
43import android.view.accessibility.AccessibilityManager;
44import com.android.contacts.common.ContactsUtils;
45import com.android.contacts.common.preference.ContactsPreferences;
46import com.android.contacts.common.util.ContactDisplayUtils;
47import com.android.dialer.common.Assert;
Eric Erfanianccca3152017-02-22 16:32:36 -080048import com.android.dialer.common.LogUtil;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070049import com.android.dialer.compat.ActivityCompat;
Eric Erfanian2ca43182017-08-31 06:57:16 -070050import com.android.dialer.configprovider.ConfigProviderBindings;
Android Dialer3855a6b2017-11-21 14:34:47 -080051import com.android.dialer.feedback.FeedbackComponent;
Eric Erfanian8369df02017-05-03 10:27:13 -070052import com.android.dialer.logging.DialerImpression;
Eric Erfaniand8046e52017-04-06 09:41:50 -070053import com.android.dialer.logging.Logger;
Eric Erfanianccca3152017-02-22 16:32:36 -080054import com.android.dialer.multimedia.MultimediaData;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070055import com.android.dialer.oem.MotorolaUtils;
wangqi97539352017-09-25 11:15:16 -070056import com.android.dialer.phonenumberutil.PhoneNumberHelper;
Eric Erfanian2ca43182017-08-31 06:57:16 -070057import com.android.dialer.postcall.PostCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080058import com.android.incallui.ContactInfoCache.ContactCacheEntry;
59import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
60import com.android.incallui.InCallPresenter.InCallDetailsListener;
61import com.android.incallui.InCallPresenter.InCallEventListener;
62import com.android.incallui.InCallPresenter.InCallState;
63import com.android.incallui.InCallPresenter.InCallStateListener;
64import com.android.incallui.InCallPresenter.IncomingCallListener;
65import com.android.incallui.call.CallList;
66import com.android.incallui.call.DialerCall;
Eric Erfanian2ca43182017-08-31 06:57:16 -070067import com.android.incallui.call.DialerCall.State;
Eric Erfanianccca3152017-02-22 16:32:36 -080068import com.android.incallui.call.DialerCallListener;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070069import com.android.incallui.calllocation.CallLocation;
70import com.android.incallui.calllocation.CallLocationComponent;
Eric Erfanianccca3152017-02-22 16:32:36 -080071import com.android.incallui.incall.protocol.ContactPhotoType;
72import com.android.incallui.incall.protocol.InCallScreen;
73import com.android.incallui.incall.protocol.InCallScreenDelegate;
74import com.android.incallui.incall.protocol.PrimaryCallState;
Eric Erfanian2ca43182017-08-31 06:57:16 -070075import com.android.incallui.incall.protocol.PrimaryCallState.ButtonState;
Eric Erfanianccca3152017-02-22 16:32:36 -080076import com.android.incallui.incall.protocol.PrimaryInfo;
77import com.android.incallui.incall.protocol.SecondaryInfo;
Eric Erfanian90508232017-03-24 09:31:16 -070078import com.android.incallui.videotech.utils.SessionModificationState;
Eric Erfanianccca3152017-02-22 16:32:36 -080079import java.lang.ref.WeakReference;
80
81/**
82 * Controller for the Call Card Fragment. This class listens for changes to InCallState and passes
83 * it along to the fragment.
84 */
85public class CallCardPresenter
86 implements InCallStateListener,
87 IncomingCallListener,
88 InCallDetailsListener,
89 InCallEventListener,
90 InCallScreenDelegate,
Eric Erfanian2ca43182017-08-31 06:57:16 -070091 DialerCallListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080092
93 /**
94 * Amount of time to wait before sending an announcement via the accessibility manager. When the
95 * call state changes to an outgoing or incoming state for the first time, the UI can often be
96 * changing due to call updates or contact lookup. This allows the UI to settle to a stable state
97 * to ensure that the correct information is announced.
98 */
99 private static final long ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS = 500;
100
101 /** Flag to allow the user's current location to be shown during emergency calls. */
102 private static final String CONFIG_ENABLE_EMERGENCY_LOCATION = "config_enable_emergency_location";
103
104 private static final boolean CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT = true;
105
106 /**
107 * Make it possible to not get location during an emergency call if the battery is too low, since
108 * doing so could trigger gps and thus potentially cause the phone to die in the middle of the
109 * call.
110 */
111 private static final String CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION =
112 "min_battery_percent_for_emergency_location";
113
114 private static final long CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT = 10;
115
116 private final Context mContext;
117 private final Handler handler = new Handler();
118
119 private DialerCall mPrimary;
120 private DialerCall mSecondary;
121 private ContactCacheEntry mPrimaryContactInfo;
122 private ContactCacheEntry mSecondaryContactInfo;
123 @Nullable private ContactsPreferences mContactsPreferences;
124 private boolean mIsFullscreen = false;
125 private InCallScreen mInCallScreen;
126 private boolean isInCallScreenReady;
127 private boolean shouldSendAccessibilityEvent;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700128
129 @NonNull private final CallLocation callLocation;
Eric Erfanianccca3152017-02-22 16:32:36 -0800130 private final Runnable sendAccessibilityEventRunnable =
131 new Runnable() {
132 @Override
133 public void run() {
134 shouldSendAccessibilityEvent = !sendAccessibilityEvent(mContext, getUi());
135 LogUtil.i(
136 "CallCardPresenter.sendAccessibilityEventRunnable",
137 "still should send: %b",
138 shouldSendAccessibilityEvent);
139 if (!shouldSendAccessibilityEvent) {
140 handler.removeCallbacks(this);
141 }
142 }
143 };
144
145 public CallCardPresenter(Context context) {
wangqi385a5a12017-09-28 10:44:54 -0700146 LogUtil.i("CallCardPresenter.constructor", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800147 mContext = Assert.isNotNull(context).getApplicationContext();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700148 callLocation = CallLocationComponent.get(mContext).getCallLocation();
Eric Erfanianccca3152017-02-22 16:32:36 -0800149 }
150
151 private static boolean hasCallSubject(DialerCall call) {
152 return !TextUtils.isEmpty(call.getCallSubject());
153 }
154
Android Dialer3855a6b2017-11-21 14:34:47 -0800155 private void addCallFeedbackListener(Context context) {
156 LogUtil.d("CallCardPresenter.addCallFeedbackListener", "Adding call feedback listener");
157 CallList.getInstance().addListener(FeedbackComponent.get(context).getCallFeedbackListener());
158 }
159
Eric Erfanianccca3152017-02-22 16:32:36 -0800160 @Override
161 public void onInCallScreenDelegateInit(InCallScreen inCallScreen) {
162 Assert.isNotNull(inCallScreen);
163 mInCallScreen = inCallScreen;
164 mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext);
165
166 // Call may be null if disconnect happened already.
167 DialerCall call = CallList.getInstance().getFirstCall();
168 if (call != null) {
169 mPrimary = call;
170 if (shouldShowNoteSentToast(mPrimary)) {
171 mInCallScreen.showNoteSentToast();
172 }
173 call.addListener(this);
Android Dialer3855a6b2017-11-21 14:34:47 -0800174 addCallFeedbackListener(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800175 // start processing lookups right away.
176 if (!call.isConferenceCall()) {
177 startContactInfoSearch(call, true, call.getState() == DialerCall.State.INCOMING);
178 } else {
179 updateContactEntry(null, true);
180 }
181 }
182
183 onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
184 }
185
186 @Override
187 public void onInCallScreenReady() {
wangqi385a5a12017-09-28 10:44:54 -0700188 LogUtil.i("CallCardPresenter.onInCallScreenReady", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800189 Assert.checkState(!isInCallScreenReady);
190 if (mContactsPreferences != null) {
191 mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
192 }
193
Eric Erfanianccca3152017-02-22 16:32:36 -0800194 // Contact search may have completed before ui is ready.
195 if (mPrimaryContactInfo != null) {
196 updatePrimaryDisplayInfo();
197 }
198
199 // Register for call state changes last
200 InCallPresenter.getInstance().addListener(this);
201 InCallPresenter.getInstance().addIncomingCallListener(this);
202 InCallPresenter.getInstance().addDetailsListener(this);
203 InCallPresenter.getInstance().addInCallEventListener(this);
204 isInCallScreenReady = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700205
Eric Erfaniand8046e52017-04-06 09:41:50 -0700206 // Log location impressions
207 if (isOutgoingEmergencyCall(mPrimary)) {
208 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_NEW_EMERGENCY_CALL);
209 } else if (isIncomingEmergencyCall(mPrimary) || isIncomingEmergencyCall(mSecondary)) {
210 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_CALLBACK);
211 }
212
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700213 // Showing the location may have been skipped if the UI wasn't ready during previous layout.
214 if (shouldShowLocation()) {
wangqi1d62ab22017-11-29 14:29:31 -0800215 mInCallScreen.showLocationUi(getLocationFragment());
Eric Erfaniand8046e52017-04-06 09:41:50 -0700216
217 // Log location impressions
218 if (!hasLocationPermission()) {
219 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_NO_LOCATION_PERMISSION);
220 } else if (isBatteryTooLowForEmergencyLocation()) {
221 Logger.get(mContext)
222 .logImpression(DialerImpression.Type.EMERGENCY_BATTERY_TOO_LOW_TO_GET_LOCATION);
223 } else if (!callLocation.canGetLocation(mContext)) {
224 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_CANT_GET_LOCATION);
225 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700226 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800227 }
228
229 @Override
230 public void onInCallScreenUnready() {
wangqi385a5a12017-09-28 10:44:54 -0700231 LogUtil.i("CallCardPresenter.onInCallScreenUnready", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800232 Assert.checkState(isInCallScreenReady);
233
Eric Erfanianccca3152017-02-22 16:32:36 -0800234 // stop getting call state changes
235 InCallPresenter.getInstance().removeListener(this);
236 InCallPresenter.getInstance().removeIncomingCallListener(this);
237 InCallPresenter.getInstance().removeDetailsListener(this);
238 InCallPresenter.getInstance().removeInCallEventListener(this);
239 if (mPrimary != null) {
240 mPrimary.removeListener(this);
241 }
242
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700243 callLocation.close();
244
Eric Erfanianccca3152017-02-22 16:32:36 -0800245 mPrimary = null;
246 mPrimaryContactInfo = null;
247 mSecondaryContactInfo = null;
248 isInCallScreenReady = false;
249 }
250
251 @Override
252 public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
253 // same logic should happen as with onStateChange()
254 onStateChange(oldState, newState, CallList.getInstance());
255 }
256
257 @Override
258 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700259 Trace.beginSection("CallCardPresenter.onStateChange");
260 LogUtil.v("CallCardPresenter.onStateChange", "oldState: %s, newState: %s", oldState, newState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800261 if (mInCallScreen == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700262 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800263 return;
264 }
265
266 DialerCall primary = null;
267 DialerCall secondary = null;
268
269 if (newState == InCallState.INCOMING) {
270 primary = callList.getIncomingCall();
271 } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
272 primary = callList.getOutgoingCall();
273 if (primary == null) {
274 primary = callList.getPendingOutgoingCall();
275 }
276
277 // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
278 // highest priority call to display as the secondary call.
279 secondary = getCallToDisplay(callList, null, true);
280 } else if (newState == InCallState.INCALL) {
281 primary = getCallToDisplay(callList, null, false);
282 secondary = getCallToDisplay(callList, primary, true);
283 }
284
285 LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
286 LogUtil.v("CallCardPresenter.onStateChange", "secondary call: " + secondary);
287
288 final boolean primaryChanged =
289 !(DialerCall.areSame(mPrimary, primary) && DialerCall.areSameNumber(mPrimary, primary));
290 final boolean secondaryChanged =
291 !(DialerCall.areSame(mSecondary, secondary)
292 && DialerCall.areSameNumber(mSecondary, secondary));
293
294 mSecondary = secondary;
295 DialerCall previousPrimary = mPrimary;
296 mPrimary = primary;
297
298 if (mPrimary != null) {
299 InCallPresenter.getInstance().onForegroundCallChanged(mPrimary);
300 mInCallScreen.updateInCallScreenColors();
301 }
302
303 if (primaryChanged && shouldShowNoteSentToast(primary)) {
304 mInCallScreen.showNoteSentToast();
305 }
306
307 // Refresh primary call information if either:
308 // 1. Primary call changed.
309 // 2. The call's ability to manage conference has changed.
310 if (shouldRefreshPrimaryInfo(primaryChanged)) {
311 // primary call has changed
312 if (previousPrimary != null) {
313 previousPrimary.removeListener(this);
314 }
315 mPrimary.addListener(this);
316
317 mPrimaryContactInfo =
318 ContactInfoCache.buildCacheEntryFromCall(
319 mContext, mPrimary, mPrimary.getState() == DialerCall.State.INCOMING);
320 updatePrimaryDisplayInfo();
321 maybeStartSearch(mPrimary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800322 }
323
324 if (previousPrimary != null && mPrimary == null) {
325 previousPrimary.removeListener(this);
326 }
327
wangqic8cf79e2017-10-17 09:21:00 -0700328 if (secondaryChanged) {
329 if (mSecondary == null) {
330 // Secondary call may have ended. Update the ui.
331 mSecondaryContactInfo = null;
332 updateSecondaryDisplayInfo();
333 } else {
334 // secondary call has changed
335 mSecondaryContactInfo =
336 ContactInfoCache.buildCacheEntryFromCall(
337 mContext, mSecondary, mSecondary.getState() == DialerCall.State.INCOMING);
338 updateSecondaryDisplayInfo();
339 maybeStartSearch(mSecondary, false);
340 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800341 }
342
343 // Set the call state
344 int callState = DialerCall.State.IDLE;
345 if (mPrimary != null) {
346 callState = mPrimary.getState();
347 updatePrimaryCallState();
348 } else {
349 getUi().setCallState(PrimaryCallState.createEmptyPrimaryCallState());
350 }
351
352 maybeShowManageConferenceCallButton();
353
354 // Hide the end call button instantly if we're receiving an incoming call.
355 getUi()
356 .setEndCallButtonEnabled(
357 shouldShowEndCallButton(mPrimary, callState),
358 callState != DialerCall.State.INCOMING /* animate */);
359
360 maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700361 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800362 }
363
364 @Override
365 public void onDetailsChanged(DialerCall call, Details details) {
366 updatePrimaryCallState();
367
368 if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
369 != details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
370 maybeShowManageConferenceCallButton();
371 }
372 }
373
374 @Override
375 public void onDialerCallDisconnect() {}
376
377 @Override
378 public void onDialerCallUpdate() {
379 // No-op; specific call updates handled elsewhere.
380 }
381
382 @Override
383 public void onWiFiToLteHandover() {}
384
385 @Override
386 public void onHandoverToWifiFailure() {}
387
Eric Erfanianc857f902017-05-15 14:05:33 -0700388 @Override
389 public void onInternationalCallOnWifi() {}
390
Eric Erfanian2ca43182017-08-31 06:57:16 -0700391 @Override
392 public void onEnrichedCallSessionUpdate() {
393 LogUtil.enterBlock("CallCardPresenter.onEnrichedCallSessionUpdate");
394 updatePrimaryDisplayInfo();
395 }
396
Eric Erfanianccca3152017-02-22 16:32:36 -0800397 /** Handles a change to the child number by refreshing the primary call info. */
398 @Override
399 public void onDialerCallChildNumberChange() {
400 LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
401
402 if (mPrimary == null) {
403 return;
404 }
405 updatePrimaryDisplayInfo();
406 }
407
408 /** Handles a change to the last forwarding number by refreshing the primary call info. */
409 @Override
410 public void onDialerCallLastForwardedNumberChange() {
411 LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
412
413 if (mPrimary == null) {
414 return;
415 }
416 updatePrimaryDisplayInfo();
417 updatePrimaryCallState();
418 }
419
420 @Override
421 public void onDialerCallUpgradeToVideo() {}
422
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700423 /** Handles a change to the session modification state for a call. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800424 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700425 public void onDialerCallSessionModificationStateChange() {
426 LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
Eric Erfanianccca3152017-02-22 16:32:36 -0800427
428 if (mPrimary == null) {
429 return;
430 }
431 getUi()
432 .setEndCallButtonEnabled(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700433 mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700434 != SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800435 true /* shouldAnimate */);
436 updatePrimaryCallState();
437 }
438
Eric Erfanianccca3152017-02-22 16:32:36 -0800439 private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
440 if (mPrimary == null) {
441 return false;
442 }
443 return primaryChanged
444 || mInCallScreen.isManageConferenceVisible() != shouldShowManageConference();
445 }
446
447 private void updatePrimaryCallState() {
448 if (getUi() != null && mPrimary != null) {
449 boolean isWorkCall =
450 mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL)
451 || (mPrimaryContactInfo != null
452 && mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
453 boolean isHdAudioCall =
454 isPrimaryCallActive() && mPrimary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700455 boolean isAttemptingHdAudioCall =
456 !isHdAudioCall
457 && !mPrimary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
458 && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(mContext);
459
460 boolean isBusiness = mPrimaryContactInfo != null && mPrimaryContactInfo.isBusiness;
461
Eric Erfanianccca3152017-02-22 16:32:36 -0800462 // Check for video state change and update the visibility of the contact photo. The contact
463 // photo is hidden when the incoming video surface is shown.
464 // The contact photo visibility can also change in setPrimary().
465 boolean shouldShowContactPhoto =
466 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
467 getUi()
468 .setCallState(
469 new PrimaryCallState(
470 mPrimary.getState(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700471 mPrimary.isVideoCall(),
472 mPrimary.getVideoTech().getSessionModificationState(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800473 mPrimary.getDisconnectCause(),
474 getConnectionLabel(),
475 getCallStateIcon(),
476 getGatewayNumber(),
477 shouldShowCallSubject(mPrimary) ? mPrimary.getCallSubject() : null,
wangqi97539352017-09-25 11:15:16 -0700478 PhoneNumberHelper.formatNumber(
479 mPrimary.getCallbackNumber(), mPrimary.getSimCountryIso()),
Eric Erfanianccca3152017-02-22 16:32:36 -0800480 mPrimary.hasProperty(Details.PROPERTY_WIFI),
Eric Erfanian83b20212017-05-31 08:53:10 -0700481 mPrimary.isConferenceCall()
482 && !mPrimary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE),
Eric Erfanianccca3152017-02-22 16:32:36 -0800483 isWorkCall,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700484 isAttemptingHdAudioCall,
Eric Erfanianccca3152017-02-22 16:32:36 -0800485 isHdAudioCall,
486 !TextUtils.isEmpty(mPrimary.getLastForwardedNumber()),
487 shouldShowContactPhoto,
488 mPrimary.getConnectTimeMillis(),
wangqi9982f0d2017-10-11 17:46:07 -0700489 mPrimary.isVoiceMailNumber(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700490 mPrimary.isRemotelyHeld(),
Eric Erfanian2ca43182017-08-31 06:57:16 -0700491 isBusiness,
492 supports2ndCallOnHold(),
493 getSwapToSecondaryButtonState(),
494 mPrimary.isAssistedDialed(),
erfaniand0f207f2017-10-11 12:23:29 -0700495 null,
496 mPrimary.getAssistedDialingExtras()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800497
498 InCallActivity activity =
499 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
500 if (activity != null) {
501 activity.onPrimaryCallStateChanged();
502 }
503 }
504 }
505
Eric Erfanian2ca43182017-08-31 06:57:16 -0700506 private @ButtonState int getSwapToSecondaryButtonState() {
507 if (mSecondary == null) {
508 return ButtonState.NOT_SUPPORT;
509 }
510 if (mPrimary.getState() == State.ACTIVE) {
511 return ButtonState.ENABLED;
512 }
513 return ButtonState.DISABLED;
514 }
515
Eric Erfanianccca3152017-02-22 16:32:36 -0800516 /** Only show the conference call button if we can manage the conference. */
517 private void maybeShowManageConferenceCallButton() {
518 getUi().showManageConferenceCallButton(shouldShowManageConference());
519 }
520
521 /**
522 * Determines if the manage conference button should be visible, based on the current primary
523 * call.
524 *
525 * @return {@code True} if the manage conference button should be visible.
526 */
527 private boolean shouldShowManageConference() {
528 if (mPrimary == null) {
529 return false;
530 }
531
532 return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)
533 && !mIsFullscreen;
534 }
535
Eric Erfanian2ca43182017-08-31 06:57:16 -0700536 private boolean supports2ndCallOnHold() {
537 DialerCall firstCall = CallList.getInstance().getActiveOrBackgroundCall();
538 DialerCall incomingCall = CallList.getInstance().getIncomingCall();
539 if (firstCall != null && incomingCall != null && firstCall != incomingCall) {
540 return incomingCall.can(Details.CAPABILITY_HOLD);
541 }
542 return true;
543 }
544
Eric Erfanianccca3152017-02-22 16:32:36 -0800545 @Override
546 public void onCallStateButtonClicked() {
547 Intent broadcastIntent = Bindings.get(mContext).getCallStateButtonBroadcastIntent(mContext);
548 if (broadcastIntent != null) {
549 LogUtil.v(
550 "CallCardPresenter.onCallStateButtonClicked",
551 "sending call state button broadcast: " + broadcastIntent);
552 mContext.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
553 }
554 }
555
556 @Override
557 public void onManageConferenceClicked() {
558 InCallActivity activity =
559 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
560 activity.showConferenceFragment(true);
561 }
562
563 @Override
564 public void onShrinkAnimationComplete() {
565 InCallPresenter.getInstance().onShrinkAnimationComplete();
566 }
567
Eric Erfanianccca3152017-02-22 16:32:36 -0800568 private void maybeStartSearch(DialerCall call, boolean isPrimary) {
569 // no need to start search for conference calls which show generic info.
570 if (call != null && !call.isConferenceCall()) {
571 startContactInfoSearch(call, isPrimary, call.getState() == DialerCall.State.INCOMING);
572 }
573 }
574
Eric Erfanianccca3152017-02-22 16:32:36 -0800575 /** Starts a query for more contact data for the save primary and secondary calls. */
576 private void startContactInfoSearch(
577 final DialerCall call, final boolean isPrimary, boolean isIncoming) {
578 final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
579
580 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
581 }
582
583 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
584 final boolean entryMatchesExistingCall =
585 (isPrimary && mPrimary != null && TextUtils.equals(callId, mPrimary.getId()))
586 || (!isPrimary && mSecondary != null && TextUtils.equals(callId, mSecondary.getId()));
587 if (entryMatchesExistingCall) {
588 updateContactEntry(entry, isPrimary);
589 } else {
590 LogUtil.e(
591 "CallCardPresenter.onContactInfoComplete",
592 "dropping stale contact lookup info for " + callId);
593 }
594
595 final DialerCall call = CallList.getInstance().getCallById(callId);
596 if (call != null) {
597 call.getLogState().contactLookupResult = entry.contactLookupResult;
598 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700599 if (entry.lookupUri != null) {
600 CallerInfoUtils.sendViewNotification(mContext, entry.lookupUri);
Eric Erfanianccca3152017-02-22 16:32:36 -0800601 }
602 }
603
604 private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
605 if (getUi() == null) {
606 return;
607 }
608
609 if (entry.photo != null) {
610 if (mPrimary != null && callId.equals(mPrimary.getId())) {
611 updateContactEntry(entry, true /* isPrimary */);
612 } else if (mSecondary != null && callId.equals(mSecondary.getId())) {
613 updateContactEntry(entry, false /* isPrimary */);
614 }
615 }
616 }
617
618 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
619 if (isPrimary) {
620 mPrimaryContactInfo = entry;
621 updatePrimaryDisplayInfo();
622 } else {
623 mSecondaryContactInfo = entry;
624 updateSecondaryDisplayInfo();
625 }
626 }
627
628 /**
629 * Get the highest priority call to display. Goes through the calls and chooses which to return
630 * based on priority of which type of call to display to the user. Callers can use the "ignore"
631 * feature to get the second best call by passing a previously found primary call as ignore.
632 *
633 * @param ignore A call to ignore if found.
634 */
635 private DialerCall getCallToDisplay(
636 CallList callList, DialerCall ignore, boolean skipDisconnected) {
637 // Active calls come second. An active call always gets precedent.
638 DialerCall retval = callList.getActiveCall();
639 if (retval != null && retval != ignore) {
640 return retval;
641 }
642
643 // Sometimes there is intemediate state that two calls are in active even one is about
644 // to be on hold.
645 retval = callList.getSecondActiveCall();
646 if (retval != null && retval != ignore) {
647 return retval;
648 }
649
650 // Disconnected calls get primary position if there are no active calls
651 // to let user know quickly what call has disconnected. Disconnected
652 // calls are very short lived.
653 if (!skipDisconnected) {
654 retval = callList.getDisconnectingCall();
655 if (retval != null && retval != ignore) {
656 return retval;
657 }
658 retval = callList.getDisconnectedCall();
659 if (retval != null && retval != ignore) {
660 return retval;
661 }
662 }
663
664 // Then we go to background call (calls on hold)
665 retval = callList.getBackgroundCall();
666 if (retval != null && retval != ignore) {
667 return retval;
668 }
669
670 // Lastly, we go to a second background call.
671 retval = callList.getSecondBackgroundCall();
672
673 return retval;
674 }
675
676 private void updatePrimaryDisplayInfo() {
677 if (mInCallScreen == null) {
678 // TODO: May also occur if search result comes back after ui is destroyed. Look into
679 // removing that case completely.
680 LogUtil.v(
681 "CallCardPresenter.updatePrimaryDisplayInfo",
682 "updatePrimaryDisplayInfo called but ui is null!");
683 return;
684 }
685
686 if (mPrimary == null) {
687 // Clear the primary display info.
688 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
689 return;
690 }
691
692 // Hide the contact photo if we are in a video call and the incoming video surface is
693 // showing.
694 boolean showContactPhoto =
695 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
696
697 // DialerCall placed through a work phone account.
698 boolean hasWorkCallProperty = mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL);
699
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700700 MultimediaData multimediaData = null;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700701 if (mPrimary.getEnrichedCallSession() != null) {
702 multimediaData = mPrimary.getEnrichedCallSession().getMultimediaData();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700703 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800704
705 if (mPrimary.isConferenceCall()) {
706 LogUtil.v(
707 "CallCardPresenter.updatePrimaryDisplayInfo",
708 "update primary display info for conference call.");
709
710 mInCallScreen.setPrimary(
711 new PrimaryInfo(
712 null /* number */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700713 CallerInfoUtils.getConferenceString(
714 mContext, mPrimary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800715 false /* nameIsNumber */,
716 null /* location */,
717 null /* label */,
Eric Erfanian83b20212017-05-31 08:53:10 -0700718 null /* photo */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800719 ContactPhotoType.DEFAULT_PLACEHOLDER,
720 false /* isSipCall */,
721 showContactPhoto,
722 hasWorkCallProperty,
723 false /* isSpam */,
wangqiae6c8ec2017-09-28 17:39:40 -0700724 false /* isLocalContact */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800725 false /* answeringDisconnectsOngoingCall */,
726 shouldShowLocation(),
727 null /* contactInfoLookupKey */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700728 null /* enrichedCallMultimediaData */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700729 true /* showInCallButtonGrid */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700730 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800731 } else if (mPrimaryContactInfo != null) {
732 LogUtil.v(
733 "CallCardPresenter.updatePrimaryDisplayInfo",
734 "update primary display info for " + mPrimaryContactInfo);
735
736 String name = getNameForCall(mPrimaryContactInfo);
737 String number;
738
739 boolean isChildNumberShown = !TextUtils.isEmpty(mPrimary.getChildNumber());
740 boolean isForwardedNumberShown = !TextUtils.isEmpty(mPrimary.getLastForwardedNumber());
741 boolean isCallSubjectShown = shouldShowCallSubject(mPrimary);
742
743 if (isCallSubjectShown) {
744 number = null;
745 } else if (isChildNumberShown) {
746 number = mContext.getString(R.string.child_number, mPrimary.getChildNumber());
747 } else if (isForwardedNumberShown) {
748 // Use last forwarded number instead of second line, if present.
749 number = mPrimary.getLastForwardedNumber();
750 } else {
751 number = mPrimaryContactInfo.number;
752 }
753
754 boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700755
Eric Erfanianccca3152017-02-22 16:32:36 -0800756 // DialerCall with caller that is a work contact.
757 boolean isWorkContact = (mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
758 mInCallScreen.setPrimary(
759 new PrimaryInfo(
760 number,
Eric Erfanianc857f902017-05-15 14:05:33 -0700761 mPrimary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800762 nameIsNumber,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700763 shouldShowLocationAsLabel(nameIsNumber, mPrimaryContactInfo.shouldShowLocation)
764 ? mPrimaryContactInfo.location
765 : null,
Eric Erfanianccca3152017-02-22 16:32:36 -0800766 isChildNumberShown || isCallSubjectShown ? null : mPrimaryContactInfo.label,
767 mPrimaryContactInfo.photo,
768 mPrimaryContactInfo.photoType,
769 mPrimaryContactInfo.isSipCall,
770 showContactPhoto,
771 hasWorkCallProperty || isWorkContact,
772 mPrimary.isSpam(),
wangqiae6c8ec2017-09-28 17:39:40 -0700773 mPrimaryContactInfo.isLocalContact(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800774 mPrimary.answeringDisconnectsForegroundVideoCall(),
775 shouldShowLocation(),
776 mPrimaryContactInfo.lookupKey,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700777 multimediaData,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700778 true /* showInCallButtonGrid */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700779 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800780 } else {
781 // Clear the primary display info.
782 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
783 }
784
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700785 if (isInCallScreenReady) {
786 mInCallScreen.showLocationUi(getLocationFragment());
787 } else {
788 LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
789 }
790 }
791
Eric Erfaniand8046e52017-04-06 09:41:50 -0700792 private static boolean shouldShowLocationAsLabel(
793 boolean nameIsNumber, boolean shouldShowLocation) {
794 if (nameIsNumber) {
795 return true;
796 }
797 if (shouldShowLocation) {
798 return true;
799 }
800 return false;
801 }
802
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700803 private Fragment getLocationFragment() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700804 if (!shouldShowLocation()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700805 return null;
806 }
807 LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
808 return callLocation.getLocationFragment(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800809 }
810
811 private boolean shouldShowLocation() {
wangqi1d62ab22017-11-29 14:29:31 -0800812 if (!ConfigProviderBindings.get(mContext)
813 .getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
814 LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
815 return false;
816 }
817 if (!isPotentialEmergencyCall()) {
818 LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
819 return false;
820 }
821 if (!hasLocationPermission()) {
822 LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
823 return false;
824 }
825 if (isBatteryTooLowForEmergencyLocation()) {
826 LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
827 return false;
828 }
829 if (ActivityCompat.isInMultiWindowMode(mInCallScreen.getInCallScreenFragment().getActivity())) {
830 LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
831 return false;
832 }
833 if (mPrimary.isVideoCall()) {
834 LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
835 return false;
836 }
837 if (!callLocation.canGetLocation(mContext)) {
838 LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
839 return false;
840 }
841 return true;
842 }
843
844 private boolean isPotentialEmergencyCall() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800845 if (isOutgoingEmergencyCall(mPrimary)) {
846 LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
847 return true;
848 } else if (isIncomingEmergencyCall(mPrimary)) {
849 LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
850 return true;
851 } else if (isIncomingEmergencyCall(mSecondary)) {
852 LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
853 return true;
854 }
855 return false;
856 }
857
858 private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
859 return call != null && !call.isIncoming() && call.isEmergencyCall();
860 }
861
862 private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
863 return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
864 }
865
866 private boolean hasLocationPermission() {
867 return ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION)
868 == PackageManager.PERMISSION_GRANTED;
869 }
870
871 private boolean isBatteryTooLowForEmergencyLocation() {
872 Intent batteryStatus =
873 mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
874 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
875 if (status == BatteryManager.BATTERY_STATUS_CHARGING
876 || status == BatteryManager.BATTERY_STATUS_FULL) {
877 // Plugged in or full battery
878 return false;
879 }
880 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
881 int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
882 float batteryPercent = (100f * level) / scale;
883 long threshold =
884 ConfigProviderBindings.get(mContext)
885 .getLong(
886 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
887 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
888 LogUtil.i(
889 "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
890 "percent charged: " + batteryPercent + ", min required charge: " + threshold);
891 return batteryPercent < threshold;
892 }
893
894 private void updateSecondaryDisplayInfo() {
895 if (mInCallScreen == null) {
896 return;
897 }
898
899 if (mSecondary == null) {
900 // Clear the secondary display info.
901 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
902 return;
903 }
904
Eric Erfanian2ca43182017-08-31 06:57:16 -0700905 if (mSecondary.isMergeInProcess()) {
906 LogUtil.i(
907 "CallCardPresenter.updateSecondaryDisplayInfo",
908 "secondary call is merge in process, clearing info");
909 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
910 return;
911 }
912
Eric Erfanianccca3152017-02-22 16:32:36 -0800913 if (mSecondary.isConferenceCall()) {
914 mInCallScreen.setSecondary(
915 new SecondaryInfo(
916 true /* show */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700917 CallerInfoUtils.getConferenceString(
918 mContext, mSecondary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800919 false /* nameIsNumber */,
920 null /* label */,
921 mSecondary.getCallProviderLabel(),
922 true /* isConference */,
923 mSecondary.isVideoCall(),
924 mIsFullscreen));
925 } else if (mSecondaryContactInfo != null) {
926 LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + mSecondaryContactInfo);
927 String name = getNameForCall(mSecondaryContactInfo);
928 boolean nameIsNumber = name != null && name.equals(mSecondaryContactInfo.number);
929 mInCallScreen.setSecondary(
930 new SecondaryInfo(
931 true /* show */,
Eric Erfanianc857f902017-05-15 14:05:33 -0700932 mSecondary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800933 nameIsNumber,
934 mSecondaryContactInfo.label,
935 mSecondary.getCallProviderLabel(),
936 false /* isConference */,
937 mSecondary.isVideoCall(),
938 mIsFullscreen));
939 } else {
940 // Clear the secondary display info.
941 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
942 }
943 }
944
945 /** Returns the gateway number for any existing outgoing call. */
946 private String getGatewayNumber() {
947 if (hasOutgoingGatewayCall()) {
948 return DialerCall.getNumberFromHandle(mPrimary.getGatewayInfo().getGatewayAddress());
949 }
950 return null;
951 }
952
953 /**
954 * Returns the label (line of text above the number/name) for any given call. For example,
955 * "calling via [Account/Google Voice]" for outgoing calls.
956 */
957 private String getConnectionLabel() {
958 if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_PHONE_STATE)
959 != PackageManager.PERMISSION_GRANTED) {
960 return null;
961 }
962 StatusHints statusHints = mPrimary.getStatusHints();
963 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
964 return statusHints.getLabel().toString();
965 }
966
967 if (hasOutgoingGatewayCall() && getUi() != null) {
968 // Return the label for the gateway app on outgoing calls.
969 final PackageManager pm = mContext.getPackageManager();
970 try {
971 ApplicationInfo info =
972 pm.getApplicationInfo(mPrimary.getGatewayInfo().getGatewayProviderPackageName(), 0);
973 return pm.getApplicationLabel(info).toString();
974 } catch (PackageManager.NameNotFoundException e) {
975 LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
976 return null;
977 }
978 }
979 return mPrimary.getCallProviderLabel();
980 }
981
982 private Drawable getCallStateIcon() {
983 // Return connection icon if one exists.
984 StatusHints statusHints = mPrimary.getStatusHints();
985 if (statusHints != null && statusHints.getIcon() != null) {
986 Drawable icon = statusHints.getIcon().loadDrawable(mContext);
987 if (icon != null) {
988 return icon;
989 }
990 }
991
992 return null;
993 }
994
995 private boolean hasOutgoingGatewayCall() {
996 // We only display the gateway information while STATE_DIALING so return false for any other
997 // call state.
998 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
999 // is also called after a contact search completes (call is not present yet). Split the
1000 // UI update so it can receive independent updates.
1001 if (mPrimary == null) {
1002 return false;
1003 }
1004 return DialerCall.State.isDialing(mPrimary.getState())
1005 && mPrimary.getGatewayInfo() != null
1006 && !mPrimary.getGatewayInfo().isEmpty();
1007 }
1008
1009 /** Gets the name to display for the call. */
Eric Erfanian2ca43182017-08-31 06:57:16 -07001010 private String getNameForCall(ContactCacheEntry contactInfo) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001011 String preferredName =
1012 ContactDisplayUtils.getPreferredDisplayName(
1013 contactInfo.namePrimary, contactInfo.nameAlternative, mContactsPreferences);
1014 if (TextUtils.isEmpty(preferredName)) {
1015 return contactInfo.number;
1016 }
1017 return preferredName;
1018 }
1019
Eric Erfanianccca3152017-02-22 16:32:36 -08001020 @Override
1021 public void onSecondaryInfoClicked() {
1022 if (mSecondary == null) {
1023 LogUtil.e(
1024 "CallCardPresenter.onSecondaryInfoClicked",
1025 "secondary info clicked but no secondary call.");
1026 return;
1027 }
1028
Eric Erfanian2ca43182017-08-31 06:57:16 -07001029 Logger.get(mContext)
1030 .logCallImpression(
1031 DialerImpression.Type.IN_CALL_SWAP_SECONDARY_BUTTON_PRESSED,
1032 mPrimary.getUniqueCallId(),
1033 mPrimary.getTimeAddedMs());
Eric Erfanianccca3152017-02-22 16:32:36 -08001034 LogUtil.i(
1035 "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + mSecondary);
1036 mSecondary.unhold();
1037 }
1038
1039 @Override
1040 public void onEndCallClicked() {
1041 LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + mPrimary);
1042 if (mPrimary != null) {
1043 mPrimary.disconnect();
1044 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001045 PostCall.onDisconnectPressed(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -08001046 }
1047
1048 /**
1049 * Handles a change to the fullscreen mode of the in-call UI.
1050 *
1051 * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
1052 */
1053 @Override
1054 public void onFullscreenModeChanged(boolean isFullscreenMode) {
1055 mIsFullscreen = isFullscreenMode;
1056 if (mInCallScreen == null) {
1057 return;
1058 }
1059 maybeShowManageConferenceCallButton();
1060 }
1061
1062 private boolean isPrimaryCallActive() {
1063 return mPrimary != null && mPrimary.getState() == DialerCall.State.ACTIVE;
1064 }
1065
Eric Erfanianccca3152017-02-22 16:32:36 -08001066 private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
1067 if (primary == null) {
1068 return false;
1069 }
1070 if ((!DialerCall.State.isConnectingOrConnected(callState)
1071 && callState != DialerCall.State.DISCONNECTING
1072 && callState != DialerCall.State.DISCONNECTED)
1073 || callState == DialerCall.State.INCOMING) {
1074 return false;
1075 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001076 if (mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001077 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001078 return false;
1079 }
1080 return true;
1081 }
1082
1083 @Override
1084 public void onInCallScreenResumed() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001085 updatePrimaryDisplayInfo();
1086
Eric Erfanianccca3152017-02-22 16:32:36 -08001087 if (shouldSendAccessibilityEvent) {
1088 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1089 }
1090 }
1091
Eric Erfaniand8046e52017-04-06 09:41:50 -07001092 @Override
Eric Erfanian2ca43182017-08-31 06:57:16 -07001093 public void onInCallScreenPaused() {}
Eric Erfaniand8046e52017-04-06 09:41:50 -07001094
Eric Erfanianccca3152017-02-22 16:32:36 -08001095 static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
1096 AccessibilityManager am =
1097 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
1098 if (!am.isEnabled()) {
1099 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
1100 return false;
1101 }
1102 if (inCallScreen == null) {
1103 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
1104 return false;
1105 }
1106 Fragment fragment = inCallScreen.getInCallScreenFragment();
1107 if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
1108 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
1109 return false;
1110 }
1111
1112 DisplayManager displayManager =
1113 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1114 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1115 boolean screenIsOn = display.getState() == Display.STATE_ON;
1116 LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
1117 if (!screenIsOn) {
1118 return false;
1119 }
1120
1121 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1122 inCallScreen.dispatchPopulateAccessibilityEvent(event);
1123 View view = inCallScreen.getInCallScreenFragment().getView();
1124 view.getParent().requestSendAccessibilityEvent(view, event);
1125 return true;
1126 }
1127
1128 private void maybeSendAccessibilityEvent(
1129 InCallState oldState, final InCallState newState, boolean primaryChanged) {
1130 shouldSendAccessibilityEvent = false;
1131 if (mContext == null) {
1132 return;
1133 }
1134 final AccessibilityManager am =
1135 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
1136 if (!am.isEnabled()) {
1137 return;
1138 }
1139 // Announce the current call if it's new incoming/outgoing call or primary call is changed
1140 // due to switching calls between two ongoing calls (one is on hold).
1141 if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
1142 || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
1143 || primaryChanged) {
1144 LogUtil.i(
1145 "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
1146 shouldSendAccessibilityEvent = true;
1147 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1148 }
1149 }
1150
1151 /**
1152 * Determines whether the call subject should be visible on the UI. For the call subject to be
1153 * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
1154 *
1155 * @param call The call.
1156 * @return {@code true} if the subject should be shown, {@code false} otherwise.
1157 */
1158 private boolean shouldShowCallSubject(DialerCall call) {
1159 if (call == null) {
1160 return false;
1161 }
1162
1163 boolean isIncomingOrWaiting =
1164 mPrimary.getState() == DialerCall.State.INCOMING
1165 || mPrimary.getState() == DialerCall.State.CALL_WAITING;
1166 return isIncomingOrWaiting
1167 && !TextUtils.isEmpty(call.getCallSubject())
1168 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
1169 && call.isCallSubjectSupported();
1170 }
1171
1172 /**
1173 * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
1174 * call with a subject.
1175 *
1176 * @param call The call
1177 * @return {@code true} if the toast should be shown, {@code false} otherwise.
1178 */
1179 private boolean shouldShowNoteSentToast(DialerCall call) {
1180 return call != null
1181 && hasCallSubject(call)
1182 && (call.getState() == DialerCall.State.DIALING
1183 || call.getState() == DialerCall.State.CONNECTING);
1184 }
1185
1186 private InCallScreen getUi() {
1187 return mInCallScreen;
1188 }
1189
1190 public static class ContactLookupCallback implements ContactInfoCacheCallback {
1191
1192 private final WeakReference<CallCardPresenter> mCallCardPresenter;
1193 private final boolean mIsPrimary;
1194
1195 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
1196 mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
1197 mIsPrimary = isPrimary;
1198 }
1199
1200 @Override
1201 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
1202 CallCardPresenter presenter = mCallCardPresenter.get();
1203 if (presenter != null) {
1204 presenter.onContactInfoComplete(callId, entry, mIsPrimary);
1205 }
1206 }
1207
1208 @Override
1209 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
1210 CallCardPresenter presenter = mCallCardPresenter.get();
1211 if (presenter != null) {
1212 presenter.onImageLoadComplete(callId, entry);
1213 }
1214 }
1215 }
1216}