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