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