blob: 7af0269194e68e5d8950e1cf9744a379a89cbffc [file] [log] [blame]
/*
* 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.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.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(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(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);
} else {
mCallNumberAndLabel.setVisibility(View.VISIBLE);
}
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(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 {
if (callStateIcon instanceof AnimationDrawable) {
// Animate the drawable instead of the view
((AnimationDrawable) callStateIcon).start();
} else {
mCallStateIcon.startAnimation(mPulseAnimation);
}
}
} 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);
}
}
@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, String callTimeElapsed) {
if (show) {
if (mElapsedTime.getVisibility() != View.VISIBLE) {
AnimUtils.fadeIn(mElapsedTime, AnimUtils.DEFAULT_DURATION);
}
mElapsedTime.setText(callTimeElapsed);
} 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 (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;
if (touchPoint != null) {
startX = touchPoint.x;
startY = touchPoint.y;
}
final Animator valueAnimator = ViewAnimationUtils.createCircularReveal(view,
startX, startY, 0, Math.max(size.x, size.y));
valueAnimator.setDuration(mRevealAnimationDuration);
return valueAnimator;
}
private Animator getOutgoingCallAnimator(Point touchPoint, int startHeight, int endHeight,
boolean showCircularReveal) {
final Animator shrinkAnimator = getShrinkAnimator(startHeight, endHeight);
if (!showCircularReveal) {
return shrinkAnimator;
}
final Animator revealAnimator = getRevealAnimator(touchPoint);
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(revealAnimator, shrinkAnimator);
return animatorSet;
}
private void assignTranslateAnimation(View view, int offset) {
view.setTranslationY(mTranslationOffset * offset);
view.animate().translationY(0).alpha(1).withLayer()
.setDuration(mShrinkAnimationDuration).setInterpolator(AnimUtils.EASE_IN);
}
private void setViewStatePostAnimation(View view) {
view.setTranslationY(0);
view.setAlpha(1);
}
private void setViewStatePostAnimation(OnLayoutChangeListener layoutChangeListener) {
setViewStatePostAnimation(mCallButtonsContainer);
setViewStatePostAnimation(mCallStateLabel);
setViewStatePostAnimation(mPrimaryName);
setViewStatePostAnimation(mCallTypeLabel);
setViewStatePostAnimation(mCallNumberAndLabel);
setViewStatePostAnimation(mCallStateIcon);
mPrimaryCallCardContainer.removeOnLayoutChangeListener(layoutChangeListener);
mPrimaryCallInfo.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY);
}
private final class LayoutIgnoringListener implements View.OnLayoutChangeListener {
@Override
public void onLayoutChange(View v,
int left,
int top,
int right,
int bottom,
int oldLeft,
int oldTop,
int oldRight,
int oldBottom) {
v.setLeft(oldLeft);
v.setRight(oldRight);
v.setTop(oldTop);
v.setBottom(oldBottom);
}
}
}