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