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