• 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 android.Manifest;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.PackageManager;
24 import android.graphics.drawable.Drawable;
25 import android.net.Uri;
26 import android.os.Bundle;
27 import android.telecom.DisconnectCause;
28 import android.telecom.PhoneAccount;
29 import android.telecom.PhoneAccountHandle;
30 import android.telecom.StatusHints;
31 import android.telecom.TelecomManager;
32 import android.telecom.VideoProfile;
33 import android.telephony.PhoneNumberUtils;
34 import android.telephony.TelephonyManager;
35 import android.text.TextUtils;
36 
37 import com.android.incallui.ContactInfoCache.ContactCacheEntry;
38 import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
39 import com.android.incallui.InCallPresenter.InCallDetailsListener;
40 import com.android.incallui.InCallPresenter.InCallEventListener;
41 import com.android.incallui.InCallPresenter.InCallState;
42 import com.android.incallui.InCallPresenter.InCallStateListener;
43 import com.android.incallui.InCallPresenter.IncomingCallListener;
44 import com.android.incalluibind.ObjectFactory;
45 
46 import java.lang.ref.WeakReference;
47 
48 import com.google.common.base.Preconditions;
49 
50 /**
51  * Presenter for the Call Card Fragment.
52  * <p>
53  * This class listens for changes to InCallState and passes it along to the fragment.
54  */
55 public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
56         implements InCallStateListener, IncomingCallListener, InCallDetailsListener,
57         InCallEventListener {
58 
59     private static final String TAG = CallCardPresenter.class.getSimpleName();
60     private static final long CALL_TIME_UPDATE_INTERVAL_MS = 1000;
61 
62     private Call mPrimary;
63     private Call mSecondary;
64     private ContactCacheEntry mPrimaryContactInfo;
65     private ContactCacheEntry mSecondaryContactInfo;
66     private CallTimer mCallTimer;
67     private Context mContext;
68 
69     public static class ContactLookupCallback implements ContactInfoCacheCallback {
70         private final WeakReference<CallCardPresenter> mCallCardPresenter;
71         private final boolean mIsPrimary;
72 
ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary)73         public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
74             mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
75             mIsPrimary = isPrimary;
76         }
77 
78         @Override
onContactInfoComplete(String callId, ContactCacheEntry entry)79         public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
80             CallCardPresenter presenter = mCallCardPresenter.get();
81             if (presenter != null) {
82                 presenter.onContactInfoComplete(callId, entry, mIsPrimary);
83             }
84         }
85 
86         @Override
onImageLoadComplete(String callId, ContactCacheEntry entry)87         public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
88             CallCardPresenter presenter = mCallCardPresenter.get();
89             if (presenter != null) {
90                 presenter.onImageLoadComplete(callId, entry);
91             }
92         }
93 
94     }
95 
CallCardPresenter()96     public CallCardPresenter() {
97         // create the call timer
98         mCallTimer = new CallTimer(new Runnable() {
99             @Override
100             public void run() {
101                 updateCallTime();
102             }
103         });
104     }
105 
init(Context context, Call call)106     public void init(Context context, Call call) {
107         mContext = Preconditions.checkNotNull(context);
108 
109         // Call may be null if disconnect happened already.
110         if (call != null) {
111             mPrimary = call;
112 
113             // start processing lookups right away.
114             if (!call.isConferenceCall()) {
115                 startContactInfoSearch(call, true, call.getState() == Call.State.INCOMING);
116             } else {
117                 updateContactEntry(null, true);
118             }
119         }
120     }
121 
122     @Override
onUiReady(CallCardUi ui)123     public void onUiReady(CallCardUi ui) {
124         super.onUiReady(ui);
125 
126         // Contact search may have completed before ui is ready.
127         if (mPrimaryContactInfo != null) {
128             updatePrimaryDisplayInfo();
129         }
130 
131         // Register for call state changes last
132         InCallPresenter.getInstance().addListener(this);
133         InCallPresenter.getInstance().addIncomingCallListener(this);
134         InCallPresenter.getInstance().addDetailsListener(this);
135         InCallPresenter.getInstance().addInCallEventListener(this);
136     }
137 
138     @Override
onUiUnready(CallCardUi ui)139     public void onUiUnready(CallCardUi ui) {
140         super.onUiUnready(ui);
141 
142         // stop getting call state changes
143         InCallPresenter.getInstance().removeListener(this);
144         InCallPresenter.getInstance().removeIncomingCallListener(this);
145         InCallPresenter.getInstance().removeDetailsListener(this);
146         InCallPresenter.getInstance().removeInCallEventListener(this);
147 
148         mPrimary = null;
149         mPrimaryContactInfo = null;
150         mSecondaryContactInfo = null;
151     }
152 
153     @Override
onIncomingCall(InCallState oldState, InCallState newState, Call call)154     public void onIncomingCall(InCallState oldState, InCallState newState, Call call) {
155         // same logic should happen as with onStateChange()
156         onStateChange(oldState, newState, CallList.getInstance());
157     }
158 
159     @Override
onStateChange(InCallState oldState, InCallState newState, CallList callList)160     public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
161         Log.d(this, "onStateChange() " + newState);
162         final CallCardUi ui = getUi();
163         if (ui == null) {
164             return;
165         }
166 
167         Call primary = null;
168         Call secondary = null;
169 
170         if (newState == InCallState.INCOMING) {
171             primary = callList.getIncomingCall();
172         } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
173             primary = callList.getOutgoingCall();
174             if (primary == null) {
175                 primary = callList.getPendingOutgoingCall();
176             }
177 
178             // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
179             // highest priority call to display as the secondary call.
180             secondary = getCallToDisplay(callList, null, true);
181         } else if (newState == InCallState.INCALL) {
182             primary = getCallToDisplay(callList, null, false);
183             secondary = getCallToDisplay(callList, primary, true);
184         }
185 
186         Log.d(this, "Primary call: " + primary);
187         Log.d(this, "Secondary call: " + secondary);
188 
189         final boolean primaryChanged = !Call.areSame(mPrimary, primary);
190         final boolean secondaryChanged = !Call.areSame(mSecondary, secondary);
191 
192         mSecondary = secondary;
193         mPrimary = primary;
194 
195         // Refresh primary call information if either:
196         // 1. Primary call changed.
197         // 2. The call's ability to manage conference has changed.
198         if (mPrimary != null && (primaryChanged ||
199                 ui.isManageConferenceVisible() != shouldShowManageConference())) {
200             // primary call has changed
201             mPrimaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext, mPrimary,
202                     mPrimary.getState() == Call.State.INCOMING);
203             updatePrimaryDisplayInfo();
204             maybeStartSearch(mPrimary, true);
205             mPrimary.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
206         }
207 
208         if (mSecondary == null) {
209             // Secondary call may have ended.  Update the ui.
210             mSecondaryContactInfo = null;
211             updateSecondaryDisplayInfo();
212         } else if (secondaryChanged) {
213             // secondary call has changed
214             mSecondaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext, mSecondary,
215                     mSecondary.getState() == Call.State.INCOMING);
216             updateSecondaryDisplayInfo();
217             maybeStartSearch(mSecondary, false);
218             mSecondary.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
219         }
220 
221         // Start/stop timers.
222         if (mPrimary != null && mPrimary.getState() == Call.State.ACTIVE) {
223             Log.d(this, "Starting the calltime timer");
224             mCallTimer.start(CALL_TIME_UPDATE_INTERVAL_MS);
225         } else {
226             Log.d(this, "Canceling the calltime timer");
227             mCallTimer.cancel();
228             ui.setPrimaryCallElapsedTime(false, 0);
229         }
230 
231         // Set the call state
232         int callState = Call.State.IDLE;
233         if (mPrimary != null) {
234             callState = mPrimary.getState();
235             updatePrimaryCallState();
236         } else {
237             getUi().setCallState(
238                     callState,
239                     VideoProfile.VideoState.AUDIO_ONLY,
240                     Call.SessionModificationState.NO_REQUEST,
241                     new DisconnectCause(DisconnectCause.UNKNOWN),
242                     null,
243                     null,
244                     null);
245         }
246 
247         // Hide/show the contact photo based on the video state.
248         // If the primary call is a video call on hold, still show the contact photo.
249         // If the primary call is an active video call, hide the contact photo.
250         if (mPrimary != null) {
251             getUi().setPhotoVisible(!(mPrimary.isVideoCall(mContext) &&
252                     callState != Call.State.ONHOLD));
253         }
254 
255         maybeShowManageConferenceCallButton();
256 
257         final boolean enableEndCallButton = (Call.State.isConnectingOrConnected(callState)
258                 || callState == Call.State.DISCONNECTING) &&
259                 callState != Call.State.INCOMING && mPrimary != null;
260         // Hide the end call button instantly if we're receiving an incoming call.
261         getUi().setEndCallButtonEnabled(
262                 enableEndCallButton, callState != Call.State.INCOMING /* animate */);
263     }
264 
265     @Override
onDetailsChanged(Call call, android.telecom.Call.Details details)266     public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
267         updatePrimaryCallState();
268 
269         if (call.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) !=
270                 android.telecom.Call.Details.can(
271                         details.getCallCapabilities(),
272                         android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)) {
273             maybeShowManageConferenceCallButton();
274         }
275     }
276 
getSubscriptionNumber()277     private String getSubscriptionNumber() {
278         // If it's an emergency call, and they're not populating the callback number,
279         // then try to fall back to the phone sub info (to hopefully get the SIM's
280         // number directly from the telephony layer).
281         PhoneAccountHandle accountHandle = mPrimary.getAccountHandle();
282         if (accountHandle != null) {
283             TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager();
284             PhoneAccount account = mgr.getPhoneAccount(accountHandle);
285             if (account != null) {
286                 return getNumberFromHandle(account.getSubscriptionAddress());
287             }
288         }
289         return null;
290     }
291 
updatePrimaryCallState()292     private void updatePrimaryCallState() {
293         if (getUi() != null && mPrimary != null) {
294             getUi().setCallState(
295                     mPrimary.getState(),
296                     mPrimary.getVideoState(),
297                     mPrimary.getSessionModificationState(),
298                     mPrimary.getDisconnectCause(),
299                     getConnectionLabel(),
300                     getCallStateIcon(),
301                     getGatewayNumber());
302             setCallbackNumber();
303         }
304     }
305 
306     /**
307      * Only show the conference call button if we can manage the conference.
308      */
maybeShowManageConferenceCallButton()309     private void maybeShowManageConferenceCallButton() {
310         getUi().showManageConferenceCallButton(shouldShowManageConference());
311     }
312 
313     /**
314      * Determines if the manage conference button should be visible, based on the current primary
315      * call.
316      *
317      * @return {@code True} if the manage conference button should be visible.
318      */
shouldShowManageConference()319     private boolean shouldShowManageConference() {
320         if (mPrimary == null) {
321             return false;
322         }
323 
324         return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE);
325     }
326 
setCallbackNumber()327     private void setCallbackNumber() {
328         String callbackNumber = null;
329 
330         boolean isEmergencyCall = PhoneNumberUtils.isEmergencyNumber(
331                 getNumberFromHandle(mPrimary.getHandle()));
332         if (isEmergencyCall) {
333             callbackNumber = getSubscriptionNumber();
334         } else {
335             StatusHints statusHints = mPrimary.getTelecommCall().getDetails().getStatusHints();
336             if (statusHints != null) {
337                 Bundle extras = statusHints.getExtras();
338                 if (extras != null) {
339                     callbackNumber = extras.getString(TelecomManager.EXTRA_CALL_BACK_NUMBER);
340                 }
341             }
342         }
343 
344         TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager();
345         String simNumber = mgr.getLine1Number(mPrimary.getAccountHandle());
346         if (PhoneNumberUtils.compare(callbackNumber, simNumber)) {
347             Log.d(this, "Numbers are the same; not showing the callback number");
348             callbackNumber = null;
349         }
350 
351         getUi().setCallbackNumber(callbackNumber, isEmergencyCall);
352     }
353 
updateCallTime()354     public void updateCallTime() {
355         final CallCardUi ui = getUi();
356 
357         if (ui == null || mPrimary == null || mPrimary.getState() != Call.State.ACTIVE) {
358             if (ui != null) {
359                 ui.setPrimaryCallElapsedTime(false, 0);
360             }
361             mCallTimer.cancel();
362         } else {
363             final long callStart = mPrimary.getConnectTimeMillis();
364             final long duration = System.currentTimeMillis() - callStart;
365             ui.setPrimaryCallElapsedTime(true, duration);
366         }
367     }
368 
onCallStateButtonTouched()369     public void onCallStateButtonTouched() {
370         Intent broadcastIntent = ObjectFactory.getCallStateButtonBroadcastIntent(mContext);
371         if (broadcastIntent != null) {
372             Log.d(this, "Sending call state button broadcast: ", broadcastIntent);
373             mContext.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
374         }
375     }
376 
maybeStartSearch(Call call, boolean isPrimary)377     private void maybeStartSearch(Call call, boolean isPrimary) {
378         // no need to start search for conference calls which show generic info.
379         if (call != null && !call.isConferenceCall()) {
380             startContactInfoSearch(call, isPrimary, call.getState() == Call.State.INCOMING);
381         }
382     }
383 
384     /**
385      * Starts a query for more contact data for the save primary and secondary calls.
386      */
startContactInfoSearch(final Call call, final boolean isPrimary, boolean isIncoming)387     private void startContactInfoSearch(final Call call, final boolean isPrimary,
388             boolean isIncoming) {
389         final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
390 
391         cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
392     }
393 
onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary)394     private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
395         updateContactEntry(entry, isPrimary);
396         if (entry.name != null) {
397             Log.d(TAG, "Contact found: " + entry);
398         }
399         if (entry.contactUri != null) {
400             CallerInfoUtils.sendViewNotification(mContext, entry.contactUri);
401         }
402     }
403 
onImageLoadComplete(String callId, ContactCacheEntry entry)404     private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
405         if (getUi() == null) {
406             return;
407         }
408 
409         if (entry.photo != null) {
410             if (mPrimary != null && callId.equals(mPrimary.getId())) {
411                 getUi().setPrimaryImage(entry.photo);
412             }
413         }
414     }
415 
updateContactEntry(ContactCacheEntry entry, boolean isPrimary)416     private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
417         if (isPrimary) {
418             mPrimaryContactInfo = entry;
419             updatePrimaryDisplayInfo();
420         } else {
421             mSecondaryContactInfo = entry;
422             updateSecondaryDisplayInfo();
423         }
424     }
425 
426     /**
427      * Get the highest priority call to display.
428      * Goes through the calls and chooses which to return based on priority of which type of call
429      * to display to the user. Callers can use the "ignore" feature to get the second best call
430      * by passing a previously found primary call as ignore.
431      *
432      * @param ignore A call to ignore if found.
433      */
getCallToDisplay(CallList callList, Call ignore, boolean skipDisconnected)434     private Call getCallToDisplay(CallList callList, Call ignore, boolean skipDisconnected) {
435 
436         // Active calls come second.  An active call always gets precedent.
437         Call retval = callList.getActiveCall();
438         if (retval != null && retval != ignore) {
439             return retval;
440         }
441 
442         // Disconnected calls get primary position if there are no active calls
443         // to let user know quickly what call has disconnected. Disconnected
444         // calls are very short lived.
445         if (!skipDisconnected) {
446             retval = callList.getDisconnectingCall();
447             if (retval != null && retval != ignore) {
448                 return retval;
449             }
450             retval = callList.getDisconnectedCall();
451             if (retval != null && retval != ignore) {
452                 return retval;
453             }
454         }
455 
456         // Then we go to background call (calls on hold)
457         retval = callList.getBackgroundCall();
458         if (retval != null && retval != ignore) {
459             return retval;
460         }
461 
462         // Lastly, we go to a second background call.
463         retval = callList.getSecondBackgroundCall();
464 
465         return retval;
466     }
467 
updatePrimaryDisplayInfo()468     private void updatePrimaryDisplayInfo() {
469         final CallCardUi ui = getUi();
470         if (ui == null) {
471             // TODO: May also occur if search result comes back after ui is destroyed. Look into
472             // removing that case completely.
473             Log.d(TAG, "updatePrimaryDisplayInfo called but ui is null!");
474             return;
475         }
476 
477         if (mPrimary == null) {
478             // Clear the primary display info.
479             ui.setPrimary(null, null, false, null, null, false);
480             return;
481         }
482 
483         if (mPrimary.isConferenceCall()) {
484             Log.d(TAG, "Update primary display info for conference call.");
485 
486             ui.setPrimary(
487                     null /* number */,
488                     getConferenceString(mPrimary),
489                     false /* nameIsNumber */,
490                     null /* label */,
491                     getConferencePhoto(mPrimary),
492                     false /* isSipCall */);
493         } else if (mPrimaryContactInfo != null) {
494             Log.d(TAG, "Update primary display info for " + mPrimaryContactInfo);
495 
496             String name = getNameForCall(mPrimaryContactInfo);
497             String number = getNumberForCall(mPrimaryContactInfo);
498             boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
499             ui.setPrimary(
500                     number,
501                     name,
502                     nameIsNumber,
503                     mPrimaryContactInfo.label,
504                     mPrimaryContactInfo.photo,
505                     mPrimaryContactInfo.isSipCall);
506         } else {
507             // Clear the primary display info.
508             ui.setPrimary(null, null, false, null, null, false);
509         }
510 
511     }
512 
updateSecondaryDisplayInfo()513     private void updateSecondaryDisplayInfo() {
514         final CallCardUi ui = getUi();
515         if (ui == null) {
516             return;
517         }
518 
519         if (mSecondary == null) {
520             // Clear the secondary display info.
521             ui.setSecondary(false, null, false, null, null, false /* isConference */);
522             return;
523         }
524 
525         if (mSecondary.isConferenceCall()) {
526             ui.setSecondary(
527                     true /* show */,
528                     getConferenceString(mSecondary),
529                     false /* nameIsNumber */,
530                     null /* label */,
531                     getCallProviderLabel(mSecondary),
532                     true /* isConference */);
533         } else if (mSecondaryContactInfo != null) {
534             Log.d(TAG, "updateSecondaryDisplayInfo() " + mSecondaryContactInfo);
535             String name = getNameForCall(mSecondaryContactInfo);
536             boolean nameIsNumber = name != null && name.equals(mSecondaryContactInfo.number);
537             ui.setSecondary(
538                     true /* show */,
539                     name,
540                     nameIsNumber,
541                     mSecondaryContactInfo.label,
542                     getCallProviderLabel(mSecondary),
543                     false /* isConference */);
544         } else {
545             // Clear the secondary display info.
546             ui.setSecondary(false, null, false, null, null, false /* isConference */);
547         }
548     }
549 
550 
551     /**
552      * Gets the phone account to display for a call.
553      */
getAccountForCall(Call call)554     private PhoneAccount getAccountForCall(Call call) {
555         PhoneAccountHandle accountHandle = call.getAccountHandle();
556         if (accountHandle == null) {
557             return null;
558         }
559         return InCallPresenter.getInstance().getTelecomManager().getPhoneAccount(accountHandle);
560     }
561 
562     /**
563      * Returns the gateway number for any existing outgoing call.
564      */
getGatewayNumber()565     private String getGatewayNumber() {
566         if (hasOutgoingGatewayCall()) {
567             return getNumberFromHandle(mPrimary.getGatewayInfo().getGatewayAddress());
568         }
569         return null;
570     }
571 
572     /**
573      * Return the string label to represent the call provider
574      */
getCallProviderLabel(Call call)575     private String getCallProviderLabel(Call call) {
576         PhoneAccount account = getAccountForCall(call);
577         TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager();
578         if (account != null && !TextUtils.isEmpty(account.getLabel())
579                 && mgr.hasMultipleCallCapableAccounts()) {
580             return account.getLabel().toString();
581         }
582         return null;
583     }
584 
585     /**
586      * Returns the label (line of text above the number/name) for any given call.
587      * For example, "calling via [Account/Google Voice]" for outgoing calls.
588      */
getConnectionLabel()589     private String getConnectionLabel() {
590         StatusHints statusHints = mPrimary.getTelecommCall().getDetails().getStatusHints();
591         if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
592             return statusHints.getLabel().toString();
593         }
594 
595         if (hasOutgoingGatewayCall() && getUi() != null) {
596             // Return the label for the gateway app on outgoing calls.
597             final PackageManager pm = mContext.getPackageManager();
598             try {
599                 ApplicationInfo info = pm.getApplicationInfo(
600                         mPrimary.getGatewayInfo().getGatewayProviderPackageName(), 0);
601                 return pm.getApplicationLabel(info).toString();
602             } catch (PackageManager.NameNotFoundException e) {
603                 Log.e(this, "Gateway Application Not Found.", e);
604                 return null;
605             }
606         }
607         return getCallProviderLabel(mPrimary);
608     }
609 
getCallStateIcon()610     private Drawable getCallStateIcon() {
611         // Return connection icon if one exists.
612         StatusHints statusHints = mPrimary.getTelecommCall().getDetails().getStatusHints();
613         if (statusHints != null && statusHints.getIconResId() != 0) {
614             Drawable icon = statusHints.getIcon(mContext);
615             if (icon != null) {
616                 return icon;
617             }
618         }
619 
620         // Return high definition audio icon if the capability is indicated.
621         if (mPrimary.getTelecommCall().getDetails().can(
622                 android.telecom.Call.Details.CAPABILITY_HIGH_DEF_AUDIO)
623                 && mPrimary.getState() == Call.State.ACTIVE) {
624             return mContext.getResources().getDrawable(R.drawable.ic_hd_audio);
625         }
626 
627         return null;
628     }
629 
hasOutgoingGatewayCall()630     private boolean hasOutgoingGatewayCall() {
631         // We only display the gateway information while STATE_DIALING so return false for any othe
632         // call state.
633         // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
634         // is also called after a contact search completes (call is not present yet).  Split the
635         // UI update so it can receive independent updates.
636         if (mPrimary == null) {
637             return false;
638         }
639         return Call.State.isDialing(mPrimary.getState()) && mPrimary.getGatewayInfo() != null &&
640                 !mPrimary.getGatewayInfo().isEmpty();
641     }
642 
643     /**
644      * Gets the name to display for the call.
645      */
getNameForCall(ContactCacheEntry contactInfo)646     private static String getNameForCall(ContactCacheEntry contactInfo) {
647         if (TextUtils.isEmpty(contactInfo.name)) {
648             return contactInfo.number;
649         }
650         return contactInfo.name;
651     }
652 
653     /**
654      * Gets the number to display for a call.
655      */
getNumberForCall(ContactCacheEntry contactInfo)656     private static String getNumberForCall(ContactCacheEntry contactInfo) {
657         // If the name is empty, we use the number for the name...so dont show a second
658         // number in the number field
659         if (TextUtils.isEmpty(contactInfo.name)) {
660             return contactInfo.location;
661         }
662         return contactInfo.number;
663     }
664 
secondaryInfoClicked()665     public void secondaryInfoClicked() {
666         if (mSecondary == null) {
667             Log.w(this, "Secondary info clicked but no secondary call.");
668             return;
669         }
670 
671         Log.i(this, "Swapping call to foreground: " + mSecondary);
672         TelecomAdapter.getInstance().unholdCall(mSecondary.getId());
673     }
674 
endCallClicked()675     public void endCallClicked() {
676         if (mPrimary == null) {
677             return;
678         }
679 
680         Log.i(this, "Disconnecting call: " + mPrimary);
681         mPrimary.setState(Call.State.DISCONNECTING);
682         CallList.getInstance().onUpdate(mPrimary);
683         TelecomAdapter.getInstance().disconnectCall(mPrimary.getId());
684     }
685 
getNumberFromHandle(Uri handle)686     private String getNumberFromHandle(Uri handle) {
687         return handle == null ? "" : handle.getSchemeSpecificPart();
688     }
689 
690     /**
691      * Handles a change to the full screen video state.
692      *
693      * @param isFullScreenVideo {@code True} if the application is entering full screen video mode.
694      */
695     @Override
onFullScreenVideoStateChanged(boolean isFullScreenVideo)696     public void onFullScreenVideoStateChanged(boolean isFullScreenVideo) {
697         final CallCardUi ui = getUi();
698         if (ui == null) {
699             return;
700         }
701         ui.setCallCardVisible(!isFullScreenVideo);
702     }
703 
getConferenceString(Call call)704     private String getConferenceString(Call call) {
705         boolean isGenericConference = call.can(
706                 android.telecom.Call.Details.CAPABILITY_GENERIC_CONFERENCE);
707         Log.v(this, "getConferenceString: " + isGenericConference);
708 
709         final int resId = isGenericConference
710                 ? R.string.card_title_in_call : R.string.card_title_conf_call;
711         return mContext.getResources().getString(resId);
712     }
713 
getConferencePhoto(Call call)714     private Drawable getConferencePhoto(Call call) {
715         boolean isGenericConference = call.can(
716                 android.telecom.Call.Details.CAPABILITY_GENERIC_CONFERENCE);
717         Log.v(this, "getConferencePhoto: " + isGenericConference);
718 
719         final int resId = isGenericConference
720                 ? R.drawable.img_phone : R.drawable.img_conference;
721         Drawable photo = mContext.getResources().getDrawable(resId);
722         photo.setAutoMirrored(true);
723         return photo;
724     }
725 
726     public interface CallCardUi extends Ui {
setVisible(boolean on)727         void setVisible(boolean on);
setCallCardVisible(boolean visible)728         void setCallCardVisible(boolean visible);
setPrimary(String number, String name, boolean nameIsNumber, String label, Drawable photo, boolean isSipCall)729         void setPrimary(String number, String name, boolean nameIsNumber, String label,
730                 Drawable photo, boolean isSipCall);
setSecondary(boolean show, String name, boolean nameIsNumber, String label, String providerLabel, boolean isConference)731         void setSecondary(boolean show, String name, boolean nameIsNumber, String label,
732                 String providerLabel, boolean isConference);
setCallState(int state, int videoState, int sessionModificationState, DisconnectCause disconnectCause, String connectionLabel, Drawable connectionIcon, String gatewayNumber)733         void setCallState(int state, int videoState, int sessionModificationState,
734                 DisconnectCause disconnectCause, String connectionLabel,
735                 Drawable connectionIcon, String gatewayNumber);
setPrimaryCallElapsedTime(boolean show, long duration)736         void setPrimaryCallElapsedTime(boolean show, long duration);
setPrimaryName(String name, boolean nameIsNumber)737         void setPrimaryName(String name, boolean nameIsNumber);
setPrimaryImage(Drawable image)738         void setPrimaryImage(Drawable image);
setPrimaryPhoneNumber(String phoneNumber)739         void setPrimaryPhoneNumber(String phoneNumber);
setPrimaryLabel(String label)740         void setPrimaryLabel(String label);
setEndCallButtonEnabled(boolean enabled, boolean animate)741         void setEndCallButtonEnabled(boolean enabled, boolean animate);
setCallbackNumber(String number, boolean isEmergencyCalls)742         void setCallbackNumber(String number, boolean isEmergencyCalls);
setPhotoVisible(boolean isVisible)743         void setPhotoVisible(boolean isVisible);
setProgressSpinnerVisible(boolean visible)744         void setProgressSpinnerVisible(boolean visible);
showManageConferenceCallButton(boolean visible)745         void showManageConferenceCallButton(boolean visible);
isManageConferenceVisible()746         boolean isManageConferenceVisible();
747     }
748 }
749