Home | History | Annotate | Download | only in incallui
      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.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.AnimatorSet;
     22 import android.animation.ObjectAnimator;
     23 import android.content.Context;
     24 import android.graphics.Bitmap;
     25 import android.graphics.Canvas;
     26 import android.graphics.drawable.AnimationDrawable;
     27 import android.graphics.drawable.BitmapDrawable;
     28 import android.graphics.drawable.Drawable;
     29 import android.graphics.drawable.GradientDrawable;
     30 import android.os.Bundle;
     31 import android.os.Handler;
     32 import android.os.Looper;
     33 import android.os.Trace;
     34 import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
     35 import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
     36 import android.telecom.DisconnectCause;
     37 import android.telephony.PhoneNumberUtils;
     38 import android.text.TextUtils;
     39 import android.text.format.DateUtils;
     40 import android.view.LayoutInflater;
     41 import android.view.View;
     42 import android.view.View.OnLayoutChangeListener;
     43 import android.view.ViewGroup;
     44 import android.view.ViewPropertyAnimator;
     45 import android.view.ViewTreeObserver;
     46 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
     47 import android.view.accessibility.AccessibilityEvent;
     48 import android.view.accessibility.AccessibilityManager;
     49 import android.view.animation.Animation;
     50 import android.view.animation.AnimationUtils;
     51 import android.widget.ImageButton;
     52 import android.widget.ImageView;
     53 import android.widget.LinearLayout;
     54 import android.widget.ListAdapter;
     55 import android.widget.ListView;
     56 import android.widget.TextView;
     57 import android.widget.Toast;
     58 
     59 import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
     60 import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
     61 import com.android.contacts.common.widget.FloatingActionButtonController;
     62 import com.android.dialer.R;
     63 import com.android.phone.common.animation.AnimUtils;
     64 
     65 import java.util.List;
     66 
     67 /**
     68  * Fragment for call card.
     69  */
     70 public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPresenter.CallCardUi>
     71         implements CallCardPresenter.CallCardUi {
     72     private static final String TAG = "CallCardFragment";
     73 
     74     /**
     75      * Internal class which represents the call state label which is to be applied.
     76      */
     77     private class CallStateLabel {
     78         private CharSequence mCallStateLabel;
     79         private boolean mIsAutoDismissing;
     80 
     81         public CallStateLabel(CharSequence callStateLabel, boolean isAutoDismissing) {
     82             mCallStateLabel = callStateLabel;
     83             mIsAutoDismissing = isAutoDismissing;
     84         }
     85 
     86         public CharSequence getCallStateLabel() {
     87             return mCallStateLabel;
     88         }
     89 
     90         /**
     91          * Determines if the call state label should auto-dismiss.
     92          *
     93          * @return {@code true} if the call state label should auto-dismiss.
     94          */
     95         public boolean isAutoDismissing() {
     96             return mIsAutoDismissing;
     97         }
     98     };
     99 
    100     private static final String IS_DIALPAD_SHOWING_KEY = "is_dialpad_showing";
    101 
    102     /**
    103      * The duration of time (in milliseconds) a call state label should remain visible before
    104      * resetting to its previous value.
    105      */
    106     private static final long CALL_STATE_LABEL_RESET_DELAY_MS = 3000;
    107     /**
    108      * Amount of time to wait before sending an announcement via the accessibility manager.
    109      * When the call state changes to an outgoing or incoming state for the first time, the
    110      * UI can often be changing due to call updates or contact lookup. This allows the UI
    111      * to settle to a stable state to ensure that the correct information is announced.
    112      */
    113     private static final long ACCESSIBILITY_ANNOUNCEMENT_DELAY_MS = 500;
    114 
    115     private AnimatorSet mAnimatorSet;
    116     private int mShrinkAnimationDuration;
    117     private int mFabNormalDiameter;
    118     private int mFabSmallDiameter;
    119     private boolean mIsLandscape;
    120     private boolean mHasLargePhoto;
    121     private boolean mIsDialpadShowing;
    122 
    123     // Primary caller info
    124     private TextView mPhoneNumber;
    125     private TextView mNumberLabel;
    126     private TextView mPrimaryName;
    127     private View mCallStateButton;
    128     private ImageView mCallStateIcon;
    129     private ImageView mCallStateVideoCallIcon;
    130     private TextView mCallStateLabel;
    131     private TextView mCallTypeLabel;
    132     private ImageView mHdAudioIcon;
    133     private ImageView mForwardIcon;
    134     private View mCallNumberAndLabel;
    135     private TextView mElapsedTime;
    136     private Drawable mPrimaryPhotoDrawable;
    137     private TextView mCallSubject;
    138     private ImageView mWorkProfileIcon;
    139 
    140     // Container view that houses the entire primary call card, including the call buttons
    141     private View mPrimaryCallCardContainer;
    142     // Container view that houses the primary call information
    143     private ViewGroup mPrimaryCallInfo;
    144     private View mCallButtonsContainer;
    145     private ImageView mPhotoSmall;
    146 
    147     // Secondary caller info
    148     private View mSecondaryCallInfo;
    149     private TextView mSecondaryCallName;
    150     private View mSecondaryCallProviderInfo;
    151     private TextView mSecondaryCallProviderLabel;
    152     private View mSecondaryCallConferenceCallIcon;
    153     private View mSecondaryCallVideoCallIcon;
    154     private View mProgressSpinner;
    155 
    156     // Call card content
    157     private View mCallCardContent;
    158     private ImageView mPhotoLarge;
    159     private View mContactContext;
    160     private TextView mContactContextTitle;
    161     private ListView mContactContextListView;
    162     private LinearLayout mContactContextListHeaders;
    163 
    164     private View mManageConferenceCallButton;
    165 
    166     // Dark number info bar
    167     private TextView mInCallMessageLabel;
    168 
    169     private FloatingActionButtonController mFloatingActionButtonController;
    170     private View mFloatingActionButtonContainer;
    171     private ImageButton mFloatingActionButton;
    172     private int mFloatingActionButtonVerticalOffset;
    173 
    174     private float mTranslationOffset;
    175     private Animation mPulseAnimation;
    176 
    177     private int mVideoAnimationDuration;
    178     // Whether or not the call card is currently in the process of an animation
    179     private boolean mIsAnimating;
    180 
    181     private MaterialPalette mCurrentThemeColors;
    182 
    183     /**
    184      * Call state label to set when an auto-dismissing call state label is dismissed.
    185      */
    186     private CharSequence mPostResetCallStateLabel;
    187     private boolean mCallStateLabelResetPending = false;
    188     private Handler mHandler;
    189 
    190     /**
    191      * Determines if secondary call info is populated in the secondary call info UI.
    192      */
    193     private boolean mHasSecondaryCallInfo = false;
    194 
    195     @Override
    196     public CallCardPresenter.CallCardUi getUi() {
    197         return this;
    198     }
    199 
    200     @Override
    201     public CallCardPresenter createPresenter() {
    202         return new CallCardPresenter();
    203     }
    204 
    205     @Override
    206     public void onCreate(Bundle savedInstanceState) {
    207         super.onCreate(savedInstanceState);
    208 
    209         mHandler = new Handler(Looper.getMainLooper());
    210         mShrinkAnimationDuration = getResources().getInteger(R.integer.shrink_animation_duration);
    211         mVideoAnimationDuration = getResources().getInteger(R.integer.video_animation_duration);
    212         mFloatingActionButtonVerticalOffset = getResources().getDimensionPixelOffset(
    213                 R.dimen.floating_action_button_vertical_offset);
    214         mFabNormalDiameter = getResources().getDimensionPixelOffset(
    215                 R.dimen.end_call_floating_action_button_diameter);
    216         mFabSmallDiameter = getResources().getDimensionPixelOffset(
    217                 R.dimen.end_call_floating_action_button_small_diameter);
    218 
    219         if (savedInstanceState != null) {
    220             mIsDialpadShowing = savedInstanceState.getBoolean(IS_DIALPAD_SHOWING_KEY, false);
    221         }
    222     }
    223 
    224     @Override
    225     public void onActivityCreated(Bundle savedInstanceState) {
    226         super.onActivityCreated(savedInstanceState);
    227 
    228         final CallList calls = CallList.getInstance();
    229         final Call call = calls.getFirstCall();
    230         getPresenter().init(getActivity(), call);
    231     }
    232 
    233     @Override
    234     public void onSaveInstanceState(Bundle outState) {
    235         outState.putBoolean(IS_DIALPAD_SHOWING_KEY, mIsDialpadShowing);
    236         super.onSaveInstanceState(outState);
    237     }
    238 
    239     @Override
    240     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    241             Bundle savedInstanceState) {
    242         Trace.beginSection(TAG + " onCreate");
    243         mTranslationOffset =
    244                 getResources().getDimensionPixelSize(R.dimen.call_card_anim_translate_y_offset);
    245         final View view = inflater.inflate(R.layout.call_card_fragment, container, false);
    246         Trace.endSection();
    247         return view;
    248     }
    249 
    250     @Override
    251     public void onViewCreated(View view, Bundle savedInstanceState) {
    252         super.onViewCreated(view, savedInstanceState);
    253 
    254         mPulseAnimation =
    255                 AnimationUtils.loadAnimation(view.getContext(), R.anim.call_status_pulse);
    256 
    257         mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber);
    258         mPrimaryName = (TextView) view.findViewById(R.id.name);
    259         mNumberLabel = (TextView) view.findViewById(R.id.label);
    260         mSecondaryCallInfo = view.findViewById(R.id.secondary_call_info);
    261         mSecondaryCallProviderInfo = view.findViewById(R.id.secondary_call_provider_info);
    262         mCallCardContent = view.findViewById(R.id.call_card_content);
    263         mPhotoLarge = (ImageView) view.findViewById(R.id.photoLarge);
    264         mPhotoLarge.setOnClickListener(new View.OnClickListener() {
    265             @Override
    266             public void onClick(View v) {
    267                 getPresenter().onContactPhotoClick();
    268             }
    269         });
    270 
    271         mContactContext = view.findViewById(R.id.contact_context);
    272         mContactContextTitle = (TextView) view.findViewById(R.id.contactContextTitle);
    273         mContactContextListView = (ListView) view.findViewById(R.id.contactContextInfo);
    274         // This layout stores all the list header layouts so they can be easily removed.
    275         mContactContextListHeaders = new LinearLayout(getView().getContext());
    276         mContactContextListView.addHeaderView(mContactContextListHeaders);
    277 
    278         mCallStateIcon = (ImageView) view.findViewById(R.id.callStateIcon);
    279         mCallStateVideoCallIcon = (ImageView) view.findViewById(R.id.videoCallIcon);
    280         mWorkProfileIcon = (ImageView) view.findViewById(R.id.workProfileIcon);
    281         mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel);
    282         mHdAudioIcon = (ImageView) view.findViewById(R.id.hdAudioIcon);
    283         mForwardIcon = (ImageView) view.findViewById(R.id.forwardIcon);
    284         mCallNumberAndLabel = view.findViewById(R.id.labelAndNumber);
    285         mCallTypeLabel = (TextView) view.findViewById(R.id.callTypeLabel);
    286         mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime);
    287         mPrimaryCallCardContainer = view.findViewById(R.id.primary_call_info_container);
    288         mPrimaryCallInfo = (ViewGroup) view.findViewById(R.id.primary_call_banner);
    289         mCallButtonsContainer = view.findViewById(R.id.callButtonFragment);
    290         mPhotoSmall = (ImageView) view.findViewById(R.id.photoSmall);
    291         mPhotoSmall.setVisibility(View.GONE);
    292         mInCallMessageLabel = (TextView) view.findViewById(R.id.connectionServiceMessage);
    293         mProgressSpinner = view.findViewById(R.id.progressSpinner);
    294 
    295         mFloatingActionButtonContainer = view.findViewById(
    296                 R.id.floating_end_call_action_button_container);
    297         mFloatingActionButton = (ImageButton) view.findViewById(
    298                 R.id.floating_end_call_action_button);
    299         mFloatingActionButton.setOnClickListener(new View.OnClickListener() {
    300             @Override
    301             public void onClick(View v) {
    302                 getPresenter().endCallClicked();
    303             }
    304         });
    305         mFloatingActionButtonController = new FloatingActionButtonController(getActivity(),
    306                 mFloatingActionButtonContainer, mFloatingActionButton);
    307 
    308         mSecondaryCallInfo.setOnClickListener(new View.OnClickListener() {
    309             @Override
    310             public void onClick(View v) {
    311                 getPresenter().secondaryInfoClicked();
    312                 updateFabPositionForSecondaryCallInfo();
    313             }
    314         });
    315 
    316         mCallStateButton = view.findViewById(R.id.callStateButton);
    317         mCallStateButton.setOnLongClickListener(new View.OnLongClickListener() {
    318             @Override
    319             public boolean onLongClick(View v) {
    320                 getPresenter().onCallStateButtonTouched();
    321                 return false;
    322             }
    323         });
    324 
    325         mManageConferenceCallButton = view.findViewById(R.id.manage_conference_call_button);
    326         mManageConferenceCallButton.setOnClickListener(new View.OnClickListener() {
    327             @Override
    328             public void onClick(View v) {
    329                 InCallActivity activity = (InCallActivity) getActivity();
    330                 activity.showConferenceFragment(true);
    331             }
    332         });
    333 
    334         mPrimaryName.setElegantTextHeight(false);
    335         mCallStateLabel.setElegantTextHeight(false);
    336         mCallSubject = (TextView) view.findViewById(R.id.callSubject);
    337     }
    338 
    339     @Override
    340     public void setVisible(boolean on) {
    341         if (on) {
    342             getView().setVisibility(View.VISIBLE);
    343         } else {
    344             getView().setVisibility(View.INVISIBLE);
    345         }
    346     }
    347 
    348     /**
    349      * Hides or shows the progress spinner.
    350      *
    351      * @param visible {@code True} if the progress spinner should be visible.
    352      */
    353     @Override
    354     public void setProgressSpinnerVisible(boolean visible) {
    355         mProgressSpinner.setVisibility(visible ? View.VISIBLE : View.GONE);
    356     }
    357 
    358     @Override
    359     public void setContactContextTitle(View headerView) {
    360         mContactContextListHeaders.removeAllViews();
    361         mContactContextListHeaders.addView(headerView);
    362     }
    363 
    364     @Override
    365     public void setContactContextContent(ListAdapter listAdapter) {
    366         mContactContextListView.setAdapter(listAdapter);
    367     }
    368 
    369     @Override
    370     public void showContactContext(boolean show) {
    371         showImageView(mPhotoLarge, !show);
    372         showImageView(mPhotoSmall, show);
    373         mPrimaryCallCardContainer.setElevation(
    374                 show ? 0 : getResources().getDimension(R.dimen.primary_call_elevation));
    375         mContactContext.setVisibility(show ? View.VISIBLE : View.GONE);
    376     }
    377 
    378     /**
    379      * Sets the visibility of the primary call card.
    380      * Ensures that when the primary call card is hidden, the video surface slides over to fill the
    381      * entire screen.
    382      *
    383      * @param visible {@code True} if the primary call card should be visible.
    384      */
    385     @Override
    386     public void setCallCardVisible(final boolean visible) {
    387         Log.v(this, "setCallCardVisible : isVisible = " + visible);
    388         // When animating the hide/show of the views in a landscape layout, we need to take into
    389         // account whether we are in a left-to-right locale or a right-to-left locale and adjust
    390         // the animations accordingly.
    391         final boolean isLayoutRtl = InCallPresenter.isRtl();
    392 
    393         // Retrieve here since at fragment creation time the incoming video view is not inflated.
    394         final View videoView = getView().findViewById(R.id.incomingVideo);
    395         if (videoView == null) {
    396             return;
    397         }
    398 
    399         // Determine how much space there is below or to the side of the call card.
    400         final float spaceBesideCallCard = getSpaceBesideCallCard();
    401 
    402         // We need to translate the video surface, but we need to know its position after the layout
    403         // has occurred so use a {@code ViewTreeObserver}.
    404         final ViewTreeObserver observer = getView().getViewTreeObserver();
    405         observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    406             @Override
    407             public boolean onPreDraw() {
    408                 // We don't want to continue getting called.
    409                 getView().getViewTreeObserver().removeOnPreDrawListener(this);
    410 
    411                 float videoViewTranslation = 0f;
    412 
    413                 // Translate the call card to its pre-animation state.
    414                 if (!mIsLandscape) {
    415                     mPrimaryCallCardContainer.setTranslationY(visible ?
    416                             -mPrimaryCallCardContainer.getHeight() : 0);
    417 
    418                     ViewGroup.LayoutParams p = videoView.getLayoutParams();
    419                     videoViewTranslation = p.height / 2 - spaceBesideCallCard / 2;
    420                 }
    421 
    422                 // Perform animation of video view.
    423                 ViewPropertyAnimator videoViewAnimator = videoView.animate()
    424                         .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
    425                         .setDuration(mVideoAnimationDuration);
    426                 if (mIsLandscape) {
    427                     videoViewAnimator
    428                             .translationX(visible ? videoViewTranslation : 0);
    429                 } else {
    430                     videoViewAnimator
    431                             .translationY(visible ? videoViewTranslation : 0);
    432                 }
    433                 videoViewAnimator.start();
    434 
    435                 // Animate the call card sliding.
    436                 ViewPropertyAnimator callCardAnimator = mPrimaryCallCardContainer.animate()
    437                         .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
    438                         .setDuration(mVideoAnimationDuration)
    439                         .setListener(new AnimatorListenerAdapter() {
    440                             @Override
    441                             public void onAnimationEnd(Animator animation) {
    442                                 super.onAnimationEnd(animation);
    443                                 if (!visible) {
    444                                     mPrimaryCallCardContainer.setVisibility(View.GONE);
    445                                 }
    446                             }
    447 
    448                             @Override
    449                             public void onAnimationStart(Animator animation) {
    450                                 super.onAnimationStart(animation);
    451                                 if (visible) {
    452                                     mPrimaryCallCardContainer.setVisibility(View.VISIBLE);
    453                                 }
    454                             }
    455                         });
    456 
    457                 if (mIsLandscape) {
    458                     float translationX = mPrimaryCallCardContainer.getWidth();
    459                     translationX *= isLayoutRtl ? 1 : -1;
    460                     callCardAnimator
    461                             .translationX(visible ? 0 : translationX)
    462                             .start();
    463                 } else {
    464                     callCardAnimator
    465                             .translationY(visible ? 0 : -mPrimaryCallCardContainer.getHeight())
    466                             .start();
    467                 }
    468 
    469                 return true;
    470             }
    471         });
    472     }
    473 
    474     /**
    475      * Determines the amount of space below the call card for portrait layouts), or beside the
    476      * call card for landscape layouts.
    477      *
    478      * @return The amount of space below or beside the call card.
    479      */
    480     public float getSpaceBesideCallCard() {
    481         if (mIsLandscape) {
    482             return getView().getWidth() - mPrimaryCallCardContainer.getWidth();
    483         } else {
    484             final int callCardHeight;
    485             // Retrieve the actual height of the call card, independent of whether or not the
    486             // outgoing call animation is in progress. The animation does not run in landscape mode
    487             // so this only needs to be done for portrait.
    488             if (mPrimaryCallCardContainer.getTag(R.id.view_tag_callcard_actual_height) != null) {
    489                 callCardHeight = (int) mPrimaryCallCardContainer.getTag(
    490                         R.id.view_tag_callcard_actual_height);
    491             } else {
    492                 callCardHeight = mPrimaryCallCardContainer.getHeight();
    493             }
    494             return getView().getHeight() - callCardHeight;
    495         }
    496     }
    497 
    498     @Override
    499     public void setPrimaryName(String name, boolean nameIsNumber) {
    500         if (TextUtils.isEmpty(name)) {
    501             mPrimaryName.setText(null);
    502         } else {
    503             mPrimaryName.setText(nameIsNumber
    504                     ? PhoneNumberUtilsCompat.createTtsSpannable(name)
    505                     : name);
    506 
    507             // Set direction of the name field
    508             int nameDirection = View.TEXT_DIRECTION_INHERIT;
    509             if (nameIsNumber) {
    510                 nameDirection = View.TEXT_DIRECTION_LTR;
    511             }
    512             mPrimaryName.setTextDirection(nameDirection);
    513         }
    514     }
    515 
    516     /**
    517      * Sets the primary image for the contact photo.
    518      *
    519      * @param image The drawable to set.
    520      * @param isVisible Whether the contact photo should be visible after being set.
    521      */
    522     @Override
    523     public void setPrimaryImage(Drawable image, boolean isVisible) {
    524         if (image != null) {
    525             setDrawableToImageViews(image);
    526             showImageView(mPhotoLarge, isVisible);
    527         }
    528     }
    529 
    530     @Override
    531     public void setPrimaryPhoneNumber(String number) {
    532         // Set the number
    533         if (TextUtils.isEmpty(number)) {
    534             mPhoneNumber.setText(null);
    535             mPhoneNumber.setVisibility(View.GONE);
    536         } else {
    537             mPhoneNumber.setText(PhoneNumberUtilsCompat.createTtsSpannable(number));
    538             mPhoneNumber.setVisibility(View.VISIBLE);
    539             mPhoneNumber.setTextDirection(View.TEXT_DIRECTION_LTR);
    540         }
    541     }
    542 
    543     @Override
    544     public void setPrimaryLabel(String label) {
    545         if (!TextUtils.isEmpty(label)) {
    546             mNumberLabel.setText(label);
    547             mNumberLabel.setVisibility(View.VISIBLE);
    548         } else {
    549             mNumberLabel.setVisibility(View.GONE);
    550         }
    551 
    552     }
    553 
    554     /**
    555      * Sets the primary caller information.
    556      *
    557      * @param number The caller phone number.
    558      * @param name The caller name.
    559      * @param nameIsNumber {@code true} if the name should be shown in place of the phone number.
    560      * @param label The label.
    561      * @param photo The contact photo drawable.
    562      * @param isSipCall {@code true} if this is a SIP call.
    563      * @param isContactPhotoShown {@code true} if the contact photo should be shown (it will be
    564      *      updated even if it is not shown).
    565      * @param isWorkCall Whether the call is placed through a work phone account or caller is a work
    566               contact.
    567      */
    568     @Override
    569     public void setPrimary(String number, String name, boolean nameIsNumber, String label,
    570             Drawable photo, boolean isSipCall, boolean isContactPhotoShown, boolean isWorkCall) {
    571         Log.d(this, "Setting primary call");
    572         // set the name field.
    573         setPrimaryName(name, nameIsNumber);
    574 
    575         if (TextUtils.isEmpty(number) && TextUtils.isEmpty(label)) {
    576             mCallNumberAndLabel.setVisibility(View.GONE);
    577             mElapsedTime.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
    578         } else {
    579             mCallNumberAndLabel.setVisibility(View.VISIBLE);
    580             mElapsedTime.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
    581         }
    582 
    583         setPrimaryPhoneNumber(number);
    584 
    585         // Set the label (Mobile, Work, etc)
    586         setPrimaryLabel(label);
    587 
    588         showInternetCallLabel(isSipCall);
    589 
    590         setDrawableToImageViews(photo);
    591         showImageView(mPhotoLarge, isContactPhotoShown);
    592         showImageView(mWorkProfileIcon, isWorkCall);
    593     }
    594 
    595     @Override
    596     public void setSecondary(boolean show, String name, boolean nameIsNumber, String label,
    597             String providerLabel, boolean isConference, boolean isVideoCall, boolean isFullscreen) {
    598 
    599         if (show) {
    600             mHasSecondaryCallInfo = true;
    601             boolean hasProvider = !TextUtils.isEmpty(providerLabel);
    602             initializeSecondaryCallInfo(hasProvider);
    603 
    604             // Do not show the secondary caller info in fullscreen mode, but ensure it is populated
    605             // in case fullscreen mode is exited in the future.
    606             setSecondaryInfoVisible(!isFullscreen);
    607 
    608             mSecondaryCallConferenceCallIcon.setVisibility(isConference ? View.VISIBLE : View.GONE);
    609             mSecondaryCallVideoCallIcon.setVisibility(isVideoCall ? View.VISIBLE : View.GONE);
    610 
    611             mSecondaryCallName.setText(nameIsNumber
    612                     ? PhoneNumberUtilsCompat.createTtsSpannable(name)
    613                     : name);
    614             if (hasProvider) {
    615                 mSecondaryCallProviderLabel.setText(providerLabel);
    616             }
    617 
    618             int nameDirection = View.TEXT_DIRECTION_INHERIT;
    619             if (nameIsNumber) {
    620                 nameDirection = View.TEXT_DIRECTION_LTR;
    621             }
    622             mSecondaryCallName.setTextDirection(nameDirection);
    623         } else {
    624             mHasSecondaryCallInfo = false;
    625             setSecondaryInfoVisible(false);
    626         }
    627     }
    628 
    629     /**
    630      * Sets the visibility of the secondary caller info box.  Note, if the {@code visible} parameter
    631      * is passed in {@code true}, and there is no secondary caller info populated (as determined by
    632      * {@code mHasSecondaryCallInfo}, the secondary caller info box will not be shown.
    633      *
    634      * @param visible {@code true} if the secondary caller info should be shown, {@code false}
    635      *      otherwise.
    636      */
    637     @Override
    638     public void setSecondaryInfoVisible(final boolean visible) {
    639         boolean wasVisible = mSecondaryCallInfo.isShown();
    640         final boolean isVisible = visible && mHasSecondaryCallInfo;
    641         Log.v(this, "setSecondaryInfoVisible: wasVisible = " + wasVisible + " isVisible = "
    642                 + isVisible);
    643 
    644         // If visibility didn't change, nothing to do.
    645         if (wasVisible == isVisible) {
    646             return;
    647         }
    648 
    649         // If we are showing the secondary info, we need to show it before animating so that its
    650         // height will be determined on layout.
    651         if (isVisible) {
    652             mSecondaryCallInfo.setVisibility(View.VISIBLE);
    653         } else {
    654             mSecondaryCallInfo.setVisibility(View.GONE);
    655         }
    656 
    657         updateFabPositionForSecondaryCallInfo();
    658         // We need to translate the secondary caller info, but we need to know its position after
    659         // the layout has occurred so use a {@code ViewTreeObserver}.
    660         final ViewTreeObserver observer = getView().getViewTreeObserver();
    661 
    662         observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    663             @Override
    664             public boolean onPreDraw() {
    665                 // We don't want to continue getting called.
    666                 getView().getViewTreeObserver().removeOnPreDrawListener(this);
    667 
    668                 // Get the height of the secondary call info now, and then re-hide the view prior
    669                 // to doing the actual animation.
    670                 int secondaryHeight = mSecondaryCallInfo.getHeight();
    671                 if (isVisible) {
    672                     mSecondaryCallInfo.setVisibility(View.GONE);
    673                 } else {
    674                     mSecondaryCallInfo.setVisibility(View.VISIBLE);
    675                 }
    676                 Log.v(this, "setSecondaryInfoVisible: secondaryHeight = " + secondaryHeight);
    677 
    678                 // Set the position of the secondary call info card to its starting location.
    679                 mSecondaryCallInfo.setTranslationY(visible ? secondaryHeight : 0);
    680 
    681                 // Animate the secondary card info slide up/down as it appears and disappears.
    682                 ViewPropertyAnimator secondaryInfoAnimator = mSecondaryCallInfo.animate()
    683                         .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
    684                         .setDuration(mVideoAnimationDuration)
    685                         .translationY(isVisible ? 0 : secondaryHeight)
    686                         .setListener(new AnimatorListenerAdapter() {
    687                             @Override
    688                             public void onAnimationEnd(Animator animation) {
    689                                 if (!isVisible) {
    690                                     mSecondaryCallInfo.setVisibility(View.GONE);
    691                                 }
    692                             }
    693 
    694                             @Override
    695                             public void onAnimationStart(Animator animation) {
    696                                 if (isVisible) {
    697                                     mSecondaryCallInfo.setVisibility(View.VISIBLE);
    698                                 }
    699                             }
    700                         });
    701                 secondaryInfoAnimator.start();
    702 
    703                 // Notify listeners of a change in the visibility of the secondary info. This is
    704                 // important when in a video call so that the video call presenter can shift the
    705                 // video preview up or down to accommodate the secondary caller info.
    706                 InCallPresenter.getInstance().notifySecondaryCallerInfoVisibilityChanged(visible,
    707                         secondaryHeight);
    708 
    709                 return true;
    710             }
    711         });
    712     }
    713 
    714     @Override
    715     public void setCallState(
    716             int state,
    717             int videoState,
    718             int sessionModificationState,
    719             DisconnectCause disconnectCause,
    720             String connectionLabel,
    721             Drawable callStateIcon,
    722             String gatewayNumber,
    723             boolean isWifi,
    724             boolean isConference,
    725             boolean isWorkCall) {
    726         boolean isGatewayCall = !TextUtils.isEmpty(gatewayNumber);
    727         CallStateLabel callStateLabel = getCallStateLabelFromState(state, videoState,
    728                 sessionModificationState, disconnectCause, connectionLabel, isGatewayCall, isWifi,
    729                 isConference, isWorkCall);
    730 
    731         Log.v(this, "setCallState " + callStateLabel.getCallStateLabel());
    732         Log.v(this, "AutoDismiss " + callStateLabel.isAutoDismissing());
    733         Log.v(this, "DisconnectCause " + disconnectCause.toString());
    734         Log.v(this, "gateway " + connectionLabel + gatewayNumber);
    735 
    736         // Check for video state change and update the visibility of the contact photo.  The contact
    737         // photo is hidden when the incoming video surface is shown.
    738         // The contact photo visibility can also change in setPrimary().
    739         boolean showContactPhoto = !VideoCallPresenter.showIncomingVideo(videoState, state);
    740         mPhotoLarge.setVisibility(showContactPhoto ? View.VISIBLE : View.GONE);
    741 
    742         // Check if the call subject is showing -- if it is, we want to bypass showing the call
    743         // state.
    744         boolean isSubjectShowing = mCallSubject.getVisibility() == View.VISIBLE;
    745 
    746         if (TextUtils.equals(callStateLabel.getCallStateLabel(), mCallStateLabel.getText()) &&
    747                 !isSubjectShowing) {
    748             // Nothing to do if the labels are the same
    749             if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED) {
    750                 mCallStateLabel.clearAnimation();
    751                 mCallStateIcon.clearAnimation();
    752             }
    753             return;
    754         }
    755 
    756         if (isSubjectShowing) {
    757             changeCallStateLabel(null);
    758             callStateIcon = null;
    759         } else {
    760             // Update the call state label and icon.
    761             setCallStateLabel(callStateLabel);
    762         }
    763 
    764         if (!TextUtils.isEmpty(callStateLabel.getCallStateLabel())) {
    765             if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED) {
    766                 mCallStateLabel.clearAnimation();
    767             } else {
    768                 mCallStateLabel.startAnimation(mPulseAnimation);
    769             }
    770         } else {
    771             mCallStateLabel.clearAnimation();
    772         }
    773 
    774         if (callStateIcon != null) {
    775             mCallStateIcon.setVisibility(View.VISIBLE);
    776             // Invoke setAlpha(float) instead of setAlpha(int) to set the view's alpha. This is
    777             // needed because the pulse animation operates on the view alpha.
    778             mCallStateIcon.setAlpha(1.0f);
    779             mCallStateIcon.setImageDrawable(callStateIcon);
    780 
    781             if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED
    782                     || TextUtils.isEmpty(callStateLabel.getCallStateLabel())) {
    783                 mCallStateIcon.clearAnimation();
    784             } else {
    785                 mCallStateIcon.startAnimation(mPulseAnimation);
    786             }
    787 
    788             if (callStateIcon instanceof AnimationDrawable) {
    789                 ((AnimationDrawable) callStateIcon).start();
    790             }
    791         } else {
    792             mCallStateIcon.clearAnimation();
    793 
    794             // Invoke setAlpha(float) instead of setAlpha(int) to set the view's alpha. This is
    795             // needed because the pulse animation operates on the view alpha.
    796             mCallStateIcon.setAlpha(0.0f);
    797             mCallStateIcon.setVisibility(View.GONE);
    798         }
    799 
    800         if (VideoUtils.isVideoCall(videoState)
    801                 || (state == Call.State.ACTIVE && sessionModificationState
    802                         == Call.SessionModificationState.WAITING_FOR_RESPONSE)) {
    803             mCallStateVideoCallIcon.setVisibility(View.VISIBLE);
    804         } else {
    805             mCallStateVideoCallIcon.setVisibility(View.GONE);
    806         }
    807     }
    808 
    809     private void setCallStateLabel(CallStateLabel callStateLabel) {
    810         Log.v(this, "setCallStateLabel : label = " + callStateLabel.getCallStateLabel());
    811 
    812         if (callStateLabel.isAutoDismissing()) {
    813             mCallStateLabelResetPending = true;
    814             mHandler.postDelayed(new Runnable() {
    815                 @Override
    816                 public void run() {
    817                     Log.v(this, "restoringCallStateLabel : label = " +
    818                             mPostResetCallStateLabel);
    819                     changeCallStateLabel(mPostResetCallStateLabel);
    820                     mCallStateLabelResetPending = false;
    821                 }
    822             }, CALL_STATE_LABEL_RESET_DELAY_MS);
    823 
    824             changeCallStateLabel(callStateLabel.getCallStateLabel());
    825         } else {
    826             // Keep track of the current call state label; used when resetting auto dismissing
    827             // call state labels.
    828             mPostResetCallStateLabel = callStateLabel.getCallStateLabel();
    829 
    830             if (!mCallStateLabelResetPending) {
    831                 changeCallStateLabel(callStateLabel.getCallStateLabel());
    832             }
    833         }
    834     }
    835 
    836     private void changeCallStateLabel(CharSequence callStateLabel) {
    837         Log.v(this, "changeCallStateLabel : label = " + callStateLabel);
    838         if (!TextUtils.isEmpty(callStateLabel)) {
    839             mCallStateLabel.setText(callStateLabel);
    840             mCallStateLabel.setAlpha(1);
    841             mCallStateLabel.setVisibility(View.VISIBLE);
    842         } else {
    843             Animation callStateLabelAnimation = mCallStateLabel.getAnimation();
    844             if (callStateLabelAnimation != null) {
    845                 callStateLabelAnimation.cancel();
    846             }
    847             mCallStateLabel.setText(null);
    848             mCallStateLabel.setAlpha(0);
    849             mCallStateLabel.setVisibility(View.GONE);
    850         }
    851     }
    852 
    853     @Override
    854     public void setCallbackNumber(String callbackNumber, boolean isEmergencyCall) {
    855         if (mInCallMessageLabel == null) {
    856             return;
    857         }
    858 
    859         if (TextUtils.isEmpty(callbackNumber)) {
    860             mInCallMessageLabel.setVisibility(View.GONE);
    861             return;
    862         }
    863 
    864         // TODO: The new Locale-specific methods don't seem to be working. Revisit this.
    865         callbackNumber = PhoneNumberUtils.formatNumber(callbackNumber);
    866 
    867         int stringResourceId = isEmergencyCall ? R.string.card_title_callback_number_emergency
    868                 : R.string.card_title_callback_number;
    869 
    870         String text = getString(stringResourceId, callbackNumber);
    871         mInCallMessageLabel.setText(text);
    872 
    873         mInCallMessageLabel.setVisibility(View.VISIBLE);
    874     }
    875 
    876     /**
    877      * Sets and shows the call subject if it is not empty.  Hides the call subject otherwise.
    878      *
    879      * @param callSubject The call subject.
    880      */
    881     @Override
    882     public void setCallSubject(String callSubject) {
    883         boolean showSubject = !TextUtils.isEmpty(callSubject);
    884 
    885         mCallSubject.setVisibility(showSubject ? View.VISIBLE : View.GONE);
    886         if (showSubject) {
    887             mCallSubject.setText(callSubject);
    888         } else {
    889             mCallSubject.setText(null);
    890         }
    891     }
    892 
    893     public boolean isAnimating() {
    894         return mIsAnimating;
    895     }
    896 
    897     private void showInternetCallLabel(boolean show) {
    898         if (show) {
    899             final String label = getView().getContext().getString(
    900                     R.string.incall_call_type_label_sip);
    901             mCallTypeLabel.setVisibility(View.VISIBLE);
    902             mCallTypeLabel.setText(label);
    903         } else {
    904             mCallTypeLabel.setVisibility(View.GONE);
    905         }
    906     }
    907 
    908     @Override
    909     public void setPrimaryCallElapsedTime(boolean show, long duration) {
    910         if (show) {
    911             if (mElapsedTime.getVisibility() != View.VISIBLE) {
    912                 AnimUtils.fadeIn(mElapsedTime, AnimUtils.DEFAULT_DURATION);
    913             }
    914             String callTimeElapsed = DateUtils.formatElapsedTime(duration / 1000);
    915             mElapsedTime.setText(callTimeElapsed);
    916 
    917             String durationDescription =
    918                     InCallDateUtils.formatDuration(getView().getContext(), duration);
    919             mElapsedTime.setContentDescription(
    920                     !TextUtils.isEmpty(durationDescription) ? durationDescription : null);
    921         } else {
    922             // hide() animation has no effect if it is already hidden.
    923             AnimUtils.fadeOut(mElapsedTime, AnimUtils.DEFAULT_DURATION);
    924         }
    925     }
    926 
    927     /**
    928      * Set all the ImageViews to the same photo. Currently there are 2 photo views: the large one
    929      * (which fills about the bottom half of the screen) and the small one, which displays as a
    930      * circle next to the primary contact info. This method does not handle whether the ImageView
    931      * is shown or not.
    932      *
    933      * @param photo The photo to set for the image views.
    934      */
    935     private void setDrawableToImageViews(Drawable photo) {
    936         if (photo == null) {
    937             photo = ContactInfoCache.getInstance(getView().getContext())
    938                             .getDefaultContactPhotoDrawable();
    939         }
    940 
    941         if (mPrimaryPhotoDrawable == photo){
    942             return;
    943         }
    944         mPrimaryPhotoDrawable = photo;
    945 
    946         mPhotoLarge.setImageDrawable(photo);
    947 
    948         // Modify the drawable to be round for the smaller ImageView.
    949         Bitmap bitmap = drawableToBitmap(photo);
    950         if (bitmap != null) {
    951             final RoundedBitmapDrawable drawable =
    952                     RoundedBitmapDrawableFactory.create(getResources(), bitmap);
    953             drawable.setAntiAlias(true);
    954             drawable.setCornerRadius(bitmap.getHeight() / 2);
    955             photo = drawable;
    956         }
    957         mPhotoSmall.setImageDrawable(photo);
    958     }
    959 
    960     /**
    961      * Helper method for image view to handle animations.
    962      *
    963      * @param view The image view to show or hide.
    964      * @param isVisible {@code true} if we want to show the image, {@code false} to hide it.
    965      */
    966     private void showImageView(ImageView view, boolean isVisible) {
    967         if (view.getDrawable() == null) {
    968             if (isVisible) {
    969                 AnimUtils.fadeIn(mElapsedTime, AnimUtils.DEFAULT_DURATION);
    970             }
    971         } else {
    972             // Cross fading is buggy and not noticeable due to the multiple calls to this method
    973             // that switch drawables in the middle of the cross-fade animations. Just show the
    974             // photo directly instead.
    975             view.setVisibility(isVisible ? View.VISIBLE : View.GONE);
    976         }
    977     }
    978 
    979     /**
    980      * Converts a drawable into a bitmap.
    981      *
    982      * @param drawable the drawable to be converted.
    983      */
    984     public static Bitmap drawableToBitmap(Drawable drawable) {
    985         Bitmap bitmap;
    986         if (drawable instanceof BitmapDrawable) {
    987             bitmap = ((BitmapDrawable) drawable).getBitmap();
    988         } else {
    989             if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
    990                 // Needed for drawables that are just a colour.
    991                 bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
    992             } else {
    993                 bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
    994                         drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
    995             }
    996 
    997             Log.i(TAG, "Created bitmap with width " + bitmap.getWidth() + ", height "
    998                     + bitmap.getHeight());
    999 
   1000             Canvas canvas = new Canvas(bitmap);
   1001             drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
   1002             drawable.draw(canvas);
   1003         }
   1004         return bitmap;
   1005     }
   1006 
   1007     /**
   1008      * Gets the call state label based on the state of the call or cause of disconnect.
   1009      *
   1010      * Additional labels are applied as follows:
   1011      *         1. All outgoing calls with display "Calling via [Provider]".
   1012      *         2. Ongoing calls will display the name of the provider.
   1013      *         3. Incoming calls will only display "Incoming via..." for accounts.
   1014      *         4. Video calls, and session modification states (eg. requesting video).
   1015      *         5. Incoming and active Wi-Fi calls will show label provided by hint.
   1016      *
   1017      * TODO: Move this to the CallCardPresenter.
   1018      */
   1019     private CallStateLabel getCallStateLabelFromState(int state, int videoState,
   1020             int sessionModificationState, DisconnectCause disconnectCause, String label,
   1021             boolean isGatewayCall, boolean isWifi, boolean isConference, boolean isWorkCall) {
   1022         final Context context = getView().getContext();
   1023         CharSequence callStateLabel = null;  // Label to display as part of the call banner
   1024 
   1025         boolean hasSuggestedLabel = label != null;
   1026         boolean isAccount = hasSuggestedLabel && !isGatewayCall;
   1027         boolean isAutoDismissing = false;
   1028 
   1029         switch  (state) {
   1030             case Call.State.IDLE:
   1031                 // "Call state" is meaningless in this state.
   1032                 break;
   1033             case Call.State.ACTIVE:
   1034                 // We normally don't show a "call state label" at all in this state
   1035                 // (but we can use the call state label to display the provider name).
   1036                 if ((isAccount || isWifi || isConference) && hasSuggestedLabel) {
   1037                     callStateLabel = label;
   1038                 } else if (sessionModificationState
   1039                         == Call.SessionModificationState.REQUEST_REJECTED) {
   1040                     callStateLabel = context.getString(R.string.card_title_video_call_rejected);
   1041                     isAutoDismissing = true;
   1042                 } else if (sessionModificationState
   1043                         == Call.SessionModificationState.REQUEST_FAILED) {
   1044                     callStateLabel = context.getString(R.string.card_title_video_call_error);
   1045                     isAutoDismissing = true;
   1046                 } else if (sessionModificationState
   1047                         == Call.SessionModificationState.WAITING_FOR_RESPONSE) {
   1048                     callStateLabel = context.getString(R.string.card_title_video_call_requesting);
   1049                 } else if (sessionModificationState
   1050                         == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
   1051                     callStateLabel = context.getString(R.string.card_title_video_call_requesting);
   1052                 } else if (VideoUtils.isVideoCall(videoState)) {
   1053                     callStateLabel = context.getString(R.string.card_title_video_call);
   1054                 }
   1055                 break;
   1056             case Call.State.ONHOLD:
   1057                 callStateLabel = context.getString(R.string.card_title_on_hold);
   1058                 break;
   1059             case Call.State.CONNECTING:
   1060             case Call.State.DIALING:
   1061                 if (hasSuggestedLabel && !isWifi) {
   1062                     callStateLabel = context.getString(R.string.calling_via_template, label);
   1063                 } else {
   1064                     callStateLabel = context.getString(R.string.card_title_dialing);
   1065                 }
   1066                 break;
   1067             case Call.State.REDIALING:
   1068                 callStateLabel = context.getString(R.string.card_title_redialing);
   1069                 break;
   1070             case Call.State.INCOMING:
   1071             case Call.State.CALL_WAITING:
   1072                 if (isWifi && hasSuggestedLabel) {
   1073                     callStateLabel = label;
   1074                 } else if (isAccount) {
   1075                     callStateLabel = context.getString(R.string.incoming_via_template, label);
   1076                 } else if (VideoUtils.isVideoCall(videoState)) {
   1077                     callStateLabel = context.getString(R.string.notification_incoming_video_call);
   1078                 } else {
   1079                     callStateLabel =
   1080                             context.getString(isWorkCall ? R.string.card_title_incoming_work_call
   1081                                     : R.string.card_title_incoming_call);
   1082                 }
   1083                 break;
   1084             case Call.State.DISCONNECTING:
   1085                 // While in the DISCONNECTING state we display a "Hanging up"
   1086                 // message in order to make the UI feel more responsive.  (In
   1087                 // GSM it's normal to see a delay of a couple of seconds while
   1088                 // negotiating the disconnect with the network, so the "Hanging
   1089                 // up" state at least lets the user know that we're doing
   1090                 // something.  This state is currently not used with CDMA.)
   1091                 callStateLabel = context.getString(R.string.card_title_hanging_up);
   1092                 break;
   1093             case Call.State.DISCONNECTED:
   1094                 callStateLabel = disconnectCause.getLabel();
   1095                 if (TextUtils.isEmpty(callStateLabel)) {
   1096                     callStateLabel = context.getString(R.string.card_title_call_ended);
   1097                 }
   1098                 break;
   1099             case Call.State.CONFERENCED:
   1100                 callStateLabel = context.getString(R.string.card_title_conf_call);
   1101                 break;
   1102             default:
   1103                 Log.wtf(this, "updateCallStateWidgets: unexpected call: " + state);
   1104         }
   1105         return new CallStateLabel(callStateLabel, isAutoDismissing);
   1106     }
   1107 
   1108     private void initializeSecondaryCallInfo(boolean hasProvider) {
   1109         // mSecondaryCallName is initialized here (vs. onViewCreated) because it is inaccessible
   1110         // until mSecondaryCallInfo is inflated in the call above.
   1111         if (mSecondaryCallName == null) {
   1112             mSecondaryCallName = (TextView) getView().findViewById(R.id.secondaryCallName);
   1113             mSecondaryCallConferenceCallIcon =
   1114                     getView().findViewById(R.id.secondaryCallConferenceCallIcon);
   1115             mSecondaryCallVideoCallIcon =
   1116                     getView().findViewById(R.id.secondaryCallVideoCallIcon);
   1117         }
   1118 
   1119         if (mSecondaryCallProviderLabel == null && hasProvider) {
   1120             mSecondaryCallProviderInfo.setVisibility(View.VISIBLE);
   1121             mSecondaryCallProviderLabel = (TextView) getView()
   1122                     .findViewById(R.id.secondaryCallProviderLabel);
   1123         }
   1124     }
   1125 
   1126     public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
   1127         if (event.getEventType() == AccessibilityEvent.TYPE_ANNOUNCEMENT) {
   1128             // Indicate this call is in active if no label is provided. The label is empty when
   1129             // the call is in active, not in other status such as onhold or dialing etc.
   1130             if (!mCallStateLabel.isShown() || TextUtils.isEmpty(mCallStateLabel.getText())) {
   1131                 event.getText().add(
   1132                         TextUtils.expandTemplate(
   1133                                 getResources().getText(R.string.accessibility_call_is_active),
   1134                                 mPrimaryName.getText()));
   1135             } else {
   1136                 dispatchPopulateAccessibilityEvent(event, mCallStateLabel);
   1137                 dispatchPopulateAccessibilityEvent(event, mPrimaryName);
   1138                 dispatchPopulateAccessibilityEvent(event, mCallTypeLabel);
   1139                 dispatchPopulateAccessibilityEvent(event, mPhoneNumber);
   1140             }
   1141             return;
   1142         }
   1143         dispatchPopulateAccessibilityEvent(event, mCallStateLabel);
   1144         dispatchPopulateAccessibilityEvent(event, mPrimaryName);
   1145         dispatchPopulateAccessibilityEvent(event, mPhoneNumber);
   1146         dispatchPopulateAccessibilityEvent(event, mCallTypeLabel);
   1147         dispatchPopulateAccessibilityEvent(event, mSecondaryCallName);
   1148         dispatchPopulateAccessibilityEvent(event, mSecondaryCallProviderLabel);
   1149 
   1150         return;
   1151     }
   1152 
   1153     @Override
   1154     public void sendAccessibilityAnnouncement() {
   1155         mHandler.postDelayed(new Runnable() {
   1156             @Override
   1157             public void run() {
   1158                 if (getView() != null && getView().getParent() != null &&
   1159                         isAccessibilityEnabled(getContext())) {
   1160                     AccessibilityEvent event = AccessibilityEvent.obtain(
   1161                             AccessibilityEvent.TYPE_ANNOUNCEMENT);
   1162                     dispatchPopulateAccessibilityEvent(event);
   1163                     getView().getParent().requestSendAccessibilityEvent(getView(), event);
   1164                 }
   1165             }
   1166 
   1167             private boolean isAccessibilityEnabled(Context context) {
   1168                 AccessibilityManager accessibilityManager =
   1169                         (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
   1170                 return accessibilityManager != null && accessibilityManager.isEnabled();
   1171 
   1172             }
   1173         }, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MS);
   1174     }
   1175 
   1176     @Override
   1177     public void setEndCallButtonEnabled(boolean enabled, boolean animate) {
   1178         if (enabled != mFloatingActionButton.isEnabled()) {
   1179             if (animate) {
   1180                 if (enabled) {
   1181                     mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY);
   1182                 } else {
   1183                     mFloatingActionButtonController.scaleOut();
   1184                 }
   1185             } else {
   1186                 if (enabled) {
   1187                     mFloatingActionButtonContainer.setScaleX(1);
   1188                     mFloatingActionButtonContainer.setScaleY(1);
   1189                     mFloatingActionButtonContainer.setVisibility(View.VISIBLE);
   1190                 } else {
   1191                     mFloatingActionButtonContainer.setVisibility(View.GONE);
   1192                 }
   1193             }
   1194             mFloatingActionButton.setEnabled(enabled);
   1195             updateFabPosition();
   1196         }
   1197     }
   1198 
   1199     /**
   1200      * Changes the visibility of the HD audio icon.
   1201      *
   1202      * @param visible {@code true} if the UI should show the HD audio icon.
   1203      */
   1204     @Override
   1205     public void showHdAudioIndicator(boolean visible) {
   1206         mHdAudioIcon.setVisibility(visible ? View.VISIBLE : View.GONE);
   1207     }
   1208 
   1209     /**
   1210      * Changes the visibility of the forward icon.
   1211      *
   1212      * @param visible {@code true} if the UI should show the forward icon.
   1213      */
   1214     @Override
   1215     public void showForwardIndicator(boolean visible) {
   1216         mForwardIcon.setVisibility(visible ? View.VISIBLE : View.GONE);
   1217     }
   1218 
   1219 
   1220     /**
   1221      * Changes the visibility of the "manage conference call" button.
   1222      *
   1223      * @param visible Whether to set the button to be visible or not.
   1224      */
   1225     @Override
   1226     public void showManageConferenceCallButton(boolean visible) {
   1227         mManageConferenceCallButton.setVisibility(visible ? View.VISIBLE : View.GONE);
   1228     }
   1229 
   1230     /**
   1231      * Determines the current visibility of the manage conference button.
   1232      *
   1233      * @return {@code true} if the button is visible.
   1234      */
   1235     @Override
   1236     public boolean isManageConferenceVisible() {
   1237         return mManageConferenceCallButton.getVisibility() == View.VISIBLE;
   1238     }
   1239 
   1240     /**
   1241      * Determines the current visibility of the call subject.
   1242      *
   1243      * @return {@code true} if the subject is visible.
   1244      */
   1245     @Override
   1246     public boolean isCallSubjectVisible() {
   1247         return mCallSubject.getVisibility() == View.VISIBLE;
   1248     }
   1249 
   1250     /**
   1251      * Get the overall InCallUI background colors and apply to call card.
   1252      */
   1253     public void updateColors() {
   1254         MaterialPalette themeColors = InCallPresenter.getInstance().getThemeColors();
   1255 
   1256         if (mCurrentThemeColors != null && mCurrentThemeColors.equals(themeColors)) {
   1257             return;
   1258         }
   1259 
   1260         if (getResources().getBoolean(R.bool.is_layout_landscape)) {
   1261             final GradientDrawable drawable =
   1262                     (GradientDrawable) mPrimaryCallCardContainer.getBackground();
   1263             drawable.setColor(themeColors.mPrimaryColor);
   1264         } else {
   1265             mPrimaryCallCardContainer.setBackgroundColor(themeColors.mPrimaryColor);
   1266         }
   1267         mCallButtonsContainer.setBackgroundColor(themeColors.mPrimaryColor);
   1268         mCallSubject.setTextColor(themeColors.mPrimaryColor);
   1269         mContactContext.setBackgroundColor(themeColors.mPrimaryColor);
   1270         //TODO: set color of message text in call context "recent messages" to be the theme color.
   1271 
   1272         mCurrentThemeColors = themeColors;
   1273     }
   1274 
   1275     private void dispatchPopulateAccessibilityEvent(AccessibilityEvent event, View view) {
   1276         if (view == null) return;
   1277         final List<CharSequence> eventText = event.getText();
   1278         int size = eventText.size();
   1279         view.dispatchPopulateAccessibilityEvent(event);
   1280         // if no text added write null to keep relative position
   1281         if (size == eventText.size()) {
   1282             eventText.add(null);
   1283         }
   1284     }
   1285 
   1286     @Override
   1287     public void animateForNewOutgoingCall() {
   1288         final ViewGroup parent = (ViewGroup) mPrimaryCallCardContainer.getParent();
   1289 
   1290         final ViewTreeObserver observer = getView().getViewTreeObserver();
   1291 
   1292         mIsAnimating = true;
   1293 
   1294         observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
   1295             @Override
   1296             public void onGlobalLayout() {
   1297                 final ViewTreeObserver observer = getView().getViewTreeObserver();
   1298                 if (!observer.isAlive()) {
   1299                     return;
   1300                 }
   1301                 observer.removeOnGlobalLayoutListener(this);
   1302 
   1303                 final LayoutIgnoringListener listener = new LayoutIgnoringListener();
   1304                 mPrimaryCallCardContainer.addOnLayoutChangeListener(listener);
   1305 
   1306                 // Prepare the state of views before the slide animation
   1307                 final int originalHeight = mPrimaryCallCardContainer.getHeight();
   1308                 mPrimaryCallCardContainer.setTag(R.id.view_tag_callcard_actual_height,
   1309                         originalHeight);
   1310                 mPrimaryCallCardContainer.setBottom(parent.getHeight());
   1311 
   1312                 // Set up FAB.
   1313                 mFloatingActionButtonContainer.setVisibility(View.GONE);
   1314                 mFloatingActionButtonController.setScreenWidth(parent.getWidth());
   1315 
   1316                 mCallButtonsContainer.setAlpha(0);
   1317                 mCallStateLabel.setAlpha(0);
   1318                 mPrimaryName.setAlpha(0);
   1319                 mCallTypeLabel.setAlpha(0);
   1320                 mCallNumberAndLabel.setAlpha(0);
   1321 
   1322                 assignTranslateAnimation(mCallStateLabel, 1);
   1323                 assignTranslateAnimation(mCallStateIcon, 1);
   1324                 assignTranslateAnimation(mPrimaryName, 2);
   1325                 assignTranslateAnimation(mCallNumberAndLabel, 3);
   1326                 assignTranslateAnimation(mCallTypeLabel, 4);
   1327                 assignTranslateAnimation(mCallButtonsContainer, 5);
   1328 
   1329                 final Animator animator = getShrinkAnimator(parent.getHeight(), originalHeight);
   1330 
   1331                 animator.addListener(new AnimatorListenerAdapter() {
   1332                     @Override
   1333                     public void onAnimationEnd(Animator animation) {
   1334                         mPrimaryCallCardContainer.setTag(R.id.view_tag_callcard_actual_height,
   1335                                 null);
   1336                         setViewStatePostAnimation(listener);
   1337                         mIsAnimating = false;
   1338                         InCallPresenter.getInstance().onShrinkAnimationComplete();
   1339                     }
   1340                 });
   1341                 animator.start();
   1342             }
   1343         });
   1344     }
   1345 
   1346     @Override
   1347     public void showNoteSentToast() {
   1348         Toast.makeText(getContext(), R.string.note_sent, Toast.LENGTH_LONG).show();
   1349     }
   1350 
   1351     public void onDialpadVisibilityChange(boolean isShown) {
   1352         mIsDialpadShowing = isShown;
   1353         updateFabPosition();
   1354     }
   1355 
   1356     private void updateFabPosition() {
   1357         int offsetY = 0;
   1358         if (!mIsDialpadShowing) {
   1359             offsetY = mFloatingActionButtonVerticalOffset;
   1360             if (mSecondaryCallInfo.isShown() && mHasLargePhoto) {
   1361                 offsetY -= mSecondaryCallInfo.getHeight();
   1362             }
   1363         }
   1364 
   1365         mFloatingActionButtonController.align(
   1366                 FloatingActionButtonController.ALIGN_MIDDLE,
   1367                 0 /* offsetX */,
   1368                 offsetY,
   1369                 true);
   1370 
   1371         mFloatingActionButtonController.resize(
   1372                 mIsDialpadShowing ? mFabSmallDiameter : mFabNormalDiameter, true);
   1373     }
   1374 
   1375     @Override
   1376     public Context getContext() {
   1377         return getActivity();
   1378     }
   1379 
   1380     @Override
   1381     public void onResume() {
   1382         super.onResume();
   1383         // If the previous launch animation is still running, cancel it so that we don't get
   1384         // stuck in an intermediate animation state.
   1385         if (mAnimatorSet != null && mAnimatorSet.isRunning()) {
   1386             mAnimatorSet.cancel();
   1387         }
   1388 
   1389         mIsLandscape = getResources().getBoolean(R.bool.is_layout_landscape);
   1390         mHasLargePhoto = getResources().getBoolean(R.bool.has_large_photo);
   1391 
   1392         final ViewGroup parent = ((ViewGroup) mPrimaryCallCardContainer.getParent());
   1393         final ViewTreeObserver observer = parent.getViewTreeObserver();
   1394         parent.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
   1395             @Override
   1396             public void onGlobalLayout() {
   1397                 ViewTreeObserver viewTreeObserver = observer;
   1398                 if (!viewTreeObserver.isAlive()) {
   1399                     viewTreeObserver = parent.getViewTreeObserver();
   1400                 }
   1401                 viewTreeObserver.removeOnGlobalLayoutListener(this);
   1402                 mFloatingActionButtonController.setScreenWidth(parent.getWidth());
   1403                 updateFabPosition();
   1404             }
   1405         });
   1406 
   1407         updateColors();
   1408     }
   1409 
   1410     /**
   1411      * Adds a global layout listener to update the FAB's positioning on the next layout. This allows
   1412      * us to position the FAB after the secondary call info's height has been calculated.
   1413      */
   1414     private void updateFabPositionForSecondaryCallInfo() {
   1415         mSecondaryCallInfo.getViewTreeObserver().addOnGlobalLayoutListener(
   1416                 new ViewTreeObserver.OnGlobalLayoutListener() {
   1417                     @Override
   1418                     public void onGlobalLayout() {
   1419                         final ViewTreeObserver observer = mSecondaryCallInfo.getViewTreeObserver();
   1420                         if (!observer.isAlive()) {
   1421                             return;
   1422                         }
   1423                         observer.removeOnGlobalLayoutListener(this);
   1424 
   1425                         onDialpadVisibilityChange(mIsDialpadShowing);
   1426                     }
   1427                 });
   1428     }
   1429 
   1430     /**
   1431      * Animator that performs the upwards shrinking animation of the blue call card scrim.
   1432      * At the start of the animation, each child view is moved downwards by a pre-specified amount
   1433      * and then translated upwards together with the scrim.
   1434      */
   1435     private Animator getShrinkAnimator(int startHeight, int endHeight) {
   1436         final ObjectAnimator shrinkAnimator =
   1437                 ObjectAnimator.ofInt(mPrimaryCallCardContainer, "bottom", startHeight, endHeight);
   1438         shrinkAnimator.setDuration(mShrinkAnimationDuration);
   1439         shrinkAnimator.addListener(new AnimatorListenerAdapter() {
   1440             @Override
   1441             public void onAnimationStart(Animator animation) {
   1442                 mFloatingActionButton.setEnabled(true);
   1443             }
   1444         });
   1445         shrinkAnimator.setInterpolator(AnimUtils.EASE_IN);
   1446         return shrinkAnimator;
   1447     }
   1448 
   1449     private void assignTranslateAnimation(View view, int offset) {
   1450         view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
   1451         view.buildLayer();
   1452         view.setTranslationY(mTranslationOffset * offset);
   1453         view.animate().translationY(0).alpha(1).withLayer()
   1454                 .setDuration(mShrinkAnimationDuration).setInterpolator(AnimUtils.EASE_IN);
   1455     }
   1456 
   1457     private void setViewStatePostAnimation(View view) {
   1458         view.setTranslationY(0);
   1459         view.setAlpha(1);
   1460     }
   1461 
   1462     private void setViewStatePostAnimation(OnLayoutChangeListener layoutChangeListener) {
   1463         setViewStatePostAnimation(mCallButtonsContainer);
   1464         setViewStatePostAnimation(mCallStateLabel);
   1465         setViewStatePostAnimation(mPrimaryName);
   1466         setViewStatePostAnimation(mCallTypeLabel);
   1467         setViewStatePostAnimation(mCallNumberAndLabel);
   1468         setViewStatePostAnimation(mCallStateIcon);
   1469 
   1470         mPrimaryCallCardContainer.removeOnLayoutChangeListener(layoutChangeListener);
   1471 
   1472         mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY);
   1473     }
   1474 
   1475     private final class LayoutIgnoringListener implements View.OnLayoutChangeListener {
   1476         @Override
   1477         public void onLayoutChange(View v,
   1478                 int left,
   1479                 int top,
   1480                 int right,
   1481                 int bottom,
   1482                 int oldLeft,
   1483                 int oldTop,
   1484                 int oldRight,
   1485                 int oldBottom) {
   1486             v.setLeft(oldLeft);
   1487             v.setRight(oldRight);
   1488             v.setTop(oldTop);
   1489             v.setBottom(oldBottom);
   1490         }
   1491     }
   1492 }
   1493