gt:         if (touchPoint != null) {
context: /*
   * Copyright (C) 2013 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License
   */
  
  package com.android.incallui;
  
  import android.animation.Animator;
  import android.animation.AnimatorListenerAdapter;
  import android.animation.AnimatorSet;
  import android.animation.LayoutTransition;
  import android.animation.ObjectAnimator;
  import android.app.Activity;
  import android.content.Context;
  import android.content.res.Configuration;
  import android.graphics.Point;
  import android.graphics.drawable.AnimationDrawable;
  import android.graphics.drawable.Drawable;
  import android.os.Bundle;
  import android.telecom.DisconnectCause;
  import android.telecom.VideoProfile;
  import android.telephony.PhoneNumberUtils;
  import android.text.TextUtils;
  import android.text.format.DateUtils;
  import android.view.Display;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.view.View.OnLayoutChangeListener;
  import android.view.ViewAnimationUtils;
  import android.view.ViewGroup;
  import android.view.ViewPropertyAnimator;
  import android.view.ViewTreeObserver;
  import android.view.ViewTreeObserver.OnGlobalLayoutListener;
  import android.view.accessibility.AccessibilityEvent;
  import android.view.animation.Animation;
  import android.view.animation.AnimationUtils;
  import android.widget.ImageButton;
  import android.widget.ImageView;
  import android.widget.TextView;
  
  import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
  import com.android.contacts.common.widget.FloatingActionButtonController;
  import com.android.incallui.service.PhoneNumberService;
  import com.android.phone.common.animation.AnimUtils;
  
  import java.util.List;
  
  /**
   * Fragment for call card.
   */
  public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPresenter.CallCardUi>
          implements CallCardPresenter.CallCardUi {
  
      private AnimatorSet mAnimatorSet;
      private int mRevealAnimationDuration;
      private int mShrinkAnimationDuration;
      private int mFabNormalDiameter;
      private int mFabSmallDiameter;
      private boolean mIsLandscape;
      private boolean mIsDialpadShowing;
  
      // Primary caller info
      private TextView mPhoneNumber;
      private TextView mNumberLabel;
      private TextView mPrimaryName;
      private View mCallStateButton;
      private ImageView mCallStateIcon;
      private ImageView mCallStateVideoCallIcon;
      private TextView mCallStateLabel;
      private TextView mCallTypeLabel;
      private View mCallNumberAndLabel;
      private ImageView mPhoto;
      private TextView mElapsedTime;
      private Drawable mPrimaryPhotoDrawable;
  
      // Container view that houses the entire primary call card, including the call buttons
      private View mPrimaryCallCardContainer;
      // Container view that houses the primary call information
      private ViewGroup mPrimaryCallInfo;
      private View mCallButtonsContainer;
  
      // Secondary caller info
      private View mSecondaryCallInfo;
      private TextView mSecondaryCallName;
      private View mSecondaryCallProviderInfo;
      private TextView mSecondaryCallProviderLabel;
      private View mSecondaryCallConferenceCallIcon;
      private View mProgressSpinner;
  
      private View mManageConferenceCallButton;
  
      // Dark number info bar
      private TextView mInCallMessageLabel;
  
      private FloatingActionButtonController mFloatingActionButtonController;
      private View mFloatingActionButtonContainer;
      private ImageButton mFloatingActionButton;
      private int mFloatingActionButtonVerticalOffset;
  
      // Cached DisplayMetrics density.
      private float mDensity;
  
      private float mTranslationOffset;
      private Animation mPulseAnimation;
  
      private int mVideoAnimationDuration;
  
      private MaterialPalette mCurrentThemeColors;
  
      @Override
      CallCardPresenter.CallCardUi getUi() {
          return this;
      }
  
      @Override
      CallCardPresenter createPresenter() {
          return new CallCardPresenter();
      }
  
      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
  
          mRevealAnimationDuration = getResources().getInteger(R.integer.reveal_animation_duration);
          mShrinkAnimationDuration = getResources().getInteger(R.integer.shrink_animation_duration);
          mVideoAnimationDuration = getResources().getInteger(R.integer.video_animation_duration);
          mFloatingActionButtonVerticalOffset = getResources().getDimensionPixelOffset(
                  R.dimen.floating_action_bar_vertical_offset);
          mFabNormalDiameter = getResources().getDimensionPixelOffset(
                  R.dimen.end_call_floating_action_button_diameter);
          mFabSmallDiameter = getResources().getDimensionPixelOffset(
                  R.dimen.end_call_floating_action_button_small_diameter);
      }
  
  
      @Override
      public void onActivityCreated(Bundle savedInstanceState) {
          super.onActivityCreated(savedInstanceState);
  
          final CallList calls = CallList.getInstance();
          final Call call = calls.getFirstCall();
          getPresenter().init(getActivity(), call);
      }
  
      @Override
      public View onCreateView(LayoutInflater inflater, ViewGroup container,
              Bundle savedInstanceState) {
          super.onCreateView(inflater, container, savedInstanceState);
  
          mDensity = getResources().getDisplayMetrics().density;
          mTranslationOffset =
                  getResources().getDimensionPixelSize(R.dimen.call_card_anim_translate_y_offset);
  
          return inflater.inflate(R.layout.call_card_content, container, false);
      }
  
      @Override
      public void onViewCreated(View view, Bundle savedInstanceState) {
          super.onViewCreated(view, savedInstanceState);
  
          mPulseAnimation =
                  AnimationUtils.loadAnimation(view.getContext(), R.anim.call_status_pulse);
  
          mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber);
          mPrimaryName = (TextView) view.findViewById(R.id.name);
          mNumberLabel = (TextView) view.findViewById(R.id.label);
          mSecondaryCallInfo = view.findViewById(R.id.secondary_call_info);
          mSecondaryCallProviderInfo = view.findViewById(R.id.secondary_call_provider_info);
          mPhoto = (ImageView) view.findViewById(R.id.photo);
          mCallStateIcon = (ImageView) view.findViewById(R.id.callStateIcon);
          mCallStateVideoCallIcon = (ImageView) view.findViewById(R.id.videoCallIcon);
          mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel);
          mCallNumberAndLabel = view.findViewById(R.id.labelAndNumber);
          mCallTypeLabel = (TextView) view.findViewById(R.id.callTypeLabel);
          mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime);
          mPrimaryCallCardContainer = view.findViewById(R.id.primary_call_info_container);
          mPrimaryCallInfo = (ViewGroup) view.findViewById(R.id.primary_call_banner);
          mCallButtonsContainer = view.findViewById(R.id.callButtonFragment);
          mInCallMessageLabel = (TextView) view.findViewById(R.id.connectionServiceMessage);
          mProgressSpinner = view.findViewById(R.id.progressSpinner);
  
          mFloatingActionButtonContainer = view.findViewById(
                  R.id.floating_end_call_action_button_container);
          mFloatingActionButton = (ImageButton) view.findViewById(
                  R.id.floating_end_call_action_button);
          mFloatingActionButton.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  getPresenter().endCallClicked();
              }
          });
          mFloatingActionButtonController = new FloatingActionButtonController(getActivity(),
                  mFloatingActionButtonContainer, mFloatingActionButton);
  
          mSecondaryCallInfo.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  getPresenter().secondaryInfoClicked();
                  updateFabPositionForSecondaryCallInfo();
              }
          });
  
          mCallStateButton = view.findViewById(R.id.callStateButton);
          mCallStateButton.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  getPresenter().onCallStateButtonTouched();
              }
          });
  
          mManageConferenceCallButton = view.findViewById(R.id.manage_conference_call_button);
          mManageConferenceCallButton.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  InCallActivity activity = (InCallActivity) getActivity();
                  activity.showConferenceCallManager(true);
              }
          });
  
          mPrimaryName.setElegantTextHeight(false);
          mCallStateLabel.setElegantTextHeight(false);
      }
  
      @Override
      public void setVisible(boolean on) {
          if (on) {
              getView().setVisibility(View.VISIBLE);
          } else {
              getView().setVisibility(View.INVISIBLE);
          }
      }
  
      /**
       * Hides or shows the progress spinner.
       *
       * @param visible {@code True} if the progress spinner should be visible.
       */
      @Override
      public void setProgressSpinnerVisible(boolean visible) {
          mProgressSpinner.setVisibility(visible ? View.VISIBLE : View.GONE);
      }
  
      /**
       * Sets the visibility of the primary call card.
       * Ensures that when the primary call card is hidden, the video surface slides over to fill the
       * entire screen.
       *
       * @param visible {@code True} if the primary call card should be visible.
       */
      @Override
      public void setCallCardVisible(final boolean visible) {
          // When animating the hide/show of the views in a landscape layout, we need to take into
          // account whether we are in a left-to-right locale or a right-to-left locale and adjust
          // the animations accordingly.
          final boolean isLayoutRtl = InCallPresenter.isRtl();
  
          // Retrieve here since at fragment creation time the incoming video view is not inflated.
          final View videoView = getView().findViewById(R.id.incomingVideo);
  
          // Determine how much space there is below or to the side of the call card.
          final float spaceBesideCallCard = getSpaceBesideCallCard();
  
          // We need to translate the video surface, but we need to know its position after the layout
          // has occurred so use a {@code ViewTreeObserver}.
          final ViewTreeObserver observer = getView().getViewTreeObserver();
          observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
              @Override
              public boolean onPreDraw() {
                  // We don't want to continue getting called.
                  if (observer.isAlive()) {
                      observer.removeOnPreDrawListener(this);
                  }
  
                  float videoViewTranslation = 0f;
  
                  // Translate the call card to its pre-animation state.
                  if (mIsLandscape) {
                      float translationX = mPrimaryCallCardContainer.getWidth();
                      translationX *= isLayoutRtl ? 1 : -1;
  
                      mPrimaryCallCardContainer.setTranslationX(visible ? translationX : 0);
  
                      if (visible) {
                          videoViewTranslation = videoView.getWidth() / 2 - spaceBesideCallCard / 2;
                          videoViewTranslation *= isLayoutRtl ? -1 : 1;
                      }
                  } else {
                      mPrimaryCallCardContainer.setTranslationY(visible ?
                              -mPrimaryCallCardContainer.getHeight() : 0);
  
                      if (visible) {
                          videoViewTranslation = videoView.getHeight() / 2 - spaceBesideCallCard / 2;
                      }
                  }
  
                  // Perform animation of video view.
                  ViewPropertyAnimator videoViewAnimator = videoView.animate()
                          .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
                          .setDuration(mVideoAnimationDuration);
                  if (mIsLandscape) {
                      videoViewAnimator
                              .translationX(videoViewTranslation)
                              .start();
                  } else {
                      videoViewAnimator
                              .translationY(videoViewTranslation)
                              .start();
                  }
                  videoViewAnimator.start();
  
                  // Animate the call card sliding.
                  ViewPropertyAnimator callCardAnimator = mPrimaryCallCardContainer.animate()
                          .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
                          .setDuration(mVideoAnimationDuration)
                          .setListener(new AnimatorListenerAdapter() {
                              @Override
                              public void onAnimationEnd(Animator animation) {
                                  super.onAnimationEnd(animation);
                                  if (!visible) {
                                      mPrimaryCallCardContainer.setVisibility(View.GONE);
                                  }
                              }
  
                              @Override
                              public void onAnimationStart(Animator animation) {
                                  super.onAnimationStart(animation);
                                  if (visible) {
                                      mPrimaryCallCardContainer.setVisibility(View.VISIBLE);
                                  }
                              }
                          });
  
                  if (mIsLandscape) {
                      float translationX = mPrimaryCallCardContainer.getWidth();
                      translationX *= isLayoutRtl ? 1 : -1;
                      callCardAnimator
                              .translationX(visible ? 0 : translationX)
                              .start();
                  } else {
                      callCardAnimator
                              .translationY(visible ? 0 : -mPrimaryCallCardContainer.getHeight())
                              .start();
                  }
  
                  return true;
              }
          });
      }
  
      /**
       * Determines the amount of space below the call card for portrait layouts), or beside the
       * call card for landscape layouts.
       *
       * @return The amount of space below or beside the call card.
       */
      public float getSpaceBesideCallCard() {
          if (mIsLandscape) {
              return getView().getWidth() - mPrimaryCallCardContainer.getWidth();
          } else {
              return getView().getHeight() - mPrimaryCallCardContainer.getHeight();
          }
      }
  
      @Override
      public void setPrimaryName(String name, boolean nameIsNumber) {
          if (TextUtils.isEmpty(name)) {
              mPrimaryName.setText(null);
          } else {
              mPrimaryName.setText(nameIsNumber
                      ? PhoneNumberUtils.ttsSpanAsPhoneNumber(name)
                      : name);
  
              // Set direction of the name field
              int nameDirection = View.TEXT_DIRECTION_INHERIT;
              if (nameIsNumber) {
                  nameDirection = View.TEXT_DIRECTION_LTR;
              }
              mPrimaryName.setTextDirection(nameDirection);
          }
      }
  
      @Override
      public void setPrimaryImage(Drawable image) {
          if (image != null) {
              setDrawableToImageView(mPhoto, image);
          }
      }
  
      @Override
      public void setPrimaryPhoneNumber(String number) {
          // Set the number
          if (TextUtils.isEmpty(number)) {
              mPhoneNumber.setText(null);
              mPhoneNumber.setVisibility(View.GONE);
          } else {
              mPhoneNumber.setText(PhoneNumberUtils.ttsSpanAsPhoneNumber(number));
              mPhoneNumber.setVisibility(View.VISIBLE);
              mPhoneNumber.setTextDirection(View.TEXT_DIRECTION_LTR);
          }
      }
  
      @Override
      public void setPrimaryLabel(String label) {
          if (!TextUtils.isEmpty(label)) {
              mNumberLabel.setText(label);
              mNumberLabel.setVisibility(View.VISIBLE);
          } else {
              mNumberLabel.setVisibility(View.GONE);
          }
  
      }
  
      @Override
      public void setPrimary(String number, String name, boolean nameIsNumber, String label,
              Drawable photo, boolean isSipCall) {
          Log.d(this, "Setting primary call");
  
          // set the name field.
          setPrimaryName(name, nameIsNumber);
  
          if (TextUtils.isEmpty(number) && TextUtils.isEmpty(label)) {
              mCallNumberAndLabel.setVisibility(View.GONE);
              mElapsedTime.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
          } else {
              mCallNumberAndLabel.setVisibility(View.VISIBLE);
              mElapsedTime.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
          }
  
          setPrimaryPhoneNumber(number);
  
          // Set the label (Mobile, Work, etc)
          setPrimaryLabel(label);
  
          showInternetCallLabel(isSipCall);
  
          setDrawableToImageView(mPhoto, photo);
      }
  
      @Override
      public void setSecondary(boolean show, String name, boolean nameIsNumber, String label,
              String providerLabel, boolean isConference) {
  
          if (show != mSecondaryCallInfo.isShown()) {
              updateFabPositionForSecondaryCallInfo();
          }
  
          if (show) {
              boolean hasProvider = !TextUtils.isEmpty(providerLabel);
              showAndInitializeSecondaryCallInfo(hasProvider);
  
              mSecondaryCallConferenceCallIcon.setVisibility(isConference ? View.VISIBLE : View.GONE);
  
              mSecondaryCallName.setText(nameIsNumber
                      ? PhoneNumberUtils.ttsSpanAsPhoneNumber(name)
                      : name);
              if (hasProvider) {
                  mSecondaryCallProviderLabel.setText(providerLabel);
              }
  
              int nameDirection = View.TEXT_DIRECTION_INHERIT;
              if (nameIsNumber) {
                  nameDirection = View.TEXT_DIRECTION_LTR;
              }
              mSecondaryCallName.setTextDirection(nameDirection);
          } else {
              mSecondaryCallInfo.setVisibility(View.GONE);
          }
      }
  
      @Override
      public void setCallState(
              int state,
              int videoState,
              int sessionModificationState,
              DisconnectCause disconnectCause,
              String connectionLabel,
              Drawable callStateIcon,
              String gatewayNumber) {
          boolean isGatewayCall = !TextUtils.isEmpty(gatewayNumber);
          CharSequence callStateLabel = getCallStateLabelFromState(state, videoState,
                  sessionModificationState, disconnectCause, connectionLabel, isGatewayCall);
  
          Log.v(this, "setCallState " + callStateLabel);
          Log.v(this, "DisconnectCause " + disconnectCause.toString());
          Log.v(this, "gateway " + connectionLabel + gatewayNumber);
  
          if (TextUtils.equals(callStateLabel, mCallStateLabel.getText())) {
              // Nothing to do if the labels are the same
              return;
          }
  
          // Update the call state label and icon.
          if (!TextUtils.isEmpty(callStateLabel)) {
              mCallStateLabel.setText(callStateLabel);
              mCallStateLabel.setAlpha(1);
              mCallStateLabel.setVisibility(View.VISIBLE);
  
              if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED) {
                  mCallStateLabel.clearAnimation();
              } else {
                  mCallStateLabel.startAnimation(mPulseAnimation);
              }
          } else {
              Animation callStateLabelAnimation = mCallStateLabel.getAnimation();
              if (callStateLabelAnimation != null) {
                  callStateLabelAnimation.cancel();
              }
              mCallStateLabel.setText(null);
              mCallStateLabel.setAlpha(0);
              mCallStateLabel.setVisibility(View.GONE);
          }
  
          if (callStateIcon != null) {
              mCallStateIcon.setVisibility(View.VISIBLE);
              // Invoke setAlpha(float) instead of setAlpha(int) to set the view's alpha. This is
              // needed because the pulse animation operates on the view alpha.
              mCallStateIcon.setAlpha(1.0f);
              mCallStateIcon.setImageDrawable(callStateIcon);
  
              if (state == Call.State.ACTIVE || state == Call.State.CONFERENCED
                      || TextUtils.isEmpty(callStateLabel)) {
                  mCallStateIcon.clearAnimation();
              } else {
                  mCallStateIcon.startAnimation(mPulseAnimation);
              }
  
              if (callStateIcon instanceof AnimationDrawable) {
                  ((AnimationDrawable) callStateIcon).start();
              }
          } else {
              Animation callStateIconAnimation = mCallStateIcon.getAnimation();
              if (callStateIconAnimation != null) {
                  callStateIconAnimation.cancel();
              }
  
              // Invoke setAlpha(float) instead of setAlpha(int) to set the view's alpha. This is
              // needed because the pulse animation operates on the view alpha.
              mCallStateIcon.setAlpha(0.0f);
              mCallStateIcon.setVisibility(View.GONE);
          }
  
          if (VideoProfile.VideoState.isBidirectional(videoState)
                  || (state == Call.State.ACTIVE && sessionModificationState
                          == Call.SessionModificationState.WAITING_FOR_RESPONSE)) {
              mCallStateVideoCallIcon.setVisibility(View.VISIBLE);
          } else {
              mCallStateVideoCallIcon.setVisibility(View.GONE);
          }
  
          if (state == Call.State.INCOMING) {
              if (callStateLabel != null) {
                  getView().announceForAccessibility(callStateLabel);
              }
              if (mPrimaryName.getText() != null) {
                  getView().announceForAccessibility(mPrimaryName.getText());
              }
          }
      }
  
      @Override
      public void setCallbackNumber(String callbackNumber, boolean isEmergencyCall) {
          if (mInCallMessageLabel == null) {
              return;
          }
  
          if (TextUtils.isEmpty(callbackNumber)) {
              mInCallMessageLabel.setVisibility(View.GONE);
              return;
          }
  
          // TODO: The new Locale-specific methods don't seem to be working. Revisit this.
          callbackNumber = PhoneNumberUtils.formatNumber(callbackNumber);
  
          int stringResourceId = isEmergencyCall ? R.string.card_title_callback_number_emergency
                  : R.string.card_title_callback_number;
  
          String text = getString(stringResourceId, callbackNumber);
          mInCallMessageLabel.setText(text);
  
          mInCallMessageLabel.setVisibility(View.VISIBLE);
      }
  
      private void showInternetCallLabel(boolean show) {
          if (show) {
              final String label = getView().getContext().getString(
                      R.string.incall_call_type_label_sip);
              mCallTypeLabel.setVisibility(View.VISIBLE);
              mCallTypeLabel.setText(label);
          } else {
              mCallTypeLabel.setVisibility(View.GONE);
          }
      }
  
      @Override
      public void setPrimaryCallElapsedTime(boolean show, long duration) {
          if (show) {
              if (mElapsedTime.getVisibility() != View.VISIBLE) {
                  AnimUtils.fadeIn(mElapsedTime, AnimUtils.DEFAULT_DURATION);
              }
              String callTimeElapsed = DateUtils.formatElapsedTime(duration / 1000);
              String durationDescription = InCallDateUtils.formatDetailedDuration(duration);
              mElapsedTime.setText(callTimeElapsed);
              mElapsedTime.setContentDescription(durationDescription);
          } else {
              // hide() animation has no effect if it is already hidden.
              AnimUtils.fadeOut(mElapsedTime, AnimUtils.DEFAULT_DURATION);
          }
      }
  
      private void setDrawableToImageView(ImageView view, Drawable photo) {
          if (photo == null) {
              photo = ContactInfoCache.getInstance(
                      view.getContext()).getDefaultContactPhotoDrawable();
          }
  
          if (mPrimaryPhotoDrawable == photo) {
              return;
          }
          mPrimaryPhotoDrawable = photo;
  
          final Drawable current = view.getDrawable();
          if (current == null) {
              view.setImageDrawable(photo);
              AnimUtils.fadeIn(mElapsedTime, AnimUtils.DEFAULT_DURATION);
          } else {
              // Cross fading is buggy and not noticable due to the multiple calls to this method
              // that switch drawables in the middle of the cross-fade animations. Just set the
              // photo directly instead.
              view.setImageDrawable(photo);
              view.setVisibility(View.VISIBLE);
          }
      }
  
      /**
       * Gets the call state label based on the state of the call or cause of disconnect.
       *
       * Additional labels are applied as follows:
       *         1. All outgoing calls with display "Calling via [Provider]".
       *         2. Ongoing calls will display the name of the provider.
       *         3. Incoming calls will only display "Incoming via..." for accounts.
       *         4. Video calls, and session modification states (eg. requesting video).
       */
      private CharSequence getCallStateLabelFromState(int state, int videoState,
              int sessionModificationState, DisconnectCause disconnectCause, String label,
              boolean isGatewayCall) {
          final Context context = getView().getContext();
          CharSequence callStateLabel = null;  // Label to display as part of the call banner
  
          boolean isSpecialCall = label != null;
          boolean isAccount = isSpecialCall && !isGatewayCall;
  
          switch  (state) {
              case Call.State.IDLE:
                  // "Call state" is meaningless in this state.
                  break;
              case Call.State.ACTIVE:
                  // We normally don't show a "call state label" at all in this state
                  // (but we can use the call state label to display the provider name).
                  if (isAccount) {
                      callStateLabel = label;
                  } else if (sessionModificationState
                          == Call.SessionModificationState.REQUEST_FAILED) {
                      callStateLabel = context.getString(R.string.card_title_video_call_error);
                  } else if (sessionModificationState
                          == Call.SessionModificationState.WAITING_FOR_RESPONSE) {
                      callStateLabel = context.getString(R.string.card_title_video_call_requesting);
                  } else if (VideoProfile.VideoState.isBidirectional(videoState)) {
                      callStateLabel = context.getString(R.string.card_title_video_call);
                  }
                  break;
              case Call.State.ONHOLD:
                  callStateLabel = context.getString(R.string.card_title_on_hold);
                  break;
              case Call.State.CONNECTING:
              case Call.State.DIALING:
                  if (isSpecialCall) {
                      callStateLabel = context.getString(R.string.calling_via_template, label);
                  } else {
                      callStateLabel = context.getString(R.string.card_title_dialing);
                  }
                  break;
              case Call.State.REDIALING:
                  callStateLabel = context.getString(R.string.card_title_redialing);
                  break;
              case Call.State.INCOMING:
              case Call.State.CALL_WAITING:
                  if (isAccount) {
                      callStateLabel = context.getString(R.string.incoming_via_template, label);
                  } else if (VideoProfile.VideoState.isBidirectional(videoState)) {
                      callStateLabel = context.getString(R.string.notification_incoming_video_call);
                  } else {
                      callStateLabel = context.getString(R.string.card_title_incoming_call);
                  }
                  break;
              case Call.State.DISCONNECTING:
                  // While in the DISCONNECTING state we display a "Hanging up"
                  // message in order to make the UI feel more responsive.  (In
                  // GSM it's normal to see a delay of a couple of seconds while
                  // negotiating the disconnect with the network, so the "Hanging
                  // up" state at least lets the user know that we're doing
                  // something.  This state is currently not used with CDMA.)
                  callStateLabel = context.getString(R.string.card_title_hanging_up);
                  break;
              case Call.State.DISCONNECTED:
                  callStateLabel = disconnectCause.getLabel();
                  if (TextUtils.isEmpty(callStateLabel)) {
                      callStateLabel = context.getString(R.string.card_title_call_ended);
                  }
                  break;
              case Call.State.CONFERENCED:
                  callStateLabel = context.getString(R.string.card_title_conf_call);
                  break;
              default:
                  Log.wtf(this, "updateCallStateWidgets: unexpected call: " + state);
          }
          return callStateLabel;
      }
  
      private void showAndInitializeSecondaryCallInfo(boolean hasProvider) {
          mSecondaryCallInfo.setVisibility(View.VISIBLE);
  
          // mSecondaryCallName is initialized here (vs. onViewCreated) because it is inaccessible
          // until mSecondaryCallInfo is inflated in the call above.
          if (mSecondaryCallName == null) {
              mSecondaryCallName = (TextView) getView().findViewById(R.id.secondaryCallName);
              mSecondaryCallConferenceCallIcon =
                      getView().findViewById(R.id.secondaryCallConferenceCallIcon);
          }
  
          if (mSecondaryCallProviderLabel == null && hasProvider) {
              mSecondaryCallProviderInfo.setVisibility(View.VISIBLE);
              mSecondaryCallProviderLabel = (TextView) getView()
                      .findViewById(R.id.secondaryCallProviderLabel);
          }
      }
  
      public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
          if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
              dispatchPopulateAccessibilityEvent(event, mCallStateLabel);
              dispatchPopulateAccessibilityEvent(event, mPrimaryName);
              dispatchPopulateAccessibilityEvent(event, mPhoneNumber);
              return;
          }
          dispatchPopulateAccessibilityEvent(event, mCallStateLabel);
          dispatchPopulateAccessibilityEvent(event, mPrimaryName);
          dispatchPopulateAccessibilityEvent(event, mPhoneNumber);
          dispatchPopulateAccessibilityEvent(event, mCallTypeLabel);
          dispatchPopulateAccessibilityEvent(event, mSecondaryCallName);
          dispatchPopulateAccessibilityEvent(event, mSecondaryCallProviderLabel);
  
          return;
      }
  
      @Override
      public void setEndCallButtonEnabled(boolean enabled, boolean animate) {
          if (enabled != mFloatingActionButton.isEnabled()) {
              if (animate) {
                  if (enabled) {
                      mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY);
                  } else {
                      mFloatingActionButtonController.scaleOut();
                  }
              } else {
                  if (enabled) {
                      mFloatingActionButtonContainer.setScaleX(1);
                      mFloatingActionButtonContainer.setScaleY(1);
                      mFloatingActionButtonContainer.setVisibility(View.VISIBLE);
                  } else {
                      mFloatingActionButtonContainer.setVisibility(View.GONE);
                  }
              }
              mFloatingActionButton.setEnabled(enabled);
              updateFabPosition();
          }
      }
  
      /**
       * Changes the visibility of the contact photo.
       *
       * @param isVisible {@code True} if the UI should show the contact photo.
       */
      @Override
      public void setPhotoVisible(boolean isVisible) {
          mPhoto.setVisibility(isVisible ? View.VISIBLE : View.GONE);
      }
  
      /**
       * Changes the visibility of the "manage conference call" button.
       *
       * @param visible Whether to set the button to be visible or not.
       */
      @Override
      public void showManageConferenceCallButton(boolean visible) {
          mManageConferenceCallButton.setVisibility(visible ? View.VISIBLE : View.GONE);
      }
  
      /**
       * Determines the current visibility of the manage conference button.
       *
       * @return {@code true} if the button is visible.
       */
      @Override
      public boolean isManageConferenceVisible() {
          return mManageConferenceCallButton.getVisibility() == View.VISIBLE;
      }
  
      /**
       * Get the overall InCallUI background colors and apply to call card.
       */
      public void updateColors() {
          MaterialPalette themeColors = InCallPresenter.getInstance().getThemeColors();
  
          if (mCurrentThemeColors != null && mCurrentThemeColors.equals(themeColors)) {
              return;
          }
  
          mPrimaryCallCardContainer.setBackgroundColor(themeColors.mPrimaryColor);
          mCallButtonsContainer.setBackgroundColor(themeColors.mPrimaryColor);
  
          mCurrentThemeColors = themeColors;
      }
  
      private void dispatchPopulateAccessibilityEvent(AccessibilityEvent event, View view) {
          if (view == null) return;
          final List<CharSequence> eventText = event.getText();
          int size = eventText.size();
          view.dispatchPopulateAccessibilityEvent(event);
          // if no text added write null to keep relative position
          if (size == eventText.size()) {
              eventText.add(null);
          }
      }
  
      public void animateForNewOutgoingCall(final Point touchPoint,
              final boolean showCircularReveal) {
          final ViewGroup parent = (ViewGroup) mPrimaryCallCardContainer.getParent();
  
          final ViewTreeObserver observer = getView().getViewTreeObserver();
  
          mPrimaryCallInfo.getLayoutTransition().disableTransitionType(LayoutTransition.CHANGING);
  
          observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
              @Override
              public void onGlobalLayout() {
                  final ViewTreeObserver observer = getView().getViewTreeObserver();
                  if (!observer.isAlive()) {
                      return;
                  }
                  observer.removeOnGlobalLayoutListener(this);
  
                  final LayoutIgnoringListener listener = new LayoutIgnoringListener();
                  mPrimaryCallCardContainer.addOnLayoutChangeListener(listener);
  
                  // Prepare the state of views before the circular reveal animation
                  final int originalHeight = mPrimaryCallCardContainer.getHeight();
                  mPrimaryCallCardContainer.setBottom(parent.getHeight());
  
                  // Set up FAB.
                  mFloatingActionButtonContainer.setVisibility(View.GONE);
                  mFloatingActionButtonController.setScreenWidth(parent.getWidth());
                  mCallButtonsContainer.setAlpha(0);
                  mCallStateLabel.setAlpha(0);
                  mPrimaryName.setAlpha(0);
                  mCallTypeLabel.setAlpha(0);
                  mCallNumberAndLabel.setAlpha(0);
  
                  final Animator animator = getOutgoingCallAnimator(touchPoint,
                          parent.getHeight(), originalHeight, showCircularReveal);
  
                  animator.addListener(new AnimatorListenerAdapter() {
                      @Override
                      public void onAnimationEnd(Animator animation) {
                          setViewStatePostAnimation(listener);
                      }
                  });
                  animator.start();
              }
          });
      }
  
      public void onDialpadVisiblityChange(boolean isShown) {
          mIsDialpadShowing = isShown;
          updateFabPosition();
      }
  
      private void updateFabPosition() {
          int offsetY = 0;
          if (!mIsDialpadShowing) {
              offsetY = mFloatingActionButtonVerticalOffset;
              if (mSecondaryCallInfo.isShown()) {
                  offsetY -= mSecondaryCallInfo.getHeight();
              }
          }
  
          mFloatingActionButtonController.align(
                  mIsLandscape ? FloatingActionButtonController.ALIGN_QUARTER_END
                          : FloatingActionButtonController.ALIGN_MIDDLE,
                  0 /* offsetX */,
                  offsetY,
                  true);
  
          mFloatingActionButtonController.resize(
                  mIsDialpadShowing ? mFabSmallDiameter : mFabNormalDiameter, true);
      }
  
      @Override
      public void onResume() {
          super.onResume();
          // If the previous launch animation is still running, cancel it so that we don't get
          // stuck in an intermediate animation state.
          if (mAnimatorSet != null && mAnimatorSet.isRunning()) {
              mAnimatorSet.cancel();
          }
  
          mIsLandscape = getResources().getConfiguration().orientation
                  == Configuration.ORIENTATION_LANDSCAPE;
  
          final ViewGroup parent = ((ViewGroup) mPrimaryCallCardContainer.getParent());
          final ViewTreeObserver observer = parent.getViewTreeObserver();
          parent.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
              @Override
              public void onGlobalLayout() {
                  ViewTreeObserver viewTreeObserver = observer;
                  if (!viewTreeObserver.isAlive()) {
                      viewTreeObserver = parent.getViewTreeObserver();
                  }
                  viewTreeObserver.removeOnGlobalLayoutListener(this);
                  mFloatingActionButtonController.setScreenWidth(parent.getWidth());
                  updateFabPosition();
              }
          });
  
          updateColors();
      }
  
      /**
       * Adds a global layout listener to update the FAB's positioning on the next layout. This allows
       * us to position the FAB after the secondary call info's height has been calculated.
       */
      private void updateFabPositionForSecondaryCallInfo() {
          mSecondaryCallInfo.getViewTreeObserver().addOnGlobalLayoutListener(
                  new ViewTreeObserver.OnGlobalLayoutListener() {
                      @Override
                      public void onGlobalLayout() {
                          final ViewTreeObserver observer = mSecondaryCallInfo.getViewTreeObserver();
                          if (!observer.isAlive()) {
                              return;
                          }
                          observer.removeOnGlobalLayoutListener(this);
  
                          onDialpadVisiblityChange(mIsDialpadShowing);
                      }
                  });
      }
  
      /**
       * Animator that performs the upwards shrinking animation of the blue call card scrim.
       * At the start of the animation, each child view is moved downwards by a pre-specified amount
       * and then translated upwards together with the scrim.
       */
      private Animator getShrinkAnimator(int startHeight, int endHeight) {
          final Animator shrinkAnimator =
                  ObjectAnimator.ofInt(mPrimaryCallCardContainer, "bottom", startHeight, endHeight);
          shrinkAnimator.setDuration(mShrinkAnimationDuration);
          shrinkAnimator.addListener(new AnimatorListenerAdapter() {
              @Override
              public void onAnimationStart(Animator animation) {
                  assignTranslateAnimation(mCallStateLabel, 1);
                  assignTranslateAnimation(mCallStateIcon, 1);
                  assignTranslateAnimation(mPrimaryName, 2);
                  assignTranslateAnimation(mCallNumberAndLabel, 3);
                  assignTranslateAnimation(mCallTypeLabel, 4);
                  assignTranslateAnimation(mCallButtonsContainer, 5);
  
                  mFloatingActionButton.setEnabled(true);
              }
          });
          shrinkAnimator.setInterpolator(AnimUtils.EASE_IN);
          return shrinkAnimator;
      }
  
      private Animator getRevealAnimator(Point touchPoint) {
          final Activity activity = getActivity();
          final View view  = activity.getWindow().getDecorView();
          final Display display = activity.getWindowManager().getDefaultDisplay();
          final Point size = new Point();
          display.getSize(size);
  
          int startX = size.x / 2;
          int startY = size.y / 2;
  