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