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