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