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
17package com.android.incallui;
18
19import android.Manifest;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.ApplicationInfo;
23import android.content.pm.PackageManager;
24import android.graphics.drawable.Drawable;
25import android.net.Uri;
26import android.os.Bundle;
27import android.telecom.DisconnectCause;
28import android.telecom.PhoneAccount;
29import android.telecom.PhoneAccountHandle;
30import android.telecom.StatusHints;
31import android.telecom.TelecomManager;
32import android.telecom.VideoProfile;
33import android.telephony.PhoneNumberUtils;
34import android.telephony.TelephonyManager;
35import android.text.TextUtils;
36
37import com.android.incallui.ContactInfoCache.ContactCacheEntry;
38import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
39import com.android.incallui.InCallPresenter.InCallDetailsListener;
40import com.android.incallui.InCallPresenter.InCallEventListener;
41import com.android.incallui.InCallPresenter.InCallState;
42import com.android.incallui.InCallPresenter.InCallStateListener;
43import com.android.incallui.InCallPresenter.IncomingCallListener;
44import com.android.incalluibind.ObjectFactory;
45
46import java.lang.ref.WeakReference;
47
48import 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 */
55public 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
73        public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
74            mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
75            mIsPrimary = isPrimary;
76        }
77
78        @Override
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
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
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
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
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
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
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
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
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
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
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     */
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     */
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
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
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
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
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     */
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
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
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
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     */
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
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
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     */
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     */
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     */
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     */
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
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
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     */
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     */
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
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
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
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
696    public void onFullScreenVideoStateChanged(boolean isFullScreenVideo) {
697        final CallCardUi ui = getUi();
698        if (ui == null) {
699            return;
700        }
701        ui.setCallCardVisible(!isFullScreenVideo);
702    }
703
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
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 {
727        void setVisible(boolean on);
728        void setCallCardVisible(boolean visible);
729        void setPrimary(String number, String name, boolean nameIsNumber, String label,
730                Drawable photo, boolean isSipCall);
731        void setSecondary(boolean show, String name, boolean nameIsNumber, String label,
732                String providerLabel, boolean isConference);
733        void setCallState(int state, int videoState, int sessionModificationState,
734                DisconnectCause disconnectCause, String connectionLabel,
735                Drawable connectionIcon, String gatewayNumber);
736        void setPrimaryCallElapsedTime(boolean show, long duration);
737        void setPrimaryName(String name, boolean nameIsNumber);
738        void setPrimaryImage(Drawable image);
739        void setPrimaryPhoneNumber(String phoneNumber);
740        void setPrimaryLabel(String label);
741        void setEndCallButtonEnabled(boolean enabled, boolean animate);
742        void setCallbackNumber(String number, boolean isEmergencyCalls);
743        void setPhotoVisible(boolean isVisible);
744        void setProgressSpinnerVisible(boolean visible);
745        void showManageConferenceCallButton(boolean visible);
746        boolean isManageConferenceVisible();
747    }
748}
749