blob: 668692d718ba646332f600b2412533f6d7fbdabf [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 Erfaniand5e47f62017-03-15 14:41:07 -070031import android.support.annotation.NonNull;
Eric Erfanianccca3152017-02-22 16:32:36 -080032import android.support.annotation.Nullable;
33import android.support.v4.app.Fragment;
34import android.support.v4.content.ContextCompat;
35import android.telecom.Call.Details;
36import android.telecom.StatusHints;
37import android.telecom.TelecomManager;
38import android.text.TextUtils;
39import android.view.Display;
40import android.view.View;
41import android.view.accessibility.AccessibilityEvent;
42import android.view.accessibility.AccessibilityManager;
43import com.android.contacts.common.ContactsUtils;
44import com.android.contacts.common.preference.ContactsPreferences;
45import com.android.contacts.common.util.ContactDisplayUtils;
46import com.android.dialer.common.Assert;
47import com.android.dialer.common.ConfigProviderBindings;
48import com.android.dialer.common.LogUtil;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070049import com.android.dialer.compat.ActivityCompat;
50import com.android.dialer.enrichedcall.EnrichedCallComponent;
Eric Erfanianccca3152017-02-22 16:32:36 -080051import com.android.dialer.enrichedcall.EnrichedCallManager;
52import com.android.dialer.enrichedcall.Session;
53import com.android.dialer.multimedia.MultimediaData;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070054import com.android.dialer.oem.MotorolaUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080055import com.android.incallui.ContactInfoCache.ContactCacheEntry;
56import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
57import com.android.incallui.InCallPresenter.InCallDetailsListener;
58import com.android.incallui.InCallPresenter.InCallEventListener;
59import com.android.incallui.InCallPresenter.InCallState;
60import com.android.incallui.InCallPresenter.InCallStateListener;
61import com.android.incallui.InCallPresenter.IncomingCallListener;
62import com.android.incallui.call.CallList;
63import com.android.incallui.call.DialerCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080064import com.android.incallui.call.DialerCallListener;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070065import com.android.incallui.calllocation.CallLocation;
66import com.android.incallui.calllocation.CallLocationComponent;
Eric Erfanianccca3152017-02-22 16:32:36 -080067import com.android.incallui.incall.protocol.ContactPhotoType;
68import com.android.incallui.incall.protocol.InCallScreen;
69import com.android.incallui.incall.protocol.InCallScreenDelegate;
70import com.android.incallui.incall.protocol.PrimaryCallState;
71import com.android.incallui.incall.protocol.PrimaryInfo;
72import com.android.incallui.incall.protocol.SecondaryInfo;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070073import com.android.incallui.videotech.VideoTech;
Eric Erfanianccca3152017-02-22 16:32:36 -080074import java.lang.ref.WeakReference;
75
76/**
77 * Controller for the Call Card Fragment. This class listens for changes to InCallState and passes
78 * it along to the fragment.
79 */
80public class CallCardPresenter
81 implements InCallStateListener,
82 IncomingCallListener,
83 InCallDetailsListener,
84 InCallEventListener,
85 InCallScreenDelegate,
86 DialerCallListener,
87 EnrichedCallManager.StateChangedListener {
88
89 /**
90 * Amount of time to wait before sending an announcement via the accessibility manager. When the
91 * call state changes to an outgoing or incoming state for the first time, the UI can often be
92 * changing due to call updates or contact lookup. This allows the UI to settle to a stable state
93 * to ensure that the correct information is announced.
94 */
95 private static final long ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS = 500;
96
97 /** Flag to allow the user's current location to be shown during emergency calls. */
98 private static final String CONFIG_ENABLE_EMERGENCY_LOCATION = "config_enable_emergency_location";
99
100 private static final boolean CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT = true;
101
102 /**
103 * Make it possible to not get location during an emergency call if the battery is too low, since
104 * doing so could trigger gps and thus potentially cause the phone to die in the middle of the
105 * call.
106 */
107 private static final String CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION =
108 "min_battery_percent_for_emergency_location";
109
110 private static final long CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT = 10;
111
112 private final Context mContext;
113 private final Handler handler = new Handler();
114
115 private DialerCall mPrimary;
116 private DialerCall mSecondary;
117 private ContactCacheEntry mPrimaryContactInfo;
118 private ContactCacheEntry mSecondaryContactInfo;
119 @Nullable private ContactsPreferences mContactsPreferences;
120 private boolean mIsFullscreen = false;
121 private InCallScreen mInCallScreen;
122 private boolean isInCallScreenReady;
123 private boolean shouldSendAccessibilityEvent;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700124
125 @NonNull private final CallLocation callLocation;
Eric Erfanianccca3152017-02-22 16:32:36 -0800126 private final Runnable sendAccessibilityEventRunnable =
127 new Runnable() {
128 @Override
129 public void run() {
130 shouldSendAccessibilityEvent = !sendAccessibilityEvent(mContext, getUi());
131 LogUtil.i(
132 "CallCardPresenter.sendAccessibilityEventRunnable",
133 "still should send: %b",
134 shouldSendAccessibilityEvent);
135 if (!shouldSendAccessibilityEvent) {
136 handler.removeCallbacks(this);
137 }
138 }
139 };
140
141 public CallCardPresenter(Context context) {
142 LogUtil.i("CallCardController.constructor", null);
143 mContext = Assert.isNotNull(context).getApplicationContext();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700144 callLocation = CallLocationComponent.get(mContext).getCallLocation();
Eric Erfanianccca3152017-02-22 16:32:36 -0800145 }
146
147 private static boolean hasCallSubject(DialerCall call) {
148 return !TextUtils.isEmpty(call.getCallSubject());
149 }
150
151 @Override
152 public void onInCallScreenDelegateInit(InCallScreen inCallScreen) {
153 Assert.isNotNull(inCallScreen);
154 mInCallScreen = inCallScreen;
155 mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext);
156
157 // Call may be null if disconnect happened already.
158 DialerCall call = CallList.getInstance().getFirstCall();
159 if (call != null) {
160 mPrimary = call;
161 if (shouldShowNoteSentToast(mPrimary)) {
162 mInCallScreen.showNoteSentToast();
163 }
164 call.addListener(this);
165
166 // start processing lookups right away.
167 if (!call.isConferenceCall()) {
168 startContactInfoSearch(call, true, call.getState() == DialerCall.State.INCOMING);
169 } else {
170 updateContactEntry(null, true);
171 }
172 }
173
174 onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
175 }
176
177 @Override
178 public void onInCallScreenReady() {
179 LogUtil.i("CallCardController.onInCallScreenReady", null);
180 Assert.checkState(!isInCallScreenReady);
181 if (mContactsPreferences != null) {
182 mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
183 }
184
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700185 EnrichedCallComponent.get(mContext).getEnrichedCallManager().registerStateChangedListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800186
187 // Contact search may have completed before ui is ready.
188 if (mPrimaryContactInfo != null) {
189 updatePrimaryDisplayInfo();
190 }
191
192 // Register for call state changes last
193 InCallPresenter.getInstance().addListener(this);
194 InCallPresenter.getInstance().addIncomingCallListener(this);
195 InCallPresenter.getInstance().addDetailsListener(this);
196 InCallPresenter.getInstance().addInCallEventListener(this);
197 isInCallScreenReady = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700198
199 // Showing the location may have been skipped if the UI wasn't ready during previous layout.
200 if (shouldShowLocation()) {
201 updatePrimaryDisplayInfo();
202 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800203 }
204
205 @Override
206 public void onInCallScreenUnready() {
207 LogUtil.i("CallCardController.onInCallScreenUnready", null);
208 Assert.checkState(isInCallScreenReady);
209
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700210 EnrichedCallComponent.get(mContext)
211 .getEnrichedCallManager()
Eric Erfanianccca3152017-02-22 16:32:36 -0800212 .unregisterStateChangedListener(this);
213 // stop getting call state changes
214 InCallPresenter.getInstance().removeListener(this);
215 InCallPresenter.getInstance().removeIncomingCallListener(this);
216 InCallPresenter.getInstance().removeDetailsListener(this);
217 InCallPresenter.getInstance().removeInCallEventListener(this);
218 if (mPrimary != null) {
219 mPrimary.removeListener(this);
220 }
221
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700222 callLocation.close();
223
Eric Erfanianccca3152017-02-22 16:32:36 -0800224 mPrimary = null;
225 mPrimaryContactInfo = null;
226 mSecondaryContactInfo = null;
227 isInCallScreenReady = false;
228 }
229
230 @Override
231 public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
232 // same logic should happen as with onStateChange()
233 onStateChange(oldState, newState, CallList.getInstance());
234 }
235
236 @Override
237 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
238 LogUtil.v("CallCardPresenter.onStateChange", "" + newState);
239 if (mInCallScreen == null) {
240 return;
241 }
242
243 DialerCall primary = null;
244 DialerCall secondary = null;
245
246 if (newState == InCallState.INCOMING) {
247 primary = callList.getIncomingCall();
248 } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
249 primary = callList.getOutgoingCall();
250 if (primary == null) {
251 primary = callList.getPendingOutgoingCall();
252 }
253
254 // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
255 // highest priority call to display as the secondary call.
256 secondary = getCallToDisplay(callList, null, true);
257 } else if (newState == InCallState.INCALL) {
258 primary = getCallToDisplay(callList, null, false);
259 secondary = getCallToDisplay(callList, primary, true);
260 }
261
262 LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
263 LogUtil.v("CallCardPresenter.onStateChange", "secondary call: " + secondary);
264
265 final boolean primaryChanged =
266 !(DialerCall.areSame(mPrimary, primary) && DialerCall.areSameNumber(mPrimary, primary));
267 final boolean secondaryChanged =
268 !(DialerCall.areSame(mSecondary, secondary)
269 && DialerCall.areSameNumber(mSecondary, secondary));
270
271 mSecondary = secondary;
272 DialerCall previousPrimary = mPrimary;
273 mPrimary = primary;
274
275 if (mPrimary != null) {
276 InCallPresenter.getInstance().onForegroundCallChanged(mPrimary);
277 mInCallScreen.updateInCallScreenColors();
278 }
279
280 if (primaryChanged && shouldShowNoteSentToast(primary)) {
281 mInCallScreen.showNoteSentToast();
282 }
283
284 // Refresh primary call information if either:
285 // 1. Primary call changed.
286 // 2. The call's ability to manage conference has changed.
287 if (shouldRefreshPrimaryInfo(primaryChanged)) {
288 // primary call has changed
289 if (previousPrimary != null) {
290 previousPrimary.removeListener(this);
291 }
292 mPrimary.addListener(this);
293
294 mPrimaryContactInfo =
295 ContactInfoCache.buildCacheEntryFromCall(
296 mContext, mPrimary, mPrimary.getState() == DialerCall.State.INCOMING);
297 updatePrimaryDisplayInfo();
298 maybeStartSearch(mPrimary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800299 }
300
301 if (previousPrimary != null && mPrimary == null) {
302 previousPrimary.removeListener(this);
303 }
304
305 if (mSecondary == null) {
306 // Secondary call may have ended. Update the ui.
307 mSecondaryContactInfo = null;
308 updateSecondaryDisplayInfo();
309 } else if (secondaryChanged) {
310 // secondary call has changed
311 mSecondaryContactInfo =
312 ContactInfoCache.buildCacheEntryFromCall(
313 mContext, mSecondary, mSecondary.getState() == DialerCall.State.INCOMING);
314 updateSecondaryDisplayInfo();
315 maybeStartSearch(mSecondary, false);
Eric Erfanianccca3152017-02-22 16:32:36 -0800316 }
317
318 // Set the call state
319 int callState = DialerCall.State.IDLE;
320 if (mPrimary != null) {
321 callState = mPrimary.getState();
322 updatePrimaryCallState();
323 } else {
324 getUi().setCallState(PrimaryCallState.createEmptyPrimaryCallState());
325 }
326
327 maybeShowManageConferenceCallButton();
328
329 // Hide the end call button instantly if we're receiving an incoming call.
330 getUi()
331 .setEndCallButtonEnabled(
332 shouldShowEndCallButton(mPrimary, callState),
333 callState != DialerCall.State.INCOMING /* animate */);
334
335 maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
336 }
337
338 @Override
339 public void onDetailsChanged(DialerCall call, Details details) {
340 updatePrimaryCallState();
341
342 if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
343 != details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
344 maybeShowManageConferenceCallButton();
345 }
346 }
347
348 @Override
349 public void onDialerCallDisconnect() {}
350
351 @Override
352 public void onDialerCallUpdate() {
353 // No-op; specific call updates handled elsewhere.
354 }
355
356 @Override
357 public void onWiFiToLteHandover() {}
358
359 @Override
360 public void onHandoverToWifiFailure() {}
361
362 /** Handles a change to the child number by refreshing the primary call info. */
363 @Override
364 public void onDialerCallChildNumberChange() {
365 LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
366
367 if (mPrimary == null) {
368 return;
369 }
370 updatePrimaryDisplayInfo();
371 }
372
373 /** Handles a change to the last forwarding number by refreshing the primary call info. */
374 @Override
375 public void onDialerCallLastForwardedNumberChange() {
376 LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
377
378 if (mPrimary == null) {
379 return;
380 }
381 updatePrimaryDisplayInfo();
382 updatePrimaryCallState();
383 }
384
385 @Override
386 public void onDialerCallUpgradeToVideo() {}
387
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700388 /** Handles a change to the session modification state for a call. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800389 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700390 public void onDialerCallSessionModificationStateChange() {
391 LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
Eric Erfanianccca3152017-02-22 16:32:36 -0800392
393 if (mPrimary == null) {
394 return;
395 }
396 getUi()
397 .setEndCallButtonEnabled(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700398 mPrimary.getVideoTech().getSessionModificationState()
399 != VideoTech.SESSION_MODIFICATION_STATE_RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800400 true /* shouldAnimate */);
401 updatePrimaryCallState();
402 }
403
404 @Override
405 public void onEnrichedCallStateChanged() {
406 LogUtil.enterBlock("CallCardPresenter.onEnrichedCallStateChanged");
407 updatePrimaryDisplayInfo();
408 }
409
410 private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
411 if (mPrimary == null) {
412 return false;
413 }
414 return primaryChanged
415 || mInCallScreen.isManageConferenceVisible() != shouldShowManageConference();
416 }
417
418 private void updatePrimaryCallState() {
419 if (getUi() != null && mPrimary != null) {
420 boolean isWorkCall =
421 mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL)
422 || (mPrimaryContactInfo != null
423 && mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
424 boolean isHdAudioCall =
425 isPrimaryCallActive() && mPrimary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700426 boolean isAttemptingHdAudioCall =
427 !isHdAudioCall
428 && !mPrimary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
429 && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(mContext);
430
431 boolean isBusiness = mPrimaryContactInfo != null && mPrimaryContactInfo.isBusiness;
432
Eric Erfanianccca3152017-02-22 16:32:36 -0800433 // Check for video state change and update the visibility of the contact photo. The contact
434 // photo is hidden when the incoming video surface is shown.
435 // The contact photo visibility can also change in setPrimary().
436 boolean shouldShowContactPhoto =
437 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
438 getUi()
439 .setCallState(
440 new PrimaryCallState(
441 mPrimary.getState(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700442 mPrimary.isVideoCall(),
443 mPrimary.getVideoTech().getSessionModificationState(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800444 mPrimary.getDisconnectCause(),
445 getConnectionLabel(),
446 getCallStateIcon(),
447 getGatewayNumber(),
448 shouldShowCallSubject(mPrimary) ? mPrimary.getCallSubject() : null,
449 mPrimary.getCallbackNumber(),
450 mPrimary.hasProperty(Details.PROPERTY_WIFI),
451 mPrimary.isConferenceCall(),
452 isWorkCall,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700453 isAttemptingHdAudioCall,
Eric Erfanianccca3152017-02-22 16:32:36 -0800454 isHdAudioCall,
455 !TextUtils.isEmpty(mPrimary.getLastForwardedNumber()),
456 shouldShowContactPhoto,
457 mPrimary.getConnectTimeMillis(),
458 CallerInfoUtils.isVoiceMailNumber(mContext, mPrimary),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700459 mPrimary.isRemotelyHeld(),
460 isBusiness));
Eric Erfanianccca3152017-02-22 16:32:36 -0800461
462 InCallActivity activity =
463 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
464 if (activity != null) {
465 activity.onPrimaryCallStateChanged();
466 }
467 }
468 }
469
470 /** Only show the conference call button if we can manage the conference. */
471 private void maybeShowManageConferenceCallButton() {
472 getUi().showManageConferenceCallButton(shouldShowManageConference());
473 }
474
475 /**
476 * Determines if the manage conference button should be visible, based on the current primary
477 * call.
478 *
479 * @return {@code True} if the manage conference button should be visible.
480 */
481 private boolean shouldShowManageConference() {
482 if (mPrimary == null) {
483 return false;
484 }
485
486 return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)
487 && !mIsFullscreen;
488 }
489
490 @Override
491 public void onCallStateButtonClicked() {
492 Intent broadcastIntent = Bindings.get(mContext).getCallStateButtonBroadcastIntent(mContext);
493 if (broadcastIntent != null) {
494 LogUtil.v(
495 "CallCardPresenter.onCallStateButtonClicked",
496 "sending call state button broadcast: " + broadcastIntent);
497 mContext.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
498 }
499 }
500
501 @Override
502 public void onManageConferenceClicked() {
503 InCallActivity activity =
504 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
505 activity.showConferenceFragment(true);
506 }
507
508 @Override
509 public void onShrinkAnimationComplete() {
510 InCallPresenter.getInstance().onShrinkAnimationComplete();
511 }
512
513 @Override
514 public Drawable getDefaultContactPhotoDrawable() {
515 return ContactInfoCache.getInstance(mContext).getDefaultContactPhotoDrawable();
516 }
517
518 private void maybeStartSearch(DialerCall call, boolean isPrimary) {
519 // no need to start search for conference calls which show generic info.
520 if (call != null && !call.isConferenceCall()) {
521 startContactInfoSearch(call, isPrimary, call.getState() == DialerCall.State.INCOMING);
522 }
523 }
524
Eric Erfanianccca3152017-02-22 16:32:36 -0800525 /** Starts a query for more contact data for the save primary and secondary calls. */
526 private void startContactInfoSearch(
527 final DialerCall call, final boolean isPrimary, boolean isIncoming) {
528 final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
529
530 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
531 }
532
533 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
534 final boolean entryMatchesExistingCall =
535 (isPrimary && mPrimary != null && TextUtils.equals(callId, mPrimary.getId()))
536 || (!isPrimary && mSecondary != null && TextUtils.equals(callId, mSecondary.getId()));
537 if (entryMatchesExistingCall) {
538 updateContactEntry(entry, isPrimary);
539 } else {
540 LogUtil.e(
541 "CallCardPresenter.onContactInfoComplete",
542 "dropping stale contact lookup info for " + callId);
543 }
544
545 final DialerCall call = CallList.getInstance().getCallById(callId);
546 if (call != null) {
547 call.getLogState().contactLookupResult = entry.contactLookupResult;
548 }
549 if (entry.contactUri != null) {
550 CallerInfoUtils.sendViewNotification(mContext, entry.contactUri);
551 }
552 }
553
554 private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
555 if (getUi() == null) {
556 return;
557 }
558
559 if (entry.photo != null) {
560 if (mPrimary != null && callId.equals(mPrimary.getId())) {
561 updateContactEntry(entry, true /* isPrimary */);
562 } else if (mSecondary != null && callId.equals(mSecondary.getId())) {
563 updateContactEntry(entry, false /* isPrimary */);
564 }
565 }
566 }
567
568 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
569 if (isPrimary) {
570 mPrimaryContactInfo = entry;
571 updatePrimaryDisplayInfo();
572 } else {
573 mSecondaryContactInfo = entry;
574 updateSecondaryDisplayInfo();
575 }
576 }
577
578 /**
579 * Get the highest priority call to display. Goes through the calls and chooses which to return
580 * based on priority of which type of call to display to the user. Callers can use the "ignore"
581 * feature to get the second best call by passing a previously found primary call as ignore.
582 *
583 * @param ignore A call to ignore if found.
584 */
585 private DialerCall getCallToDisplay(
586 CallList callList, DialerCall ignore, boolean skipDisconnected) {
587 // Active calls come second. An active call always gets precedent.
588 DialerCall retval = callList.getActiveCall();
589 if (retval != null && retval != ignore) {
590 return retval;
591 }
592
593 // Sometimes there is intemediate state that two calls are in active even one is about
594 // to be on hold.
595 retval = callList.getSecondActiveCall();
596 if (retval != null && retval != ignore) {
597 return retval;
598 }
599
600 // Disconnected calls get primary position if there are no active calls
601 // to let user know quickly what call has disconnected. Disconnected
602 // calls are very short lived.
603 if (!skipDisconnected) {
604 retval = callList.getDisconnectingCall();
605 if (retval != null && retval != ignore) {
606 return retval;
607 }
608 retval = callList.getDisconnectedCall();
609 if (retval != null && retval != ignore) {
610 return retval;
611 }
612 }
613
614 // Then we go to background call (calls on hold)
615 retval = callList.getBackgroundCall();
616 if (retval != null && retval != ignore) {
617 return retval;
618 }
619
620 // Lastly, we go to a second background call.
621 retval = callList.getSecondBackgroundCall();
622
623 return retval;
624 }
625
626 private void updatePrimaryDisplayInfo() {
627 if (mInCallScreen == null) {
628 // TODO: May also occur if search result comes back after ui is destroyed. Look into
629 // removing that case completely.
630 LogUtil.v(
631 "CallCardPresenter.updatePrimaryDisplayInfo",
632 "updatePrimaryDisplayInfo called but ui is null!");
633 return;
634 }
635
636 if (mPrimary == null) {
637 // Clear the primary display info.
638 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
639 return;
640 }
641
642 // Hide the contact photo if we are in a video call and the incoming video surface is
643 // showing.
644 boolean showContactPhoto =
645 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
646
647 // DialerCall placed through a work phone account.
648 boolean hasWorkCallProperty = mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL);
649
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700650 MultimediaData multimediaData = null;
651 if (mPrimary.getNumber() != null) {
652 Session enrichedCallSession =
653 EnrichedCallComponent.get(mContext)
654 .getEnrichedCallManager()
655 .getSession(mPrimary.getUniqueCallId(), mPrimary.getNumber());
656 if (enrichedCallSession != null) {
657 enrichedCallSession.setUniqueDialerCallId(mPrimary.getUniqueCallId());
658 multimediaData = enrichedCallSession.getMultimediaData();
659 }
660 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800661
662 if (mPrimary.isConferenceCall()) {
663 LogUtil.v(
664 "CallCardPresenter.updatePrimaryDisplayInfo",
665 "update primary display info for conference call.");
666
667 mInCallScreen.setPrimary(
668 new PrimaryInfo(
669 null /* number */,
670 getConferenceString(mPrimary),
671 false /* nameIsNumber */,
672 null /* location */,
673 null /* label */,
674 getConferencePhoto(mPrimary),
675 ContactPhotoType.DEFAULT_PLACEHOLDER,
676 false /* isSipCall */,
677 showContactPhoto,
678 hasWorkCallProperty,
679 false /* isSpam */,
680 false /* answeringDisconnectsOngoingCall */,
681 shouldShowLocation(),
682 null /* contactInfoLookupKey */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700683 null /* enrichedCallMultimediaData */,
684 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800685 } else if (mPrimaryContactInfo != null) {
686 LogUtil.v(
687 "CallCardPresenter.updatePrimaryDisplayInfo",
688 "update primary display info for " + mPrimaryContactInfo);
689
690 String name = getNameForCall(mPrimaryContactInfo);
691 String number;
692
693 boolean isChildNumberShown = !TextUtils.isEmpty(mPrimary.getChildNumber());
694 boolean isForwardedNumberShown = !TextUtils.isEmpty(mPrimary.getLastForwardedNumber());
695 boolean isCallSubjectShown = shouldShowCallSubject(mPrimary);
696
697 if (isCallSubjectShown) {
698 number = null;
699 } else if (isChildNumberShown) {
700 number = mContext.getString(R.string.child_number, mPrimary.getChildNumber());
701 } else if (isForwardedNumberShown) {
702 // Use last forwarded number instead of second line, if present.
703 number = mPrimary.getLastForwardedNumber();
704 } else {
705 number = mPrimaryContactInfo.number;
706 }
707
708 boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700709
Eric Erfanianccca3152017-02-22 16:32:36 -0800710 // DialerCall with caller that is a work contact.
711 boolean isWorkContact = (mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
712 mInCallScreen.setPrimary(
713 new PrimaryInfo(
714 number,
715 name,
716 nameIsNumber,
717 mPrimaryContactInfo.location,
718 isChildNumberShown || isCallSubjectShown ? null : mPrimaryContactInfo.label,
719 mPrimaryContactInfo.photo,
720 mPrimaryContactInfo.photoType,
721 mPrimaryContactInfo.isSipCall,
722 showContactPhoto,
723 hasWorkCallProperty || isWorkContact,
724 mPrimary.isSpam(),
725 mPrimary.answeringDisconnectsForegroundVideoCall(),
726 shouldShowLocation(),
727 mPrimaryContactInfo.lookupKey,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700728 multimediaData,
729 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800730 } else {
731 // Clear the primary display info.
732 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
733 }
734
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700735 if (isInCallScreenReady) {
736 mInCallScreen.showLocationUi(getLocationFragment());
737 } else {
738 LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
739 }
740 }
741
742 private Fragment getLocationFragment() {
743 if (!ConfigProviderBindings.get(mContext)
744 .getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
745 LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
746 return null;
747 }
748 if (!shouldShowLocation()) {
749 LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
750 return null;
751 }
752 if (!hasLocationPermission()) {
753 LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
754 return null;
755 }
756 if (isBatteryTooLowForEmergencyLocation()) {
757 LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
758 return null;
759 }
760 if (ActivityCompat.isInMultiWindowMode(mInCallScreen.getInCallScreenFragment().getActivity())) {
761 LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
762 return null;
763 }
764 if (mPrimary.isVideoCall()) {
765 LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
766 return null;
767 }
768 if (!callLocation.canGetLocation(mContext)) {
769 LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
770 return null;
771 }
772 LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
773 return callLocation.getLocationFragment(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800774 }
775
776 private boolean shouldShowLocation() {
777 if (isOutgoingEmergencyCall(mPrimary)) {
778 LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
779 return true;
780 } else if (isIncomingEmergencyCall(mPrimary)) {
781 LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
782 return true;
783 } else if (isIncomingEmergencyCall(mSecondary)) {
784 LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
785 return true;
786 }
787 return false;
788 }
789
790 private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
791 return call != null && !call.isIncoming() && call.isEmergencyCall();
792 }
793
794 private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
795 return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
796 }
797
798 private boolean hasLocationPermission() {
799 return ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION)
800 == PackageManager.PERMISSION_GRANTED;
801 }
802
803 private boolean isBatteryTooLowForEmergencyLocation() {
804 Intent batteryStatus =
805 mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
806 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
807 if (status == BatteryManager.BATTERY_STATUS_CHARGING
808 || status == BatteryManager.BATTERY_STATUS_FULL) {
809 // Plugged in or full battery
810 return false;
811 }
812 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
813 int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
814 float batteryPercent = (100f * level) / scale;
815 long threshold =
816 ConfigProviderBindings.get(mContext)
817 .getLong(
818 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
819 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
820 LogUtil.i(
821 "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
822 "percent charged: " + batteryPercent + ", min required charge: " + threshold);
823 return batteryPercent < threshold;
824 }
825
826 private void updateSecondaryDisplayInfo() {
827 if (mInCallScreen == null) {
828 return;
829 }
830
831 if (mSecondary == null) {
832 // Clear the secondary display info.
833 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
834 return;
835 }
836
837 if (mSecondary.isConferenceCall()) {
838 mInCallScreen.setSecondary(
839 new SecondaryInfo(
840 true /* show */,
841 getConferenceString(mSecondary),
842 false /* nameIsNumber */,
843 null /* label */,
844 mSecondary.getCallProviderLabel(),
845 true /* isConference */,
846 mSecondary.isVideoCall(),
847 mIsFullscreen));
848 } else if (mSecondaryContactInfo != null) {
849 LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + mSecondaryContactInfo);
850 String name = getNameForCall(mSecondaryContactInfo);
851 boolean nameIsNumber = name != null && name.equals(mSecondaryContactInfo.number);
852 mInCallScreen.setSecondary(
853 new SecondaryInfo(
854 true /* show */,
855 name,
856 nameIsNumber,
857 mSecondaryContactInfo.label,
858 mSecondary.getCallProviderLabel(),
859 false /* isConference */,
860 mSecondary.isVideoCall(),
861 mIsFullscreen));
862 } else {
863 // Clear the secondary display info.
864 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
865 }
866 }
867
868 /** Returns the gateway number for any existing outgoing call. */
869 private String getGatewayNumber() {
870 if (hasOutgoingGatewayCall()) {
871 return DialerCall.getNumberFromHandle(mPrimary.getGatewayInfo().getGatewayAddress());
872 }
873 return null;
874 }
875
876 /**
877 * Returns the label (line of text above the number/name) for any given call. For example,
878 * "calling via [Account/Google Voice]" for outgoing calls.
879 */
880 private String getConnectionLabel() {
881 if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_PHONE_STATE)
882 != PackageManager.PERMISSION_GRANTED) {
883 return null;
884 }
885 StatusHints statusHints = mPrimary.getStatusHints();
886 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
887 return statusHints.getLabel().toString();
888 }
889
890 if (hasOutgoingGatewayCall() && getUi() != null) {
891 // Return the label for the gateway app on outgoing calls.
892 final PackageManager pm = mContext.getPackageManager();
893 try {
894 ApplicationInfo info =
895 pm.getApplicationInfo(mPrimary.getGatewayInfo().getGatewayProviderPackageName(), 0);
896 return pm.getApplicationLabel(info).toString();
897 } catch (PackageManager.NameNotFoundException e) {
898 LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
899 return null;
900 }
901 }
902 return mPrimary.getCallProviderLabel();
903 }
904
905 private Drawable getCallStateIcon() {
906 // Return connection icon if one exists.
907 StatusHints statusHints = mPrimary.getStatusHints();
908 if (statusHints != null && statusHints.getIcon() != null) {
909 Drawable icon = statusHints.getIcon().loadDrawable(mContext);
910 if (icon != null) {
911 return icon;
912 }
913 }
914
915 return null;
916 }
917
918 private boolean hasOutgoingGatewayCall() {
919 // We only display the gateway information while STATE_DIALING so return false for any other
920 // call state.
921 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
922 // is also called after a contact search completes (call is not present yet). Split the
923 // UI update so it can receive independent updates.
924 if (mPrimary == null) {
925 return false;
926 }
927 return DialerCall.State.isDialing(mPrimary.getState())
928 && mPrimary.getGatewayInfo() != null
929 && !mPrimary.getGatewayInfo().isEmpty();
930 }
931
932 /** Gets the name to display for the call. */
933 String getNameForCall(ContactCacheEntry contactInfo) {
934 String preferredName =
935 ContactDisplayUtils.getPreferredDisplayName(
936 contactInfo.namePrimary, contactInfo.nameAlternative, mContactsPreferences);
937 if (TextUtils.isEmpty(preferredName)) {
938 return contactInfo.number;
939 }
940 return preferredName;
941 }
942
943 /** Gets the number to display for a call. */
944 String getNumberForCall(ContactCacheEntry contactInfo) {
945 // If the name is empty, we use the number for the name...so don't show a second
946 // number in the number field
947 String preferredName =
948 ContactDisplayUtils.getPreferredDisplayName(
949 contactInfo.namePrimary, contactInfo.nameAlternative, mContactsPreferences);
950 if (TextUtils.isEmpty(preferredName)) {
951 return contactInfo.location;
952 }
953 return contactInfo.number;
954 }
955
956 @Override
957 public void onSecondaryInfoClicked() {
958 if (mSecondary == null) {
959 LogUtil.e(
960 "CallCardPresenter.onSecondaryInfoClicked",
961 "secondary info clicked but no secondary call.");
962 return;
963 }
964
965 LogUtil.i(
966 "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + mSecondary);
967 mSecondary.unhold();
968 }
969
970 @Override
971 public void onEndCallClicked() {
972 LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + mPrimary);
973 if (mPrimary != null) {
974 mPrimary.disconnect();
975 }
976 }
977
978 /**
979 * Handles a change to the fullscreen mode of the in-call UI.
980 *
981 * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
982 */
983 @Override
984 public void onFullscreenModeChanged(boolean isFullscreenMode) {
985 mIsFullscreen = isFullscreenMode;
986 if (mInCallScreen == null) {
987 return;
988 }
989 maybeShowManageConferenceCallButton();
990 }
991
992 private boolean isPrimaryCallActive() {
993 return mPrimary != null && mPrimary.getState() == DialerCall.State.ACTIVE;
994 }
995
996 private String getConferenceString(DialerCall call) {
997 boolean isGenericConference = call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE);
998 LogUtil.v("CallCardPresenter.getConferenceString", "" + isGenericConference);
999
1000 final int resId =
1001 isGenericConference ? R.string.generic_conference_call_name : R.string.conference_call_name;
1002 return mContext.getResources().getString(resId);
1003 }
1004
1005 private Drawable getConferencePhoto(DialerCall call) {
1006 boolean isGenericConference = call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE);
1007 LogUtil.v("CallCardPresenter.getConferencePhoto", "" + isGenericConference);
1008
1009 final int resId = isGenericConference ? R.drawable.img_phone : R.drawable.img_conference;
1010 Drawable photo = mContext.getResources().getDrawable(resId);
1011 photo.setAutoMirrored(true);
1012 return photo;
1013 }
1014
1015 private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
1016 if (primary == null) {
1017 return false;
1018 }
1019 if ((!DialerCall.State.isConnectingOrConnected(callState)
1020 && callState != DialerCall.State.DISCONNECTING
1021 && callState != DialerCall.State.DISCONNECTED)
1022 || callState == DialerCall.State.INCOMING) {
1023 return false;
1024 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001025 if (mPrimary.getVideoTech().getSessionModificationState()
1026 == VideoTech.SESSION_MODIFICATION_STATE_RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001027 return false;
1028 }
1029 return true;
1030 }
1031
1032 @Override
1033 public void onInCallScreenResumed() {
1034 if (shouldSendAccessibilityEvent) {
1035 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1036 }
1037 }
1038
1039 static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
1040 AccessibilityManager am =
1041 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
1042 if (!am.isEnabled()) {
1043 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
1044 return false;
1045 }
1046 if (inCallScreen == null) {
1047 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
1048 return false;
1049 }
1050 Fragment fragment = inCallScreen.getInCallScreenFragment();
1051 if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
1052 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
1053 return false;
1054 }
1055
1056 DisplayManager displayManager =
1057 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1058 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1059 boolean screenIsOn = display.getState() == Display.STATE_ON;
1060 LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
1061 if (!screenIsOn) {
1062 return false;
1063 }
1064
1065 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1066 inCallScreen.dispatchPopulateAccessibilityEvent(event);
1067 View view = inCallScreen.getInCallScreenFragment().getView();
1068 view.getParent().requestSendAccessibilityEvent(view, event);
1069 return true;
1070 }
1071
1072 private void maybeSendAccessibilityEvent(
1073 InCallState oldState, final InCallState newState, boolean primaryChanged) {
1074 shouldSendAccessibilityEvent = false;
1075 if (mContext == null) {
1076 return;
1077 }
1078 final AccessibilityManager am =
1079 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
1080 if (!am.isEnabled()) {
1081 return;
1082 }
1083 // Announce the current call if it's new incoming/outgoing call or primary call is changed
1084 // due to switching calls between two ongoing calls (one is on hold).
1085 if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
1086 || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
1087 || primaryChanged) {
1088 LogUtil.i(
1089 "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
1090 shouldSendAccessibilityEvent = true;
1091 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1092 }
1093 }
1094
1095 /**
1096 * Determines whether the call subject should be visible on the UI. For the call subject to be
1097 * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
1098 *
1099 * @param call The call.
1100 * @return {@code true} if the subject should be shown, {@code false} otherwise.
1101 */
1102 private boolean shouldShowCallSubject(DialerCall call) {
1103 if (call == null) {
1104 return false;
1105 }
1106
1107 boolean isIncomingOrWaiting =
1108 mPrimary.getState() == DialerCall.State.INCOMING
1109 || mPrimary.getState() == DialerCall.State.CALL_WAITING;
1110 return isIncomingOrWaiting
1111 && !TextUtils.isEmpty(call.getCallSubject())
1112 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
1113 && call.isCallSubjectSupported();
1114 }
1115
1116 /**
1117 * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
1118 * call with a subject.
1119 *
1120 * @param call The call
1121 * @return {@code true} if the toast should be shown, {@code false} otherwise.
1122 */
1123 private boolean shouldShowNoteSentToast(DialerCall call) {
1124 return call != null
1125 && hasCallSubject(call)
1126 && (call.getState() == DialerCall.State.DIALING
1127 || call.getState() == DialerCall.State.CONNECTING);
1128 }
1129
1130 private InCallScreen getUi() {
1131 return mInCallScreen;
1132 }
1133
1134 public static class ContactLookupCallback implements ContactInfoCacheCallback {
1135
1136 private final WeakReference<CallCardPresenter> mCallCardPresenter;
1137 private final boolean mIsPrimary;
1138
1139 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
1140 mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
1141 mIsPrimary = isPrimary;
1142 }
1143
1144 @Override
1145 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
1146 CallCardPresenter presenter = mCallCardPresenter.get();
1147 if (presenter != null) {
1148 presenter.onContactInfoComplete(callId, entry, mIsPrimary);
1149 }
1150 }
1151
1152 @Override
1153 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
1154 CallCardPresenter presenter = mCallCardPresenter.get();
1155 if (presenter != null) {
1156 presenter.onImageLoadComplete(callId, entry);
1157 }
1158 }
1159 }
1160}