Home | History | Annotate | Download | only in incallui
      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 
    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 
    149   private static boolean hasCallSubject(DialerCall call) {
    150     return !TextUtils.isEmpty(call.getCallSubject());
    151   }
    152 
    153   @Override
    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
    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
    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
    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
    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
    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
    363   public void onDialerCallDisconnect() {}
    364 
    365   @Override
    366   public void onDialerCallUpdate() {
    367     // No-op; specific call updates handled elsewhere.
    368   }
    369 
    370   @Override
    371   public void onWiFiToLteHandover() {}
    372 
    373   @Override
    374   public void onHandoverToWifiFailure() {}
    375 
    376   @Override
    377   public void onInternationalCallOnWifi() {}
    378 
    379   /** Handles a change to the child number by refreshing the primary call info. */
    380   @Override
    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
    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
    403   public void onDialerCallUpgradeToVideo() {}
    404 
    405   /** Handles a change to the session modification state for a call. */
    406   @Override
    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
    422   public void onEnrichedCallStateChanged() {
    423     LogUtil.enterBlock("CallCardPresenter.onEnrichedCallStateChanged");
    424     updatePrimaryDisplayInfo();
    425   }
    426 
    427   private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
    428     if (mPrimary == null) {
    429       return false;
    430     }
    431     return primaryChanged
    432         || mInCallScreen.isManageConferenceVisible() != shouldShowManageConference();
    433   }
    434 
    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. */
    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    */
    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
    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
    520   public void onManageConferenceClicked() {
    521     InCallActivity activity =
    522         (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
    523     activity.showConferenceFragment(true);
    524   }
    525 
    526   @Override
    527   public void onShrinkAnimationComplete() {
    528     InCallPresenter.getInstance().onShrinkAnimationComplete();
    529   }
    530 
    531   @Override
    532   public Drawable getDefaultContactPhotoDrawable() {
    533     return ContactInfoCache.getInstance(mContext).getDefaultContactPhotoDrawable();
    534   }
    535 
    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. */
    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 
    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 
    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 
    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    */
    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 
    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 
    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 
    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 
    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 
    832   private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
    833     return call != null && !call.isIncoming() && call.isEmergencyCall();
    834   }
    835 
    836   private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
    837     return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
    838   }
    839 
    840   private boolean hasLocationPermission() {
    841     return ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION)
    842         == PackageManager.PERMISSION_GRANTED;
    843   }
    844 
    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 
    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. */
    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    */
    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 
    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 
    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. */
    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. */
    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
    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
   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
   1026   public void onFullscreenModeChanged(boolean isFullscreenMode) {
   1027     mIsFullscreen = isFullscreenMode;
   1028     if (mInCallScreen == null) {
   1029       return;
   1030     }
   1031     maybeShowManageConferenceCallButton();
   1032   }
   1033 
   1034   private boolean isPrimaryCallActive() {
   1035     return mPrimary != null && mPrimary.getState() == DialerCall.State.ACTIVE;
   1036   }
   1037 
   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 
   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
   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
   1075   public void onInCallScreenPaused() {
   1076     EnrichedCallComponent.get(mContext)
   1077         .getEnrichedCallManager()
   1078         .unregisterStateChangedListener(this);
   1079   }
   1080 
   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 
   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    */
   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    */
   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 
   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 
   1181     public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
   1182       mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
   1183       mIsPrimary = isPrimary;
   1184     }
   1185 
   1186     @Override
   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
   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