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