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