CallCardFragment.java revision 68f31bc259798c6fd646a01183767138e2e9a60f
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.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.AnimatorSet;
22import android.animation.LayoutTransition;
23import android.animation.ObjectAnimator;
24import android.app.Activity;
25import android.content.Context;
26import android.content.res.Configuration;
27import android.graphics.drawable.AnimationDrawable;
28import android.graphics.drawable.Drawable;
29import android.os.Bundle;
30import android.telecom.DisconnectCause;
31import android.telecom.VideoProfile;
32import android.telephony.PhoneNumberUtils;
33import android.text.TextUtils;
34import android.text.format.DateUtils;
35import android.view.LayoutInflater;
36import android.view.View;
37import android.view.View.OnLayoutChangeListener;
38import android.view.ViewGroup;
39import android.view.ViewPropertyAnimator;
40import android.view.ViewTreeObserver;
41import android.view.ViewTreeObserver.OnGlobalLayoutListener;
42import android.view.accessibility.AccessibilityEvent;
43import android.view.animation.Animation;
44import android.view.animation.AnimationUtils;
45import android.widget.ImageButton;
46import android.widget.ImageView;
47import android.widget.TextView;
48
49import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
50import com.android.contacts.common.widget.FloatingActionButtonController;
51import com.android.phone.common.animation.AnimUtils;
52
53import java.util.List;
54
55/**
56 * Fragment for call card.
57 */
58public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPresenter.CallCardUi>
59        implements CallCardPresenter.CallCardUi {
60    private AnimatorSet mAnimatorSet;
61    private int mShrinkAnimationDuration;
62    private int mFabNormalDiameter;
63    private int mFabSmallDiameter;
64    private boolean mIsLandscape;
65    private boolean mIsDialpadShowing;
66
67    // Primary caller info
68    private TextView mPhoneNumber;
69    private TextView mNumberLabel;
70    private TextView mPrimaryName;
71    private View mCallStateButton;
72    private ImageView mCallStateIcon;
73    private ImageView mCallStateVideoCallIcon;
74    private TextView mCallStateLabel;
75    private TextView mCallTypeLabel;
76    private ImageView mHdAudioIcon;
77    private View mCallNumberAndLabel;
78    private ImageView mPhoto;
79    private TextView mElapsedTime;
80    private Drawable mPrimaryPhotoDrawable;
81
82    // Container view that houses the entire primary call card, including the call buttons
83    private View mPrimaryCallCardContainer;
84    // Container view that houses the primary call information
85    private ViewGroup mPrimaryCallInfo;
86    private View mCallButtonsContainer;
87
88    // Secondary caller info
89    private View mSecondaryCallInfo;
90    private TextView mSecondaryCallName;
91    private View mSecondaryCallProviderInfo;
92    private TextView mSecondaryCallProviderLabel;
93    private View mSecondaryCallConferenceCallIcon;
94    private View mProgressSpinner;
95
96    private View mManageConferenceCallButton;
97
98    // Dark number info bar
99    private TextView mInCallMessageLabel;
100
101    private FloatingActionButtonController mFloatingActionButtonController;
102    private View mFloatingActionButtonContainer;
103    private ImageButton mFloatingActionButton;
104    private int mFloatingActionButtonVerticalOffset;
105
106    private float mTranslationOffset;
107    private Animation mPulseAnimation;
108
109    private int mVideoAnimationDuration;
110
111    private MaterialPalette mCurrentThemeColors;
112
113    @Override
114    CallCardPresenter.CallCardUi getUi() {
115        return this;
116    }
117
118    @Override
119    CallCardPresenter createPresenter() {
120        return new CallCardPresenter();
121    }
122
123    @Override
124    public void onCreate(Bundle savedInstanceState) {
125        super.onCreate(savedInstanceState);
126
127        mShrinkAnimationDuration = getResources().getInteger(R.integer.shrink_animation_duration);
128        mVideoAnimationDuration = getResources().getInteger(R.integer.video_animation_duration);
129        mFloatingActionButtonVerticalOffset = getResources().getDimensionPixelOffset(
130                R.dimen.floating_action_bar_vertical_offset);
131        mFabNormalDiameter = getResources().getDimensionPixelOffset(
132                R.dimen.end_call_floating_action_button_diameter);
133        mFabSmallDiameter = getResources().getDimensionPixelOffset(
134                R.dimen.end_call_floating_action_button_small_diameter);
135    }
136
137    @Override
138    public void onActivityCreated(Bundle savedInstanceState) {
139        super.onActivityCreated(savedInstanceState);
140
141        final CallList calls = CallList.getInstance();
142        final Call call = calls.getFirstCall();
143        getPresenter().init(getActivity(), call);
144    }
145
146    @Override
147    public View onCreateView(LayoutInflater inflater, ViewGroup container,
148            Bundle savedInstanceState) {
149        mTranslationOffset =
150                getResources().getDimensionPixelSize(R.dimen.call_card_anim_translate_y_offset);
151
152        return inflater.inflate(R.layout.call_card_fragment, container, false);
153    }
154
155    @Override
156    public void onViewCreated(View view, Bundle savedInstanceState) {
157        super.onViewCreated(view, savedInstanceState);
158
159        mPulseAnimation =
160                AnimationUtils.loadAnimation(view.getContext(), R.anim.call_status_pulse);
161
162        mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber);
163        mPrimaryName = (TextView) view.findViewById(R.id.name);
164        mNumberLabel = (TextView) view.findViewById(R.id.label);
165        mSecondaryCallInfo = view.findViewById(R.id.secondary_call_info);
166        mSecondaryCallProviderInfo = view.findViewById(R.id.secondary_call_provider_info);
167        mPhoto = (ImageView) view.findViewById(R.id.photo);
168        mCallStateIcon = (ImageView) view.findViewById(R.id.callStateIcon);
169        mCallStateVideoCallIcon = (ImageView) view.findViewById(R.id.videoCallIcon);
170        mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel);
171        mHdAudioIcon = (ImageView) view.findViewById(R.id.hdAudioIcon);
172        mCallNumberAndLabel = view.findViewById(R.id.labelAndNumber);
173        mCallTypeLabel = (TextView) view.findViewById(R.id.callTypeLabel);
174        mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime);
175        mPrimaryCallCardContainer = view.findViewById(R.id.primary_call_info_container);
176        mPrimaryCallInfo = (ViewGroup) view.findViewById(R.id.primary_call_banner);
177        mCallButtonsContainer = view.findViewById(R.id.callButtonFragment);
178        mInCallMessageLabel = (TextView) view.findViewById(R.id.connectionServiceMessage);
179        mProgressSpinner = view.findViewById(R.id.progressSpinner);
180
181        mFloatingActionButtonContainer = view.findViewById(
182                R.id.floating_end_call_action_button_container);
183        mFloatingActionButton = (ImageButton) view.findViewById(
184                R.id.floating_end_call_action_button);
185        mFloatingActionButton.setOnClickListener(new View.OnClickListener() {
186            @Override
187            public void onClick(View v) {
188                getPresenter().endCallClicked();
189            }
190        });
191        mFloatingActionButtonController = new FloatingActionButtonController(getActivity(),
192                mFloatingActionButtonContainer, mFloatingActionButton);
193
194        mSecondaryCallInfo.setOnClickListener(new View.OnClickListener() {
195            @Override
196            public void onClick(View v) {
197                getPresenter().secondaryInfoClicked();
198                updateFabPositionForSecondaryCallInfo();
199            }
200        });
201
202        mCallStateButton = view.findViewById(R.id.callStateButton);
203        mCallStateButton.setOnClickListener(new View.OnClickListener() {
204            @Override
205            public void onClick(View v) {
206                getPresenter().onCallStateButtonTouched();
207            }
208        });
209
210        mManageConferenceCallButton = view.findViewById(R.id.manage_conference_call_button);
211        mManageConferenceCallButton.setOnClickListener(new View.OnClickListener() {
212            @Override
213            public void onClick(View v) {
214                InCallActivity activity = (InCallActivity) getActivity();
215                activity.showConferenceFragment(true);
216            }
217        });
218
219        mPrimaryName.setElegantTextHeight(false);
220        mCallStateLabel.setElegantTextHeight(false);
221
222        final LayoutTransition transition = mPrimaryCallInfo.getLayoutTransition();
223        transition.enableTransitionType(LayoutTransition.CHANGING);
224    }
225
226    @Override
227    public void setVisible(boolean on) {
228        if (on) {
229            getView().setVisibility(View.VISIBLE);
230        } else {
231            getView().setVisibility(View.INVISIBLE);
232        }
233    }
234
235    /**
236     * Hides or shows the progress spinner.
237     *
238     * @param visible {@code True} if the progress spinner should be visible.
239     */
240    @Override
241    public void setProgressSpinnerVisible(boolean visible) {
242        mProgressSpinner.setVisibility(visible ? View.VISIBLE : View.GONE);
243    }
244
245    /**
246     * Sets the visibility of the primary call card.
247     * Ensures that when the primary call card is hidden, the video surface slides over to fill the
248     * entire screen.
249     *
250     * @param visible {@code True} if the primary call card should be visible.
251     */
252    @Override
253    public void setCallCardVisible(final boolean visible) {
254        // When animating the hide/show of the views in a landscape layout, we need to take into
255        // account whether we are in a left-to-right locale or a right-to-left locale and adjust
256        // the animations accordingly.
257        final boolean isLayoutRtl = InCallPresenter.isRtl();
258
259        // Retrieve here since at fragment creation time the incoming video view is not inflated.
260        final View videoView = getView().findViewById(R.id.incomingVideo);
261
262        // Determine how much space there is below or to the side of the call card.
263        final float spaceBesideCallCard = getSpaceBesideCallCard();
264
265        // We need to translate the video surface, but we need to know its position after the layout
266        // has occurred so use a {@code ViewTreeObserver}.
267        final ViewTreeObserver observer = getView().getViewTreeObserver();
268        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
269            @Override
270            public boolean onPreDraw() {
271                // We don't want to continue getting called.
272                if (observer.isAlive()) {
273                    observer.removeOnPreDrawListener(this);
274                }
275
276                float videoViewTranslation = 0f;
277
278                // Translate the call card to its pre-animation state.
279                if (mIsLandscape) {
280                    float translationX = mPrimaryCallCardContainer.getWidth();
281                    translationX *= isLayoutRtl ? 1 : -1;
282
283                    mPrimaryCallCardContainer.setTranslationX(visible ? translationX : 0);
284
285                    if (visible) {
286                        videoViewTranslation = videoView.getWidth() / 2 - spaceBesideCallCard / 2;
287                        videoViewTranslation *= isLayoutRtl ? -1 : 1;
288                    }
289                } else {
290                    mPrimaryCallCardContainer.setTranslationY(visible ?
291                            -mPrimaryCallCardContainer.getHeight() : 0);
292
293                    if (visible) {
294                        videoViewTranslation = videoView.getHeight() / 2 - spaceBesideCallCard / 2;
295                    }
296                }
297
298                // Perform animation of video view.
299                ViewPropertyAnimator videoViewAnimator = videoView.animate()
300                        .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
301                        .setDuration(mVideoAnimationDuration);
302                if (mIsLandscape) {
303                    videoViewAnimator
304                            .translationX(videoViewTranslation)
305                            .start();
306                } else {
307                    videoViewAnimator
308                            .translationY(videoViewTranslation)
309                            .start();
310                }
311                videoViewAnimator.start();
312
313                // Animate the call card sliding.
314                ViewPropertyAnimator callCardAnimator = mPrimaryCallCardContainer.animate()
315                        .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
316                        .setDuration(mVideoAnimationDuration)
317                        .setListener(new AnimatorListenerAdapter() {
318                            @Override
319                            public void onAnimationEnd(Animator animation) {
320                                super.onAnimationEnd(animation);
321                                if (!visible) {
322                                    mPrimaryCallCardContainer.setVisibility(View.GONE);
323                                }
324                            }
325
326                            @Override
327                            public void onAnimationStart(Animator animation) {
328                                super.onAnimationStart(animation);
329                                if (visible) {
330                                    mPrimaryCallCardContainer.setVisibility(View.VISIBLE);
331                                }
332                            }
333                        });
334
335                if (mIsLandscape) {
336                    float translationX = mPrimaryCallCardContainer.getWidth();
337                    translationX *= isLayoutRtl ? 1 : -1;
338                    callCardAnimator
339                            .translationX(visible ? 0 : translationX)
340                            .start();
341                } else {
342                    callCardAnimator
343                            .translationY(visible ? 0 : -mPrimaryCallCardContainer.getHeight())
344                            .start();
345                }
346
347                return true;
348            }
349        });
350    }
351
352    /**
353     * Determines the amount of space below the call card for portrait layouts), or beside the
354     * call card for landscape layouts.
355     *
356     * @return The amount of space below or beside the call card.
357     */
358    public float getSpaceBesideCallCard() {
359        if (mIsLandscape) {
360            return getView().getWidth() - mPrimaryCallCardContainer.getWidth();
361        } else {
362            final int callCardHeight;
363            // Retrieve the actual height of the call card, independent of whether or not the
364            // outgoing call animation is in progress. The animation does not run in landscape mode
365            // so this only needs to be done for portrait.
366            if (mPrimaryCallCardContainer.getTag(R.id.view_tag_callcard_actual_height) != null) {
367                callCardHeight = (int) mPrimaryCallCardContainer.getTag(
368                        R.id.view_tag_callcard_actual_height);
369            } else {
370                callCardHeight = mPrimaryCallCardContainer.getHeight();
371            }
372            return getView().getHeight() - callCardHeight;
373        }
374    }
375
376    @Override
377    public void setPrimaryName(String name, boolean nameIsNumber) {
378        if (TextUtils.isEmpty(name)) {
379            mPrimaryName.setText(null);
380        } else {
381            mPrimaryName.setText(nameIsNumber
382                    ? PhoneNumberUtils.getPhoneTtsSpannable(name)
383                    : name);
384
385            // Set direction of the name field
386            int nameDirection = View.TEXT_DIRECTION_INHERIT;
387            if (nameIsNumber) {
388                nameDirection = View.TEXT_DIRECTION_LTR;
389            }
390            mPrimaryName.setTextDirection(nameDirection);
391        }
392    }
393
394    @Override
395    public void setPrimaryImage(Drawable image) {
396        if (image != null) {
397            setDrawableToImageView(mPhoto, image);
398        }
399    }
400
401    @Override
402    public void setPrimaryPhoneNumber(String number) {
403        // Set the number
404        if (TextUtils.isEmpty(number)) {
405            mPhoneNumber.setText(null);
406            mPhoneNumber.setVisibility(View.GONE);
407        } else {
408            mPhoneNumber.setText(PhoneNumberUtils.getPhoneTtsSpannable(number));
409            mPhoneNumber.setVisibility(View.VISIBLE);
410            mPhoneNumber.setTextDirection(View.TEXT_DIRECTION_LTR);
411        }
412    }
413
414    @Override
415    public void setPrimaryLabel(String label) {
416        if (!TextUtils.isEmpty(label)) {
417            mNumberLabel.setText(label);
418            mNumberLabel.setVisibility(View.VISIBLE);
419        } else {
420            mNumberLabel.setVisibility(View.GONE);
421        }
422
423    }
424
425    @Override
426    public void setPrimary(String number, String name, boolean nameIsNumber, String label,
427            Drawable photo, boolean isSipCall) {
428        Log.d(this, "Setting primary call");
429        // set the name field.
430        setPrimaryName(name, nameIsNumber);
431
432        if (TextUtils.isEmpty(number) && TextUtils.isEmpty(label)) {
433            mCallNumberAndLabel.setVisibility(View.GONE);
434            mElapsedTime.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
435        } else {
436            mCallNumberAndLabel.setVisibility(View.VISIBLE);
437            mElapsedTime.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
438        }
439
440        setPrimaryPhoneNumber(number);
441
442        // Set the label (Mobile, Work, etc)
443        setPrimaryLabel(label);
444
445        showInternetCallLabel(isSipCall);
446
447        setDrawableToImageView(mPhoto, photo);
448    }
449
450    @Override
451    public void setSecondary(boolean show, String name, boolean nameIsNumber, String label,
452            String providerLabel, boolean isConference) {
453
454        if (show != mSecondaryCallInfo.isShown()) {
455            updateFabPositionForSecondaryCallInfo();
456        }
457
458        if (show) {
459            boolean hasProvider = !TextUtils.isEmpty(providerLabel);
460            showAndInitializeSecondaryCallInfo(hasProvider);
461
462            mSecondaryCallConferenceCallIcon.setVisibility(isConference ? View.VISIBLE : View.GONE);
463
464            mSecondaryCallName.setText(nameIsNumber
465                    ? PhoneNumberUtils.getPhoneTtsSpannable(name)
466                    : name);
467            if (hasProvider) {
468                mSecondaryCallProviderLabel.setText(providerLabel);
469            }
470
471            int nameDirection = View.TEXT_DIRECTION_INHERIT;
472            if (nameIsNumber) {
473                nameDirection = View.TEXT_DIRECTION_LTR;
474            }
475            mSecondaryCallName.setTextDirection(nameDirection);
476        } else {
477            mSecondaryCallInfo.setVisibility(View.GONE);
478        }
479    }
480
481    @Override
482    public void setCallState(
483            int state,
484            int videoState,
485            int sessionModificationState,
486            DisconnectCause disconnectCause,
487            String connectionLabel,
488            Drawable callStateIcon,
489            String gatewayNumber) {
490        boolean isGatewayCall = !TextUtils.isEmpty(gatewayNumber);
491        CharSequence callStateLabel = getCallStateLabelFromState(state, videoState,
492                sessionModificationState, disconnectCause, connectionLabel, isGatewayCall);
493
494        Log.v(this, "setCallState " + callStateLabel);
495        Log.v(this, "DisconnectCause " + disconnectCause.toString());
496        Log.v(this, "gateway " + connectionLabel + gatewayNumber);
497
498        if (TextUtils.equals(callStateLabel, mCallStateLabel.getText())) {
499            // Nothing to do if the labels are the same
500            return;
501        }
502
503        // Update the call state label and icon.
504        if (!TextUtils.isEmpty(callStateLabel)) {
505            mCallStateLabel.setText(callStateLabel);
506            mCallStateLabel.setAlpha(1);
507            mCallStateLabel.setVisibility(View.VISIBLE);
508
509            if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED) {
510                mCallStateLabel.clearAnimation();
511            } else {
512                mCallStateLabel.startAnimation(mPulseAnimation);
513            }
514        } else {
515            Animation callStateLabelAnimation = mCallStateLabel.getAnimation();
516            if (callStateLabelAnimation != null) {
517                callStateLabelAnimation.cancel();
518            }
519            mCallStateLabel.setText(null);
520            mCallStateLabel.setAlpha(0);
521            mCallStateLabel.setVisibility(View.GONE);
522        }
523
524        if (callStateIcon != null) {
525            mCallStateIcon.setVisibility(View.VISIBLE);
526            // Invoke setAlpha(float) instead of setAlpha(int) to set the view's alpha. This is
527            // needed because the pulse animation operates on the view alpha.
528            mCallStateIcon.setAlpha(1.0f);
529            mCallStateIcon.setImageDrawable(callStateIcon);
530
531            if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED
532                    || TextUtils.isEmpty(callStateLabel)) {
533                mCallStateIcon.clearAnimation();
534            } else {
535                mCallStateIcon.startAnimation(mPulseAnimation);
536            }
537
538            if (callStateIcon instanceof AnimationDrawable) {
539                ((AnimationDrawable) callStateIcon).start();
540            }
541        } else {
542            Animation callStateIconAnimation = mCallStateIcon.getAnimation();
543            if (callStateIconAnimation != null) {
544                callStateIconAnimation.cancel();
545            }
546
547            // Invoke setAlpha(float) instead of setAlpha(int) to set the view's alpha. This is
548            // needed because the pulse animation operates on the view alpha.
549            mCallStateIcon.setAlpha(0.0f);
550            mCallStateIcon.setVisibility(View.GONE);
551        }
552
553        if (VideoProfile.VideoState.isBidirectional(videoState)
554                || (state == Call.State.ACTIVE && sessionModificationState
555                        == Call.SessionModificationState.WAITING_FOR_RESPONSE)) {
556            mCallStateVideoCallIcon.setVisibility(View.VISIBLE);
557        } else {
558            mCallStateVideoCallIcon.setVisibility(View.GONE);
559        }
560
561        if (state == Call.State.INCOMING) {
562            if (callStateLabel != null) {
563                getView().announceForAccessibility(callStateLabel);
564            }
565            if (mPrimaryName.getText() != null) {
566                getView().announceForAccessibility(mPrimaryName.getText());
567            }
568        }
569    }
570
571    @Override
572    public void setCallbackNumber(String callbackNumber, boolean isEmergencyCall) {
573        if (mInCallMessageLabel == null) {
574            return;
575        }
576
577        if (TextUtils.isEmpty(callbackNumber)) {
578            mInCallMessageLabel.setVisibility(View.GONE);
579            return;
580        }
581
582        // TODO: The new Locale-specific methods don't seem to be working. Revisit this.
583        callbackNumber = PhoneNumberUtils.formatNumber(callbackNumber);
584
585        int stringResourceId = isEmergencyCall ? R.string.card_title_callback_number_emergency
586                : R.string.card_title_callback_number;
587
588        String text = getString(stringResourceId, callbackNumber);
589        mInCallMessageLabel.setText(text);
590
591        mInCallMessageLabel.setVisibility(View.VISIBLE);
592    }
593
594    private void showInternetCallLabel(boolean show) {
595        if (show) {
596            final String label = getView().getContext().getString(
597                    R.string.incall_call_type_label_sip);
598            mCallTypeLabel.setVisibility(View.VISIBLE);
599            mCallTypeLabel.setText(label);
600        } else {
601            mCallTypeLabel.setVisibility(View.GONE);
602        }
603    }
604
605    @Override
606    public void setPrimaryCallElapsedTime(boolean show, long duration) {
607        if (show) {
608            if (mElapsedTime.getVisibility() != View.VISIBLE) {
609                AnimUtils.fadeIn(mElapsedTime, AnimUtils.DEFAULT_DURATION);
610            }
611            String callTimeElapsed = DateUtils.formatElapsedTime(duration / 1000);
612            String durationDescription = InCallDateUtils.formatDetailedDuration(duration);
613            mElapsedTime.setText(callTimeElapsed);
614            mElapsedTime.setContentDescription(durationDescription);
615        } else {
616            // hide() animation has no effect if it is already hidden.
617            AnimUtils.fadeOut(mElapsedTime, AnimUtils.DEFAULT_DURATION);
618        }
619    }
620
621    private void setDrawableToImageView(ImageView view, Drawable photo) {
622        if (photo == null) {
623            photo = ContactInfoCache.getInstance(
624                    view.getContext()).getDefaultContactPhotoDrawable();
625        }
626
627        if (mPrimaryPhotoDrawable == photo) {
628            return;
629        }
630        mPrimaryPhotoDrawable = photo;
631
632        final Drawable current = view.getDrawable();
633        if (current == null) {
634            view.setImageDrawable(photo);
635            AnimUtils.fadeIn(mElapsedTime, AnimUtils.DEFAULT_DURATION);
636        } else {
637            // Cross fading is buggy and not noticable due to the multiple calls to this method
638            // that switch drawables in the middle of the cross-fade animations. Just set the
639            // photo directly instead.
640            view.setImageDrawable(photo);
641            view.setVisibility(View.VISIBLE);
642        }
643    }
644
645    /**
646     * Gets the call state label based on the state of the call or cause of disconnect.
647     *
648     * Additional labels are applied as follows:
649     *         1. All outgoing calls with display "Calling via [Provider]".
650     *         2. Ongoing calls will display the name of the provider.
651     *         3. Incoming calls will only display "Incoming via..." for accounts.
652     *         4. Video calls, and session modification states (eg. requesting video).
653     */
654    private CharSequence getCallStateLabelFromState(int state, int videoState,
655            int sessionModificationState, DisconnectCause disconnectCause, String label,
656            boolean isGatewayCall) {
657        final Context context = getView().getContext();
658        CharSequence callStateLabel = null;  // Label to display as part of the call banner
659
660        boolean isSpecialCall = label != null;
661        boolean isAccount = isSpecialCall && !isGatewayCall;
662
663        switch  (state) {
664            case Call.State.IDLE:
665                // "Call state" is meaningless in this state.
666                break;
667            case Call.State.ACTIVE:
668                // We normally don't show a "call state label" at all in this state
669                // (but we can use the call state label to display the provider name).
670                if (isAccount) {
671                    callStateLabel = label;
672                } else if (sessionModificationState
673                        == Call.SessionModificationState.REQUEST_FAILED) {
674                    callStateLabel = context.getString(R.string.card_title_video_call_error);
675                } else if (sessionModificationState
676                        == Call.SessionModificationState.WAITING_FOR_RESPONSE) {
677                    callStateLabel = context.getString(R.string.card_title_video_call_requesting);
678                } else if (VideoProfile.VideoState.isBidirectional(videoState)) {
679                    callStateLabel = context.getString(R.string.card_title_video_call);
680                }
681                break;
682            case Call.State.ONHOLD:
683                callStateLabel = context.getString(R.string.card_title_on_hold);
684                break;
685            case Call.State.CONNECTING:
686            case Call.State.DIALING:
687                if (isSpecialCall) {
688                    callStateLabel = context.getString(R.string.calling_via_template, label);
689                } else {
690                    callStateLabel = context.getString(R.string.card_title_dialing);
691                }
692                break;
693            case Call.State.REDIALING:
694                callStateLabel = context.getString(R.string.card_title_redialing);
695                break;
696            case Call.State.INCOMING:
697            case Call.State.CALL_WAITING:
698                if (isAccount) {
699                    callStateLabel = context.getString(R.string.incoming_via_template, label);
700                } else if (VideoProfile.VideoState.isBidirectional(videoState)) {
701                    callStateLabel = context.getString(R.string.notification_incoming_video_call);
702                } else {
703                    callStateLabel = context.getString(R.string.card_title_incoming_call);
704                }
705                break;
706            case Call.State.DISCONNECTING:
707                // While in the DISCONNECTING state we display a "Hanging up"
708                // message in order to make the UI feel more responsive.  (In
709                // GSM it's normal to see a delay of a couple of seconds while
710                // negotiating the disconnect with the network, so the "Hanging
711                // up" state at least lets the user know that we're doing
712                // something.  This state is currently not used with CDMA.)
713                callStateLabel = context.getString(R.string.card_title_hanging_up);
714                break;
715            case Call.State.DISCONNECTED:
716                callStateLabel = disconnectCause.getLabel();
717                if (TextUtils.isEmpty(callStateLabel)) {
718                    callStateLabel = context.getString(R.string.card_title_call_ended);
719                }
720                break;
721            case Call.State.CONFERENCED:
722                callStateLabel = context.getString(R.string.card_title_conf_call);
723                break;
724            default:
725                Log.wtf(this, "updateCallStateWidgets: unexpected call: " + state);
726        }
727        return callStateLabel;
728    }
729
730    private void showAndInitializeSecondaryCallInfo(boolean hasProvider) {
731        mSecondaryCallInfo.setVisibility(View.VISIBLE);
732
733        // mSecondaryCallName is initialized here (vs. onViewCreated) because it is inaccessible
734        // until mSecondaryCallInfo is inflated in the call above.
735        if (mSecondaryCallName == null) {
736            mSecondaryCallName = (TextView) getView().findViewById(R.id.secondaryCallName);
737            mSecondaryCallConferenceCallIcon =
738                    getView().findViewById(R.id.secondaryCallConferenceCallIcon);
739        }
740
741        if (mSecondaryCallProviderLabel == null && hasProvider) {
742            mSecondaryCallProviderInfo.setVisibility(View.VISIBLE);
743            mSecondaryCallProviderLabel = (TextView) getView()
744                    .findViewById(R.id.secondaryCallProviderLabel);
745        }
746    }
747
748    public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
749        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
750            dispatchPopulateAccessibilityEvent(event, mCallStateLabel);
751            dispatchPopulateAccessibilityEvent(event, mPrimaryName);
752            dispatchPopulateAccessibilityEvent(event, mPhoneNumber);
753            return;
754        }
755        dispatchPopulateAccessibilityEvent(event, mCallStateLabel);
756        dispatchPopulateAccessibilityEvent(event, mPrimaryName);
757        dispatchPopulateAccessibilityEvent(event, mPhoneNumber);
758        dispatchPopulateAccessibilityEvent(event, mCallTypeLabel);
759        dispatchPopulateAccessibilityEvent(event, mSecondaryCallName);
760        dispatchPopulateAccessibilityEvent(event, mSecondaryCallProviderLabel);
761
762        return;
763    }
764
765    @Override
766    public void setEndCallButtonEnabled(boolean enabled, boolean animate) {
767        if (enabled != mFloatingActionButton.isEnabled()) {
768            if (animate) {
769                if (enabled) {
770                    mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY);
771                } else {
772                    mFloatingActionButtonController.scaleOut();
773                }
774            } else {
775                if (enabled) {
776                    mFloatingActionButtonContainer.setScaleX(1);
777                    mFloatingActionButtonContainer.setScaleY(1);
778                    mFloatingActionButtonContainer.setVisibility(View.VISIBLE);
779                } else {
780                    mFloatingActionButtonContainer.setVisibility(View.GONE);
781                }
782            }
783            mFloatingActionButton.setEnabled(enabled);
784            updateFabPosition();
785        }
786    }
787
788    /**
789     * Changes the visibility of the HD audio icon.
790     *
791     * @param visible {@code true} if the UI should show the HD audio icon.
792     */
793    @Override
794    public void showHdAudioIndicator(boolean visible) {
795        mHdAudioIcon.setVisibility(visible ? View.VISIBLE : View.GONE);
796    }
797
798    /**
799     * Changes the visibility of the "manage conference call" button.
800     *
801     * @param visible Whether to set the button to be visible or not.
802     */
803    @Override
804    public void showManageConferenceCallButton(boolean visible) {
805        mManageConferenceCallButton.setVisibility(visible ? View.VISIBLE : View.GONE);
806    }
807
808    /**
809     * Determines the current visibility of the manage conference button.
810     *
811     * @return {@code true} if the button is visible.
812     */
813    @Override
814    public boolean isManageConferenceVisible() {
815        return mManageConferenceCallButton.getVisibility() == View.VISIBLE;
816    }
817
818    /**
819     * Get the overall InCallUI background colors and apply to call card.
820     */
821    public void updateColors() {
822        MaterialPalette themeColors = InCallPresenter.getInstance().getThemeColors();
823
824        if (mCurrentThemeColors != null && mCurrentThemeColors.equals(themeColors)) {
825            return;
826        }
827
828        mPrimaryCallCardContainer.setBackgroundColor(themeColors.mPrimaryColor);
829        mCallButtonsContainer.setBackgroundColor(themeColors.mPrimaryColor);
830
831        mCurrentThemeColors = themeColors;
832    }
833
834    private void dispatchPopulateAccessibilityEvent(AccessibilityEvent event, View view) {
835        if (view == null) return;
836        final List<CharSequence> eventText = event.getText();
837        int size = eventText.size();
838        view.dispatchPopulateAccessibilityEvent(event);
839        // if no text added write null to keep relative position
840        if (size == eventText.size()) {
841            eventText.add(null);
842        }
843    }
844
845    @Override
846    public void animateForNewOutgoingCall() {
847        final ViewGroup parent = (ViewGroup) mPrimaryCallCardContainer.getParent();
848
849        final ViewTreeObserver observer = getView().getViewTreeObserver();
850
851        final LayoutTransition transition = mPrimaryCallInfo.getLayoutTransition();
852        transition.disableTransitionType(LayoutTransition.CHANGING);
853
854        observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
855            @Override
856            public void onGlobalLayout() {
857                final ViewTreeObserver observer = getView().getViewTreeObserver();
858                if (!observer.isAlive()) {
859                    return;
860                }
861                observer.removeOnGlobalLayoutListener(this);
862
863                final LayoutIgnoringListener listener = new LayoutIgnoringListener();
864                mPrimaryCallCardContainer.addOnLayoutChangeListener(listener);
865
866                // Prepare the state of views before the slide animation
867                final int originalHeight = mPrimaryCallCardContainer.getHeight();
868                mPrimaryCallCardContainer.setTag(R.id.view_tag_callcard_actual_height,
869                        originalHeight);
870                mPrimaryCallCardContainer.setBottom(parent.getHeight());
871
872                // Set up FAB.
873                mFloatingActionButtonContainer.setVisibility(View.GONE);
874                mFloatingActionButtonController.setScreenWidth(parent.getWidth());
875
876                mCallButtonsContainer.setAlpha(0);
877                mCallStateLabel.setAlpha(0);
878                mPrimaryName.setAlpha(0);
879                mCallTypeLabel.setAlpha(0);
880                mCallNumberAndLabel.setAlpha(0);
881
882                final Animator animator = getShrinkAnimator(parent.getHeight(), originalHeight);
883
884                animator.addListener(new AnimatorListenerAdapter() {
885                    @Override
886                    public void onAnimationEnd(Animator animation) {
887                        mPrimaryCallCardContainer.setTag(R.id.view_tag_callcard_actual_height,
888                                null);
889                        setViewStatePostAnimation(listener);
890                    }
891                });
892                animator.start();
893            }
894        });
895    }
896
897    public void onDialpadVisibilityChange(boolean isShown) {
898        mIsDialpadShowing = isShown;
899        updateFabPosition();
900    }
901
902    private void updateFabPosition() {
903        int offsetY = 0;
904        if (!mIsDialpadShowing) {
905            offsetY = mFloatingActionButtonVerticalOffset;
906            if (mSecondaryCallInfo.isShown()) {
907                offsetY -= mSecondaryCallInfo.getHeight();
908            }
909        }
910
911        mFloatingActionButtonController.align(
912                mIsLandscape ? FloatingActionButtonController.ALIGN_QUARTER_END
913                        : FloatingActionButtonController.ALIGN_MIDDLE,
914                0 /* offsetX */,
915                offsetY,
916                true);
917
918        mFloatingActionButtonController.resize(
919                mIsDialpadShowing ? mFabSmallDiameter : mFabNormalDiameter, true);
920    }
921
922    @Override
923    public void onResume() {
924        super.onResume();
925        // If the previous launch animation is still running, cancel it so that we don't get
926        // stuck in an intermediate animation state.
927        if (mAnimatorSet != null && mAnimatorSet.isRunning()) {
928            mAnimatorSet.cancel();
929        }
930
931        mIsLandscape = getResources().getConfiguration().orientation
932                == Configuration.ORIENTATION_LANDSCAPE;
933
934        final ViewGroup parent = ((ViewGroup) mPrimaryCallCardContainer.getParent());
935        final ViewTreeObserver observer = parent.getViewTreeObserver();
936        parent.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
937            @Override
938            public void onGlobalLayout() {
939                ViewTreeObserver viewTreeObserver = observer;
940                if (!viewTreeObserver.isAlive()) {
941                    viewTreeObserver = parent.getViewTreeObserver();
942                }
943                viewTreeObserver.removeOnGlobalLayoutListener(this);
944                mFloatingActionButtonController.setScreenWidth(parent.getWidth());
945                updateFabPosition();
946            }
947        });
948
949        updateColors();
950    }
951
952    /**
953     * Adds a global layout listener to update the FAB's positioning on the next layout. This allows
954     * us to position the FAB after the secondary call info's height has been calculated.
955     */
956    private void updateFabPositionForSecondaryCallInfo() {
957        mSecondaryCallInfo.getViewTreeObserver().addOnGlobalLayoutListener(
958                new ViewTreeObserver.OnGlobalLayoutListener() {
959                    @Override
960                    public void onGlobalLayout() {
961                        final ViewTreeObserver observer = mSecondaryCallInfo.getViewTreeObserver();
962                        if (!observer.isAlive()) {
963                            return;
964                        }
965                        observer.removeOnGlobalLayoutListener(this);
966
967                        onDialpadVisibilityChange(mIsDialpadShowing);
968                    }
969                });
970    }
971
972    /**
973     * Animator that performs the upwards shrinking animation of the blue call card scrim.
974     * At the start of the animation, each child view is moved downwards by a pre-specified amount
975     * and then translated upwards together with the scrim.
976     */
977    private Animator getShrinkAnimator(int startHeight, int endHeight) {
978        final Animator shrinkAnimator =
979                ObjectAnimator.ofInt(mPrimaryCallCardContainer, "bottom", startHeight, endHeight);
980        shrinkAnimator.setDuration(mShrinkAnimationDuration);
981        shrinkAnimator.addListener(new AnimatorListenerAdapter() {
982            @Override
983            public void onAnimationStart(Animator animation) {
984                assignTranslateAnimation(mCallStateLabel, 1);
985                assignTranslateAnimation(mCallStateIcon, 1);
986                assignTranslateAnimation(mPrimaryName, 2);
987                assignTranslateAnimation(mCallNumberAndLabel, 3);
988                assignTranslateAnimation(mCallTypeLabel, 4);
989                assignTranslateAnimation(mCallButtonsContainer, 5);
990
991                mFloatingActionButton.setEnabled(true);
992            }
993        });
994        shrinkAnimator.setInterpolator(AnimUtils.EASE_IN);
995        return shrinkAnimator;
996    }
997
998    private void assignTranslateAnimation(View view, int offset) {
999        view.setTranslationY(mTranslationOffset * offset);
1000        view.animate().translationY(0).alpha(1).withLayer()
1001                .setDuration(mShrinkAnimationDuration).setInterpolator(AnimUtils.EASE_IN);
1002    }
1003
1004    private void setViewStatePostAnimation(View view) {
1005        view.setTranslationY(0);
1006        view.setAlpha(1);
1007    }
1008
1009    private void setViewStatePostAnimation(OnLayoutChangeListener layoutChangeListener) {
1010        setViewStatePostAnimation(mCallButtonsContainer);
1011        setViewStatePostAnimation(mCallStateLabel);
1012        setViewStatePostAnimation(mPrimaryName);
1013        setViewStatePostAnimation(mCallTypeLabel);
1014        setViewStatePostAnimation(mCallNumberAndLabel);
1015        setViewStatePostAnimation(mCallStateIcon);
1016
1017        mPrimaryCallCardContainer.removeOnLayoutChangeListener(layoutChangeListener);
1018
1019        final LayoutTransition transition = mPrimaryCallInfo.getLayoutTransition();
1020        transition.enableTransitionType(LayoutTransition.CHANGING);
1021
1022        mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY);
1023    }
1024
1025    private final class LayoutIgnoringListener implements View.OnLayoutChangeListener {
1026        @Override
1027        public void onLayoutChange(View v,
1028                int left,
1029                int top,
1030                int right,
1031                int bottom,
1032                int oldLeft,
1033                int oldTop,
1034                int oldRight,
1035                int oldBottom) {
1036            v.setLeft(oldLeft);
1037            v.setRight(oldRight);
1038            v.setTop(oldTop);
1039            v.setBottom(oldBottom);
1040        }
1041    }
1042}
1043