1ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian/*
2ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Copyright (C) 2016 The Android Open Source Project
3ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian *
4ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Licensed under the Apache License, Version 2.0 (the "License");
5ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * you may not use this file except in compliance with the License.
6ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * You may obtain a copy of the License at
7ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian *
8ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian *      http://www.apache.org/licenses/LICENSE-2.0
9ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian *
10ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Unless required by applicable law or agreed to in writing, software
11ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * distributed under the License is distributed on an "AS IS" BASIS,
12ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * See the License for the specific language governing permissions and
14ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * limitations under the License
15ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian */
16ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
17ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianpackage com.android.incallui.answer.impl.answermethod;
18ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
19ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.animation.Animator;
20ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.animation.AnimatorListenerAdapter;
21ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.animation.AnimatorSet;
22ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.animation.ObjectAnimator;
23ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.animation.PropertyValuesHolder;
24ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.animation.ValueAnimator;
25ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.annotation.SuppressLint;
26ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.content.Context;
27ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.content.res.ColorStateList;
28ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.graphics.PorterDuff.Mode;
29ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.graphics.drawable.Drawable;
30ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.os.Bundle;
31ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.annotation.ColorInt;
32ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.annotation.FloatRange;
33ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.annotation.IntDef;
34ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.annotation.NonNull;
35ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.annotation.Nullable;
36ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.annotation.VisibleForTesting;
37ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.v4.graphics.ColorUtils;
38ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.v4.view.animation.FastOutLinearInInterpolator;
39ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.v4.view.animation.FastOutSlowInInterpolator;
40ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.v4.view.animation.LinearOutSlowInInterpolator;
41ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.v4.view.animation.PathInterpolatorCompat;
42ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.view.LayoutInflater;
43ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.view.MotionEvent;
44ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.view.View;
45ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.view.View.AccessibilityDelegate;
46ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.view.ViewGroup;
47ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.view.accessibility.AccessibilityNodeInfo;
48ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
49ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.view.animation.BounceInterpolator;
50ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.view.animation.DecelerateInterpolator;
51ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.view.animation.Interpolator;
52ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.widget.ImageView;
53ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.widget.TextView;
54ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport com.android.dialer.common.DpUtil;
55ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport com.android.dialer.common.LogUtil;
56ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport com.android.dialer.common.MathUtil;
57ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport com.android.dialer.util.DrawableConverter;
58ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport com.android.dialer.util.ViewUtil;
59ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport com.android.incallui.answer.impl.answermethod.FlingUpDownTouchHandler.OnProgressChangedListener;
60ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport com.android.incallui.answer.impl.classifier.FalsingManager;
61ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport com.android.incallui.answer.impl.hint.AnswerHint;
62ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport com.android.incallui.answer.impl.hint.AnswerHintFactory;
63d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.incallui.answer.impl.hint.PawImageLoaderImpl;
64ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport java.lang.annotation.Retention;
65ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport java.lang.annotation.RetentionPolicy;
66ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
67ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian/** Answer method that swipes up to answer or down to reject. */
68ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian@SuppressLint("ClickableViewAccessibility")
69ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianpublic class FlingUpDownMethod extends AnswerMethod implements OnProgressChangedListener {
70ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
71ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final float SWIPE_LERP_PROGRESS_FACTOR = 0.5f;
72ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final long ANIMATE_DURATION_SHORT_MILLIS = 667;
73ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final long ANIMATE_DURATION_NORMAL_MILLIS = 1_333;
74ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final long ANIMATE_DURATION_LONG_MILLIS = 1_500;
75ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final long BOUNCE_ANIMATION_DELAY = 167;
76ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final long VIBRATION_TIME_MILLIS = 1_833;
77ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final long SETTLE_ANIMATION_DURATION_MILLIS = 100;
78ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final int HINT_JUMP_DP = 60;
79ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final int HINT_DIP_DP = 8;
80ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final float HINT_SCALE_RATIO = 1.15f;
81ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final long SWIPE_TO_DECLINE_FADE_IN_DELAY_MILLIS = 333;
82ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final int HINT_REJECT_SHOW_DURATION_MILLIS = 2000;
83ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final int ICON_END_CALL_ROTATION_DEGREES = 135;
84ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final int HINT_REJECT_FADE_TRANSLATION_Y_DP = -8;
85ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final float SWIPE_TO_ANSWER_MAX_TRANSLATION_Y_DP = 150;
86ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static final int SWIPE_TO_REJECT_MAX_TRANSLATION_Y_DP = 24;
87ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
88ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Retention(RetentionPolicy.SOURCE)
89ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @IntDef(
90ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    value = {
91ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      AnimationState.NONE,
92ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      AnimationState.ENTRY,
93ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      AnimationState.BOUNCE,
94ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      AnimationState.SWIPE,
95ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      AnimationState.SETTLE,
96ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      AnimationState.HINT,
97ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      AnimationState.COMPLETED
98ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
99ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  )
100ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @VisibleForTesting
101ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @interface AnimationState {
102ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
103ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    int NONE = 0;
104ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    int ENTRY = 1; // Entry animation for incoming call
105ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    int BOUNCE = 2; // An idle state in which text and icon slightly bounces off its base repeatedly
106ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    int SWIPE = 3; // A special state in which text and icon follows the finger movement
107ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    int SETTLE = 4; // A short animation to reset from swipe and prepare for hint or bounce
108ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    int HINT = 5; // Jump animation to suggest what to do
109ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    int COMPLETED = 6; // Animation loop completed. Occurs after user swipes beyond threshold
110ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
111ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
112ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static void moveTowardY(View view, float newY) {
113ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    view.setTranslationY(MathUtil.lerp(view.getTranslationY(), newY, SWIPE_LERP_PROGRESS_FACTOR));
114ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
115ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
116ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static void moveTowardX(View view, float newX) {
117ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    view.setTranslationX(MathUtil.lerp(view.getTranslationX(), newX, SWIPE_LERP_PROGRESS_FACTOR));
118ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
119ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
120ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static void fadeToward(View view, float newAlpha) {
121ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    view.setAlpha(MathUtil.lerp(view.getAlpha(), newAlpha, SWIPE_LERP_PROGRESS_FACTOR));
122ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
123ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
124ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static void rotateToward(View view, float newRotation) {
125ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    view.setRotation(MathUtil.lerp(view.getRotation(), newRotation, SWIPE_LERP_PROGRESS_FACTOR));
126ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
127ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
128ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private TextView swipeToAnswerText;
129ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private TextView swipeToRejectText;
130ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private View contactPuckContainer;
131ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private ImageView contactPuckBackground;
132ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private ImageView contactPuckIcon;
133ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private View incomingDisconnectText;
1348369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian  private View spaceHolder;
135ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private Animator lockBounceAnim;
136ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private AnimatorSet lockEntryAnim;
137ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private AnimatorSet lockHintAnim;
138ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private AnimatorSet lockSettleAnim;
139ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @AnimationState private int animationState = AnimationState.NONE;
140ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @AnimationState private int afterSettleAnimationState = AnimationState.NONE;
141ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  // a value for finger swipe progress. -1 or less for "reject"; 1 or more for "accept".
142ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private float swipeProgress;
143ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private Animator rejectHintHide;
144ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private Animator vibrationAnimator;
145ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private Drawable contactPhoto;
146ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private boolean incomingWillDisconnect;
147ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private FlingUpDownTouchHandler touchHandler;
148ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private FalsingManager falsingManager;
149ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
150ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private AnswerHint answerHint;
151ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
152ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Override
153ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public void onCreate(@Nullable Bundle bundle) {
154ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    super.onCreate(bundle);
155ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    falsingManager = new FalsingManager(getContext());
156ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
157ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
158ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Override
159ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public void onStart() {
160ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    super.onStart();
161ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    falsingManager.onScreenOn();
162ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (getView() != null) {
163ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      if (animationState == AnimationState.SWIPE || animationState == AnimationState.HINT) {
164ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        swipeProgress = 0;
165ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        updateContactPuck();
166ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        onMoveReset(false);
167ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      } else if (animationState == AnimationState.ENTRY) {
168ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        // When starting from the lock screen, the activity may be stopped and started briefly.
169ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        // Don't let that interrupt the entry animation
170ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        startSwipeToAnswerEntryAnimation();
171ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      }
172ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
173ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
174ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
175ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Override
176ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public void onStop() {
177ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    endAnimation();
178ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    falsingManager.onScreenOff();
179ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (getActivity().isFinishing()) {
180ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      setAnimationState(AnimationState.COMPLETED);
181ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
182ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    super.onStop();
183ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
184ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
185ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Nullable
186ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Override
187ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public View onCreateView(
188ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) {
189ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    View view = layoutInflater.inflate(R.layout.swipe_up_down_method, viewGroup, false);
190ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
191ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckContainer = view.findViewById(R.id.incoming_call_puck_container);
192ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckBackground = (ImageView) view.findViewById(R.id.incoming_call_puck_bg);
193ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckIcon = (ImageView) view.findViewById(R.id.incoming_call_puck_icon);
194ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    swipeToAnswerText = (TextView) view.findViewById(R.id.incoming_swipe_to_answer_text);
195ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    swipeToRejectText = (TextView) view.findViewById(R.id.incoming_swipe_to_reject_text);
196ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    incomingDisconnectText = view.findViewById(R.id.incoming_will_disconnect_text);
1978369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian    incomingDisconnectText.setVisibility(incomingWillDisconnect ? View.VISIBLE : View.GONE);
198ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    incomingDisconnectText.setAlpha(incomingWillDisconnect ? 1 : 0);
1998369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian    spaceHolder = view.findViewById(R.id.incoming_bouncer_space_holder);
2008369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian    spaceHolder.setVisibility(incomingWillDisconnect ? View.GONE : View.VISIBLE);
2018369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian
2028369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian    view.findViewById(R.id.incoming_swipe_to_answer_container)
2038369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian        .setAccessibilityDelegate(
2048369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian            new AccessibilityDelegate() {
2058369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian              @Override
2068369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian              public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2078369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                super.onInitializeAccessibilityNodeInfo(host, info);
2088369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                info.addAction(
2098369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                    new AccessibilityAction(
2108369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                        R.id.accessibility_action_answer,
2118369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                        getString(R.string.call_incoming_answer)));
2128369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                info.addAction(
2138369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                    new AccessibilityAction(
2148369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                        R.id.accessibility_action_decline,
2158369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                        getString(R.string.call_incoming_decline)));
2168369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian              }
2178369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian
2188369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian              @Override
2198369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian              public boolean performAccessibilityAction(View host, int action, Bundle args) {
2208369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                if (action == R.id.accessibility_action_answer) {
2218369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                  performAccept();
2228369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                  return true;
2238369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                } else if (action == R.id.accessibility_action_decline) {
2248369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                  performReject();
2258369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                  return true;
2268369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                }
2278369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                return super.performAccessibilityAction(host, action, args);
2288369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian              }
2298369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian            });
230ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
231ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    swipeProgress = 0;
232ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
233ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    updateContactPuck();
234ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
235ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    touchHandler = FlingUpDownTouchHandler.attach(view, this, falsingManager);
236ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
237ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    answerHint =
238d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        new AnswerHintFactory(new PawImageLoaderImpl())
239ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            .create(getContext(), ANIMATE_DURATION_LONG_MILLIS, BOUNCE_ANIMATION_DELAY);
240ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    answerHint.onCreateView(
241ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        layoutInflater,
242ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        (ViewGroup) view.findViewById(R.id.hint_container),
243ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        contactPuckContainer,
244ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        swipeToAnswerText);
245ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    return view;
246ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
247ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
248ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Override
249ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public void onViewCreated(View view, @Nullable Bundle bundle) {
250ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    super.onViewCreated(view, bundle);
251ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    setAnimationState(AnimationState.ENTRY);
252ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
253ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
254ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Override
255ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public void onDestroyView() {
256ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    super.onDestroyView();
257ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (touchHandler != null) {
258ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      touchHandler.detach();
259ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      touchHandler = null;
260ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
261ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
262ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
263ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Override
264ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public void onProgressChanged(@FloatRange(from = -1f, to = 1f) float progress) {
265ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    swipeProgress = progress;
266ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (animationState == AnimationState.SWIPE && getContext() != null && isVisible()) {
267ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      updateSwipeTextAndPuckForTouch();
268ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
269ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
270ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
271ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Override
272ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public void onTrackingStart() {
273ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    setAnimationState(AnimationState.SWIPE);
274ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
275ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
276ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Override
277ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public void onTrackingStopped() {}
278ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
279ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Override
280ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public void onMoveReset(boolean showHint) {
281ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (showHint) {
282ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      showSwipeHint();
283ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    } else {
284ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      setAnimationState(AnimationState.BOUNCE);
285ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
286ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    resetTouchState();
287ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    getParent().resetAnswerProgress();
288ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
289ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
290ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Override
291ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public void onMoveFinish(boolean accept) {
292ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    touchHandler.setTouchEnabled(false);
293ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    answerHint.onAnswered();
294ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (accept) {
295ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      performAccept();
296ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    } else {
297ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      performReject();
298ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
299ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
300ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
301ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Override
302ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public boolean shouldUseFalsing(@NonNull MotionEvent downEvent) {
303ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (contactPuckContainer == null) {
304ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      return false;
305ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
306ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
307ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    float puckCenterX = contactPuckContainer.getX() + (contactPuckContainer.getWidth() / 2);
308ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    float puckCenterY = contactPuckContainer.getY() + (contactPuckContainer.getHeight() / 2);
309ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    double radius = contactPuckContainer.getHeight() / 2;
310ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
311ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Squaring a number is more performant than taking a sqrt, so we compare the square of the
312ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // distance with the square of the radius.
313ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    double distSq =
314ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        Math.pow(downEvent.getX() - puckCenterX, 2) + Math.pow(downEvent.getY() - puckCenterY, 2);
315ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    return distSq >= Math.pow(radius, 2);
316ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
317ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
318ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Override
319ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public void setContactPhoto(Drawable contactPhoto) {
320ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    this.contactPhoto = contactPhoto;
321ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
322ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    updateContactPuck();
323ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
324ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
325ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void updateContactPuck() {
326ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (contactPuckIcon == null) {
327ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      return;
328ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
329d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (getParent().isVideoCall() || getParent().isVideoUpgradeRequest()) {
330ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      contactPuckIcon.setImageResource(R.drawable.quantum_ic_videocam_white_24);
331ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    } else {
332ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      contactPuckIcon.setImageResource(R.drawable.quantum_ic_call_white_24);
333ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
334ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
335ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    int size =
336ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        contactPuckBackground
337ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            .getResources()
338ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            .getDimensionPixelSize(
339ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian                shouldShowPhotoInPuck()
340ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian                    ? R.dimen.answer_contact_puck_size_photo
341ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian                    : R.dimen.answer_contact_puck_size_no_photo);
342ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckBackground.setImageDrawable(
343ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        shouldShowPhotoInPuck()
344ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            ? makeRoundedDrawable(contactPuckBackground.getContext(), contactPhoto, size)
345ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            : null);
346ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    ViewGroup.LayoutParams contactPuckParams = contactPuckBackground.getLayoutParams();
347ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckParams.height = size;
348ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckParams.width = size;
349ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckBackground.setLayoutParams(contactPuckParams);
350ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckIcon.setAlpha(shouldShowPhotoInPuck() ? 0f : 1f);
351ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
352ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
353ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private Drawable makeRoundedDrawable(Context context, Drawable contactPhoto, int size) {
354ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    return DrawableConverter.getRoundedDrawable(context, contactPhoto, size, size);
355ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
356ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
357ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private boolean shouldShowPhotoInPuck() {
358d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return (getParent().isVideoCall() || getParent().isVideoUpgradeRequest())
359d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        && contactPhoto != null;
360ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
361ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
362ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Override
363ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public void setHintText(@Nullable CharSequence hintText) {
364ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (hintText == null) {
365ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      swipeToAnswerText.setText(R.string.call_incoming_swipe_to_answer);
366ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      swipeToRejectText.setText(R.string.call_incoming_swipe_to_reject);
367ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    } else {
368ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      swipeToAnswerText.setText(hintText);
369ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      swipeToRejectText.setText(null);
370ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
371ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
372ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
373ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @Override
374ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public void setShowIncomingWillDisconnect(boolean incomingWillDisconnect) {
375ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    this.incomingWillDisconnect = incomingWillDisconnect;
376ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (incomingDisconnectText != null) {
3778369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian      if (incomingWillDisconnect) {
3788369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian        incomingDisconnectText.setVisibility(View.VISIBLE);
3798369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian        spaceHolder.setVisibility(View.GONE);
3808369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian        incomingDisconnectText.animate().alpha(1);
3818369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian      } else {
3828369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian        incomingDisconnectText
3838369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian            .animate()
3848369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian            .alpha(0)
3858369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian            .setListener(
3868369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                new AnimatorListenerAdapter() {
3878369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                  @Override
3888369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                  public void onAnimationEnd(Animator animation) {
3898369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                    super.onAnimationEnd(animation);
3908369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                    incomingDisconnectText.setVisibility(View.GONE);
3918369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                    spaceHolder.setVisibility(View.VISIBLE);
3928369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                  }
3938369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian                });
3948369df095a73a77b3715f8ae7ba06089cebca4ceEric Erfanian      }
395ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
396ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
397ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
398ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void showSwipeHint() {
399ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    setAnimationState(AnimationState.HINT);
400ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
401ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
402ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void updateSwipeTextAndPuckForTouch() {
403ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Clamp progress value between -1 and 1.
404ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    final float clampedProgress = MathUtil.clamp(swipeProgress, -1 /* min */, 1 /* max */);
405ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    final float positiveAdjustedProgress = Math.abs(clampedProgress);
406ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    final boolean isAcceptingFlow = clampedProgress >= 0;
407ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
408ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Cancel view property animators on views we're about to mutate
409ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    swipeToAnswerText.animate().cancel();
410ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckIcon.animate().cancel();
411ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
412ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Since the animation progression is controlled by user gesture instead of real timeline, the
413ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // spec timeline can be divided into 9 slots. Each slot is equivalent to 83ms in the spec.
414ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Therefore, we use 9 slots of 83ms to map user gesture into the spec timeline.
4152df4538eb90b896be15eebc1d9adf1206131c8a3Sailesh Nepal    //
4162df4538eb90b896be15eebc1d9adf1206131c8a3Sailesh Nepal    // See specs -
4172df4538eb90b896be15eebc1d9adf1206131c8a3Sailesh Nepal    // Accept: https://direct.googleplex.com/#/spec/8510001
4182df4538eb90b896be15eebc1d9adf1206131c8a3Sailesh Nepal    // Decline: https://direct.googleplex.com/#/spec/3850001
419ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    final float progressSlots = 9;
420ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
421ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Fade out the "swipe up to answer". It only takes 1 slot to complete the fade.
422ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    float swipeTextAlpha = Math.max(0, 1 - Math.abs(clampedProgress) * progressSlots);
423ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    fadeToward(swipeToAnswerText, swipeTextAlpha);
424ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Fade out the "swipe down to dismiss" at the same time. Don't ever increase its alpha
425ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    fadeToward(swipeToRejectText, Math.min(swipeTextAlpha, swipeToRejectText.getAlpha()));
426ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Fade out the "incoming will disconnect" text
427ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    fadeToward(incomingDisconnectText, incomingWillDisconnect ? swipeTextAlpha : 0);
428ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
429ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Move swipe text back to zero.
430ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    moveTowardX(swipeToAnswerText, 0 /* newX */);
431ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    moveTowardY(swipeToAnswerText, 0 /* newY */);
432ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
433ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Animate puck color
434ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    @ColorInt
435ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    int destPuckColor =
436ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        getContext()
437ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            .getColor(
438ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian                isAcceptingFlow ? R.color.call_accept_background : R.color.call_hangup_background);
439ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    destPuckColor =
440ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ColorUtils.setAlphaComponent(destPuckColor, (int) (0xFF * positiveAdjustedProgress));
441ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckBackground.setBackgroundTintList(ColorStateList.valueOf(destPuckColor));
442ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckBackground.setBackgroundTintMode(Mode.SRC_ATOP);
443ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckBackground.setColorFilter(destPuckColor);
444ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
445ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Animate decline icon
446d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (isAcceptingFlow || getParent().isVideoCall() || getParent().isVideoUpgradeRequest()) {
447ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      rotateToward(contactPuckIcon, 0f);
448ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    } else {
449ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      rotateToward(contactPuckIcon, positiveAdjustedProgress * ICON_END_CALL_ROTATION_DEGREES);
450ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
451ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
452ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Fade in icon
453ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (shouldShowPhotoInPuck()) {
454ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      fadeToward(contactPuckIcon, positiveAdjustedProgress);
455ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
456ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    float iconProgress = Math.min(1f, positiveAdjustedProgress * 4);
457ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    @ColorInt
458ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    int iconColor =
459ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ColorUtils.setAlphaComponent(
460ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckIcon.getContext().getColor(R.color.incoming_answer_icon),
461ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            (int) (0xFF * (1 - iconProgress)));
462ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckIcon.setImageTintList(ColorStateList.valueOf(iconColor));
463ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
464ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Move puck.
465ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (isAcceptingFlow) {
466ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      moveTowardY(
467ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          contactPuckContainer,
468ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          -clampedProgress * DpUtil.dpToPx(getContext(), SWIPE_TO_ANSWER_MAX_TRANSLATION_Y_DP));
469ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    } else {
470ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      moveTowardY(
471ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          contactPuckContainer,
472ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          -clampedProgress * DpUtil.dpToPx(getContext(), SWIPE_TO_REJECT_MAX_TRANSLATION_Y_DP));
473ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
474ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
475ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    getParent().onAnswerProgressUpdate(clampedProgress);
476ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
477ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
478ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void startSwipeToAnswerSwipeAnimation() {
479ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    LogUtil.i("FlingUpDownMethod.startSwipeToAnswerSwipeAnimation", "Start swipe animation.");
480ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    resetTouchState();
481ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    endAnimation();
482ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
483ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
484ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void setPuckTouchState() {
485ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckBackground.setActivated(touchHandler.isTracking());
486ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
487ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
488ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void resetTouchState() {
489ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (getContext() == null) {
490ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      // State will be reset in onStart(), so just abort.
491ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      return;
492ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
493ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckContainer.animate().scaleX(1 /* scaleX */);
494ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckContainer.animate().scaleY(1 /* scaleY */);
495ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckBackground.animate().scaleX(1 /* scaleX */);
496ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckBackground.animate().scaleY(1 /* scaleY */);
497ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckBackground.setBackgroundTintList(null);
498ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckBackground.setColorFilter(null);
499ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckIcon.setImageTintList(
500ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ColorStateList.valueOf(getContext().getColor(R.color.incoming_answer_icon)));
501ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckIcon.animate().rotation(0);
502ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
503ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    getParent().resetAnswerProgress();
504ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    setPuckTouchState();
505ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
506ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    final float alpha = 1;
507ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    swipeToAnswerText.animate().alpha(alpha);
508ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckContainer.animate().alpha(alpha);
509ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckBackground.animate().alpha(alpha);
510ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckIcon.animate().alpha(shouldShowPhotoInPuck() ? 0 : alpha);
511ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
512ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
513ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @VisibleForTesting
514ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  void setAnimationState(@AnimationState int state) {
515ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (state != AnimationState.HINT && animationState == state) {
516ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      return;
517ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
518ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
519ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (animationState == AnimationState.COMPLETED) {
520ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      LogUtil.e(
521ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          "FlingUpDownMethod.setAnimationState",
522ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          "Animation loop has completed. Cannot switch to new state: " + state);
523ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      return;
524ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
525ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
526ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (state == AnimationState.HINT || state == AnimationState.BOUNCE) {
527ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      if (animationState == AnimationState.SWIPE) {
528ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        afterSettleAnimationState = state;
529ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        state = AnimationState.SETTLE;
530ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      }
531ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
532ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
533ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    LogUtil.i("FlingUpDownMethod.setAnimationState", "animation state: " + state);
534ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    animationState = state;
535ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
536ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Start animation after the current one is finished completely.
537ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    View view = getView();
538ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (view != null) {
539ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      // As long as the fragment is added, we can start update the animation state.
540ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      if (isAdded() && (animationState == state)) {
541ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        updateAnimationState();
542ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      } else {
543ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        endAnimation();
544ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      }
545ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
546ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
547ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
548ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @AnimationState
549ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @VisibleForTesting
550ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  int getAnimationState() {
551ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    return animationState;
552ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
553ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
554ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void updateAnimationState() {
555ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    switch (animationState) {
556ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      case AnimationState.ENTRY:
557ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        startSwipeToAnswerEntryAnimation();
558ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        break;
559ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      case AnimationState.BOUNCE:
560ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        startSwipeToAnswerBounceAnimation();
561ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        break;
562ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      case AnimationState.SWIPE:
563ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        startSwipeToAnswerSwipeAnimation();
564ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        break;
565ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      case AnimationState.SETTLE:
566ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        startSwipeToAnswerSettleAnimation();
567ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        break;
568ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      case AnimationState.COMPLETED:
569ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        clearSwipeToAnswerUi();
570ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        break;
571ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      case AnimationState.HINT:
572ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        startSwipeToAnswerHintAnimation();
573ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        break;
574ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      case AnimationState.NONE:
575ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      default:
576ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        LogUtil.e(
577ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            "FlingUpDownMethod.updateAnimationState",
578ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            "Unexpected animation state: " + animationState);
579ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        break;
580ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
581ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
582ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
583ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void startSwipeToAnswerEntryAnimation() {
584ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    LogUtil.i("FlingUpDownMethod.startSwipeToAnswerEntryAnimation", "Swipe entry animation.");
585ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    endAnimation();
586ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
587ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockEntryAnim = new AnimatorSet();
588ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator textUp =
589ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofFloat(
590ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            swipeToAnswerText,
591ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            View.TRANSLATION_Y,
592ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            DpUtil.dpToPx(getContext(), 192 /* dp */),
593ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            DpUtil.dpToPx(getContext(), -20 /* dp */));
594ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    textUp.setDuration(ANIMATE_DURATION_NORMAL_MILLIS);
595ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    textUp.setInterpolator(new LinearOutSlowInInterpolator());
596ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
597ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator textDown =
598ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofFloat(
599ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            swipeToAnswerText,
600ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            View.TRANSLATION_Y,
601ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            DpUtil.dpToPx(getContext(), -20) /* dp */,
602ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            0 /* end pos */);
603ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    textDown.setDuration(ANIMATE_DURATION_NORMAL_MILLIS);
604ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    textUp.setInterpolator(new FastOutSlowInInterpolator());
605ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
606ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // "Swipe down to reject" text fades in with a slight translation
607ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    swipeToRejectText.setAlpha(0f);
608ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator rejectTextShow =
609ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofPropertyValuesHolder(
610ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            swipeToRejectText,
611ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            PropertyValuesHolder.ofFloat(View.ALPHA, 1f),
612ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            PropertyValuesHolder.ofFloat(
613ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian                View.TRANSLATION_Y,
614ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian                DpUtil.dpToPx(getContext(), HINT_REJECT_FADE_TRANSLATION_Y_DP),
615ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian                0f));
616ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectTextShow.setInterpolator(new FastOutLinearInInterpolator());
617ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectTextShow.setDuration(ANIMATE_DURATION_SHORT_MILLIS);
618ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectTextShow.setStartDelay(SWIPE_TO_DECLINE_FADE_IN_DELAY_MILLIS);
619ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
620ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator puckUp =
621ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofFloat(
622ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckContainer,
623ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            View.TRANSLATION_Y,
624ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            DpUtil.dpToPx(getContext(), 400 /* dp */),
625ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            DpUtil.dpToPx(getContext(), -12 /* dp */));
626ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckUp.setDuration(ANIMATE_DURATION_LONG_MILLIS);
627ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckUp.setInterpolator(
628ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        PathInterpolatorCompat.create(
629ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            0 /* controlX1 */, 0 /* controlY1 */, 0 /* controlX2 */, 1 /* controlY2 */));
630ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
631ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator puckDown =
632ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofFloat(
633ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckContainer,
634ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            View.TRANSLATION_Y,
635ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            DpUtil.dpToPx(getContext(), -12 /* dp */),
636ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            0 /* end pos */);
637ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckDown.setDuration(ANIMATE_DURATION_NORMAL_MILLIS);
638ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckDown.setInterpolator(new FastOutSlowInInterpolator());
639ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
640ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator puckScaleUp =
641ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        createUniformScaleAnimators(
642ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckBackground,
643ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            0.33f /* beginScale */,
644ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            1.1f /* endScale */,
645ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            ANIMATE_DURATION_NORMAL_MILLIS,
646ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            PathInterpolatorCompat.create(
647ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian                0.4f /* controlX1 */, 0 /* controlY1 */, 0 /* controlX2 */, 1 /* controlY2 */));
648ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator puckScaleDown =
649ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        createUniformScaleAnimators(
650ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckBackground,
651ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            1.1f /* beginScale */,
652ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            1 /* endScale */,
653ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            ANIMATE_DURATION_NORMAL_MILLIS,
654ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            new FastOutSlowInInterpolator());
655ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
656ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Upward animation chain.
657ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockEntryAnim.play(textUp).with(puckScaleUp).with(puckUp);
658ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
659ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Downward animation chain.
660ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockEntryAnim.play(textDown).with(puckDown).with(puckScaleDown).after(puckUp);
661ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
662ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockEntryAnim.play(rejectTextShow).after(puckUp);
663ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
664ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Add vibration animation.
665ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    addVibrationAnimator(lockEntryAnim);
666ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
667ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockEntryAnim.addListener(
668ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        new AnimatorListenerAdapter() {
669ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
670ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          public boolean canceled;
671ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
672ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          @Override
673ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          public void onAnimationCancel(Animator animation) {
674ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            super.onAnimationCancel(animation);
675ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            canceled = true;
676ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          }
677ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
678ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          @Override
679ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          public void onAnimationEnd(Animator animation) {
680ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            super.onAnimationEnd(animation);
681ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            if (!canceled) {
682ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian              onEntryAnimationDone();
683ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            }
684ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          }
685ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        });
686ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockEntryAnim.start();
687ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
688ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
689ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @VisibleForTesting
690ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  void onEntryAnimationDone() {
691ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    LogUtil.i("FlingUpDownMethod.onEntryAnimationDone", "Swipe entry anim ends.");
692ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (animationState == AnimationState.ENTRY) {
693ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      setAnimationState(AnimationState.BOUNCE);
694ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
695ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
696ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
697ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void startSwipeToAnswerBounceAnimation() {
698ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    LogUtil.i("FlingUpDownMethod.startSwipeToAnswerBounceAnimation", "Swipe bounce animation.");
699ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    endAnimation();
700ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
701ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (ViewUtil.areAnimationsDisabled(getContext())) {
702ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      swipeToAnswerText.setTranslationY(0);
703ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      contactPuckContainer.setTranslationY(0);
704ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      contactPuckBackground.setScaleY(1f);
705ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      contactPuckBackground.setScaleX(1f);
706ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      swipeToRejectText.setAlpha(1f);
707ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      swipeToRejectText.setTranslationY(0);
708ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      return;
709ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
710ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
711ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockBounceAnim = createBreatheAnimation();
712ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
713ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    answerHint.onBounceStart();
714ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockBounceAnim.addListener(
715ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        new AnimatorListenerAdapter() {
716ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          boolean firstPass = true;
717ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
718ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          @Override
719ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          public void onAnimationEnd(Animator animation) {
720ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            super.onAnimationEnd(animation);
721ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            if (getContext() != null
722ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian                && lockBounceAnim != null
723ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian                && animationState == AnimationState.BOUNCE) {
724ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian              // AnimatorSet doesn't have repeat settings. Instead, we start a new one after the
725ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian              // previous set is completed, until endAnimation is called.
726ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian              LogUtil.v("FlingUpDownMethod.onAnimationEnd", "Bounce again.");
727ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
728ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian              // If this is the first time repeating the animation, we should recreate it so its
729ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian              // starting values will be correct
730ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian              if (firstPass) {
731ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian                lockBounceAnim = createBreatheAnimation();
732ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian                lockBounceAnim.addListener(this);
733ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian              }
734ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian              firstPass = false;
735ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian              answerHint.onBounceStart();
736ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian              lockBounceAnim.start();
737ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            }
738ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          }
739ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        });
740ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockBounceAnim.start();
741ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
742ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
743ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private Animator createBreatheAnimation() {
744ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    AnimatorSet breatheAnimation = new AnimatorSet();
745ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    float textOffset = DpUtil.dpToPx(getContext(), 42 /* dp */);
746ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator textUp =
747ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofFloat(
748ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            swipeToAnswerText, View.TRANSLATION_Y, 0 /* begin pos */, -textOffset);
749ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    textUp.setInterpolator(new FastOutSlowInInterpolator());
750ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    textUp.setDuration(ANIMATE_DURATION_NORMAL_MILLIS);
751ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
752ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator textDown =
753ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofFloat(swipeToAnswerText, View.TRANSLATION_Y, -textOffset, 0 /* end pos */);
754ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    textDown.setInterpolator(new FastOutSlowInInterpolator());
755ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    textDown.setDuration(ANIMATE_DURATION_NORMAL_MILLIS);
756ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
757ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // "Swipe down to reject" text fade in
758ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator rejectTextShow = ObjectAnimator.ofFloat(swipeToRejectText, View.ALPHA, 1f);
759ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectTextShow.setInterpolator(new LinearOutSlowInInterpolator());
760ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectTextShow.setDuration(ANIMATE_DURATION_SHORT_MILLIS);
761ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectTextShow.setStartDelay(SWIPE_TO_DECLINE_FADE_IN_DELAY_MILLIS);
762ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
763ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // reject hint text translate in
764ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator rejectTextTranslate =
765ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofFloat(
766ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            swipeToRejectText,
767ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            View.TRANSLATION_Y,
768ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            DpUtil.dpToPx(getContext(), HINT_REJECT_FADE_TRANSLATION_Y_DP),
769ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            0f);
770ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectTextTranslate.setInterpolator(new FastOutSlowInInterpolator());
771ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectTextTranslate.setDuration(ANIMATE_DURATION_NORMAL_MILLIS);
772ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
773ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // reject hint text fade out
774ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator rejectTextHide = ObjectAnimator.ofFloat(swipeToRejectText, View.ALPHA, 0f);
775ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectTextHide.setInterpolator(new FastOutLinearInInterpolator());
776ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectTextHide.setDuration(ANIMATE_DURATION_SHORT_MILLIS);
777ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
778ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Interpolator curve =
779ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        PathInterpolatorCompat.create(
780ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            0.4f /* controlX1 */, 0 /* controlY1 */, 0 /* controlX2 */, 1 /* controlY2 */);
781ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    float puckOffset = DpUtil.dpToPx(getContext(), 42 /* dp */);
782ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator puckUp = ObjectAnimator.ofFloat(contactPuckContainer, View.TRANSLATION_Y, -puckOffset);
783ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckUp.setInterpolator(curve);
784ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckUp.setDuration(ANIMATE_DURATION_LONG_MILLIS);
785ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
786ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    final float scale = 1.0625f;
787ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator puckScaleUp =
788ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        createUniformScaleAnimators(
789ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckBackground,
790ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            1 /* beginScale */,
791ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            scale,
792ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            ANIMATE_DURATION_NORMAL_MILLIS,
793ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            curve);
794ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
795ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator puckDown =
796ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofFloat(contactPuckContainer, View.TRANSLATION_Y, 0 /* end pos */);
797ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckDown.setInterpolator(new FastOutSlowInInterpolator());
798ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckDown.setDuration(ANIMATE_DURATION_NORMAL_MILLIS);
799ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
800ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator puckScaleDown =
801ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        createUniformScaleAnimators(
802ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckBackground,
803ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            scale,
804ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            1 /* endScale */,
805ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            ANIMATE_DURATION_NORMAL_MILLIS,
806ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            new FastOutSlowInInterpolator());
807ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
808ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Bounce upward animation chain.
809ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    breatheAnimation
810ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .play(textUp)
811ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(rejectTextHide)
812ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(puckUp)
813ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(puckScaleUp)
814ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .after(167 /* delay */);
815ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
816ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Bounce downward animation chain.
817ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    breatheAnimation
818ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .play(puckDown)
819ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(textDown)
820ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(puckScaleDown)
821ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(rejectTextShow)
822ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(rejectTextTranslate)
823ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .after(puckUp);
824ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
825ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Add vibration animation to the animator set.
826ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    addVibrationAnimator(breatheAnimation);
827ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
828ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    return breatheAnimation;
829ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
830ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
831ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void startSwipeToAnswerSettleAnimation() {
832ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    endAnimation();
833ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
834ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    ObjectAnimator puckScale =
835ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofPropertyValuesHolder(
836ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckBackground,
837ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            PropertyValuesHolder.ofFloat(View.SCALE_X, 1),
838ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            PropertyValuesHolder.ofFloat(View.SCALE_Y, 1));
839ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckScale.setDuration(SETTLE_ANIMATION_DURATION_MILLIS);
840ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
841ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    ObjectAnimator iconRotation = ObjectAnimator.ofFloat(contactPuckIcon, View.ROTATION, 0);
842ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    iconRotation.setDuration(SETTLE_ANIMATION_DURATION_MILLIS);
843ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
844ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    ObjectAnimator swipeToAnswerTextFade =
845ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        createFadeAnimation(swipeToAnswerText, 1, SETTLE_ANIMATION_DURATION_MILLIS);
846ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
847ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    ObjectAnimator contactPuckContainerFade =
848ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        createFadeAnimation(contactPuckContainer, 1, SETTLE_ANIMATION_DURATION_MILLIS);
849ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
850ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    ObjectAnimator contactPuckBackgroundFade =
851ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        createFadeAnimation(contactPuckBackground, 1, SETTLE_ANIMATION_DURATION_MILLIS);
852ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
853ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    ObjectAnimator contactPuckIconFade =
854ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        createFadeAnimation(
855ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckIcon, shouldShowPhotoInPuck() ? 0 : 1, SETTLE_ANIMATION_DURATION_MILLIS);
856ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
857ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    ObjectAnimator contactPuckTranslation =
858ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofPropertyValuesHolder(
859ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckContainer,
860ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 0),
861ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0));
862ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckTranslation.setDuration(SETTLE_ANIMATION_DURATION_MILLIS);
863ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
864ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockSettleAnim = new AnimatorSet();
865ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockSettleAnim
866ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .play(puckScale)
867ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(iconRotation)
868ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(swipeToAnswerTextFade)
869ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(contactPuckContainerFade)
870ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(contactPuckBackgroundFade)
871ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(contactPuckIconFade)
872ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(contactPuckTranslation);
873ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
874ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockSettleAnim.addListener(
875ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        new AnimatorListenerAdapter() {
876ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          @Override
877ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          public void onAnimationCancel(Animator animation) {
878ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            afterSettleAnimationState = AnimationState.NONE;
879ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          }
880ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
881ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          @Override
882ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          public void onAnimationEnd(Animator animation) {
883ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            onSettleAnimationDone();
884ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          }
885ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        });
886ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
887ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockSettleAnim.start();
888ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
889ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
890ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @VisibleForTesting
891ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  void onSettleAnimationDone() {
892ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (afterSettleAnimationState != AnimationState.NONE) {
893ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      int nextState = afterSettleAnimationState;
894ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      afterSettleAnimationState = AnimationState.NONE;
895ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      lockSettleAnim = null;
896ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
897ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      setAnimationState(nextState);
898ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
899ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
900ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
901ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private ObjectAnimator createFadeAnimation(View target, float targetAlpha, long duration) {
902ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(target, View.ALPHA, targetAlpha);
903ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    objectAnimator.setDuration(duration);
904ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    return objectAnimator;
905ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
906ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
907ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void startSwipeToAnswerHintAnimation() {
908ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (rejectHintHide != null) {
909ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      rejectHintHide.cancel();
910ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
911ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
912ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    endAnimation();
913ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    resetTouchState();
914ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
915ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (ViewUtil.areAnimationsDisabled(getContext())) {
916ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      onHintAnimationDone(false);
917ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      return;
918ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
919ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
920ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockHintAnim = new AnimatorSet();
921ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    float jumpOffset = DpUtil.dpToPx(getContext(), HINT_JUMP_DP);
922ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    float dipOffset = DpUtil.dpToPx(getContext(), HINT_DIP_DP);
923ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    float scaleSize = HINT_SCALE_RATIO;
924ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    float textOffset = jumpOffset + (scaleSize - 1) * contactPuckBackground.getHeight();
925ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    int shortAnimTime =
926ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        getContext().getResources().getInteger(android.R.integer.config_shortAnimTime);
927ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    int mediumAnimTime =
928ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        getContext().getResources().getInteger(android.R.integer.config_mediumAnimTime);
929ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
930ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Puck squashes to anticipate jump
931ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    ObjectAnimator puckAnticipate =
932ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofPropertyValuesHolder(
933ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckContainer,
934ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            PropertyValuesHolder.ofFloat(View.SCALE_Y, .95f),
935ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            PropertyValuesHolder.ofFloat(View.SCALE_X, 1.05f));
936ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckAnticipate.setRepeatCount(1);
937ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckAnticipate.setRepeatMode(ValueAnimator.REVERSE);
938ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckAnticipate.setDuration(shortAnimTime / 2);
939ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckAnticipate.setInterpolator(new DecelerateInterpolator());
940ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckAnticipate.addListener(
941ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        new AnimatorListenerAdapter() {
942ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          @Override
943ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          public void onAnimationStart(Animator animation) {
944ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            super.onAnimationStart(animation);
945ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckContainer.setPivotY(contactPuckContainer.getHeight());
946ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          }
947ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
948ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          @Override
949ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          public void onAnimationEnd(Animator animation) {
950ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            super.onAnimationEnd(animation);
951ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckContainer.setPivotY(contactPuckContainer.getHeight() / 2);
952ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          }
953ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        });
954ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
955ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Ensure puck is at the right starting point for the jump
956ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    ObjectAnimator puckResetTranslation =
957ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofPropertyValuesHolder(
958ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckContainer,
959ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0),
960ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 0));
961ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckResetTranslation.setDuration(shortAnimTime / 2);
962ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckAnticipate.setInterpolator(new DecelerateInterpolator());
963ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
964ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator textUp = ObjectAnimator.ofFloat(swipeToAnswerText, View.TRANSLATION_Y, -textOffset);
965ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    textUp.setInterpolator(new LinearOutSlowInInterpolator());
966ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    textUp.setDuration(shortAnimTime);
967ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
968ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator puckUp = ObjectAnimator.ofFloat(contactPuckContainer, View.TRANSLATION_Y, -jumpOffset);
969ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckUp.setInterpolator(new LinearOutSlowInInterpolator());
970ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckUp.setDuration(shortAnimTime);
971ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
972ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator puckScaleUp =
973ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        createUniformScaleAnimators(
974ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckBackground, 1f, scaleSize, shortAnimTime, new LinearOutSlowInInterpolator());
975ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
976ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator rejectHintShow =
977ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofPropertyValuesHolder(
978ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            swipeToRejectText,
979ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            PropertyValuesHolder.ofFloat(View.ALPHA, 1f),
980ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0f));
981ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectHintShow.setDuration(shortAnimTime);
982ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
983ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator rejectHintDip =
984ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofFloat(swipeToRejectText, View.TRANSLATION_Y, dipOffset);
985ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectHintDip.setInterpolator(new LinearOutSlowInInterpolator());
986ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectHintDip.setDuration(shortAnimTime);
987ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
988ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator textDown = ObjectAnimator.ofFloat(swipeToAnswerText, View.TRANSLATION_Y, 0);
989ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    textDown.setInterpolator(new LinearOutSlowInInterpolator());
990ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    textDown.setDuration(mediumAnimTime);
991ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
992ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator puckDown = ObjectAnimator.ofFloat(contactPuckContainer, View.TRANSLATION_Y, 0);
993ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    BounceInterpolator bounce = new BounceInterpolator();
994ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckDown.setInterpolator(bounce);
995ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    puckDown.setDuration(mediumAnimTime);
996ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
997ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator puckScaleDown =
998ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        createUniformScaleAnimators(
999ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckBackground, scaleSize, 1f, shortAnimTime, new LinearOutSlowInInterpolator());
1000ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1001ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Animator rejectHintUp = ObjectAnimator.ofFloat(swipeToRejectText, View.TRANSLATION_Y, 0);
1002ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectHintUp.setInterpolator(new LinearOutSlowInInterpolator());
1003ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectHintUp.setDuration(mediumAnimTime);
1004ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1005ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockHintAnim.play(puckAnticipate).with(puckResetTranslation).before(puckUp);
1006ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockHintAnim
1007ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .play(textUp)
1008ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(puckUp)
1009ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(puckScaleUp)
1010ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(rejectHintDip)
1011ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        .with(rejectHintShow);
1012ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockHintAnim.play(textDown).with(puckDown).with(puckScaleDown).with(rejectHintUp).after(puckUp);
1013ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    lockHintAnim.start();
1014ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1015ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectHintHide = ObjectAnimator.ofFloat(swipeToRejectText, View.ALPHA, 0);
1016ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectHintHide.setStartDelay(HINT_REJECT_SHOW_DURATION_MILLIS);
1017ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectHintHide.addListener(
1018ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        new AnimatorListenerAdapter() {
1019ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1020ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          private boolean canceled;
1021ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1022ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          @Override
1023ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          public void onAnimationCancel(Animator animation) {
1024ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            super.onAnimationCancel(animation);
1025ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            canceled = true;
1026ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            rejectHintHide = null;
1027ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          }
1028ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1029ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          @Override
1030ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          public void onAnimationEnd(Animator animation) {
1031ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            super.onAnimationEnd(animation);
1032ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            onHintAnimationDone(canceled);
1033ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          }
1034ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        });
1035ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectHintHide.start();
1036ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
1037ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1038ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @VisibleForTesting
1039ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  void onHintAnimationDone(boolean canceled) {
1040ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (!canceled && animationState == AnimationState.HINT) {
1041ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      setAnimationState(AnimationState.BOUNCE);
1042ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
1043ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    rejectHintHide = null;
1044ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
1045ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1046ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void clearSwipeToAnswerUi() {
1047ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    LogUtil.i("FlingUpDownMethod.clearSwipeToAnswerUi", "Clear swipe animation.");
1048ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    endAnimation();
1049ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    swipeToAnswerText.setVisibility(View.GONE);
1050ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckContainer.setVisibility(View.GONE);
1051ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
1052ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1053ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void endAnimation() {
1054ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    LogUtil.i("FlingUpDownMethod.endAnimation", "End animations.");
1055ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (lockSettleAnim != null) {
1056ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      lockSettleAnim.cancel();
1057ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      lockSettleAnim = null;
1058ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
1059ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (lockBounceAnim != null) {
1060ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      lockBounceAnim.cancel();
1061ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      lockBounceAnim = null;
1062ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
1063ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (lockEntryAnim != null) {
1064ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      lockEntryAnim.cancel();
1065ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      lockEntryAnim = null;
1066ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
1067ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (lockHintAnim != null) {
1068ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      lockHintAnim.cancel();
1069ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      lockHintAnim = null;
1070ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
1071ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (rejectHintHide != null) {
1072ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      rejectHintHide.cancel();
1073ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      rejectHintHide = null;
1074ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
1075ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (vibrationAnimator != null) {
1076ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      vibrationAnimator.end();
1077ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      vibrationAnimator = null;
1078ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
1079ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    answerHint.onBounceEnd();
1080ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
1081ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1082ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  // Create an animator to scale on X/Y directions uniformly.
1083ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private Animator createUniformScaleAnimators(
1084ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      View target, float begin, float end, long duration, Interpolator interpolator) {
1085ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    ObjectAnimator animator =
1086ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofPropertyValuesHolder(
1087ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            target,
1088ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            PropertyValuesHolder.ofFloat(View.SCALE_X, begin, end),
1089ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            PropertyValuesHolder.ofFloat(View.SCALE_Y, begin, end));
1090ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    animator.setDuration(duration);
1091ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    animator.setInterpolator(interpolator);
1092ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    return animator;
1093ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
1094ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1095ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void addVibrationAnimator(AnimatorSet animatorSet) {
1096ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (vibrationAnimator != null) {
1097ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      vibrationAnimator.end();
1098ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
1099ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1100ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Note that we animate the value between 0 and 1, but internally VibrateInterpolator will
1101ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // translate it into actually X translation value.
1102ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    vibrationAnimator =
1103ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ObjectAnimator.ofFloat(
1104ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            contactPuckContainer, View.TRANSLATION_X, 0 /* begin value */, 1 /* end value */);
1105ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    vibrationAnimator.setDuration(VIBRATION_TIME_MILLIS);
1106ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    vibrationAnimator.setInterpolator(new VibrateInterpolator(getContext()));
1107ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1108ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    animatorSet.play(vibrationAnimator).after(0 /* delay */);
1109ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
1110ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1111ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void performAccept() {
1112ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    LogUtil.i("FlingUpDownMethod.performAccept", null);
1113ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    swipeToAnswerText.setVisibility(View.GONE);
1114ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckContainer.setVisibility(View.GONE);
1115ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1116ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Complete the animation loop.
1117ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    setAnimationState(AnimationState.COMPLETED);
1118ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    getParent().answerFromMethod();
1119ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
1120ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1121ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private void performReject() {
1122ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    LogUtil.i("FlingUpDownMethod.performReject", null);
1123ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    swipeToAnswerText.setVisibility(View.GONE);
1124ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    contactPuckContainer.setVisibility(View.GONE);
1125ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1126ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // Complete the animation loop.
1127ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    setAnimationState(AnimationState.COMPLETED);
1128ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    getParent().rejectFromMethod();
1129ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
1130ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1131ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  /** Custom interpolator class for puck vibration. */
1132ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private static class VibrateInterpolator implements Interpolator {
1133ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1134ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    private static final long RAMP_UP_BEGIN_MS = 583;
1135ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    private static final long RAMP_UP_DURATION_MS = 167;
1136ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    private static final long RAMP_UP_END_MS = RAMP_UP_BEGIN_MS + RAMP_UP_DURATION_MS;
1137ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    private static final long RAMP_DOWN_BEGIN_MS = 1_583;
1138ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    private static final long RAMP_DOWN_DURATION_MS = 250;
1139ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    private static final long RAMP_DOWN_END_MS = RAMP_DOWN_BEGIN_MS + RAMP_DOWN_DURATION_MS;
1140ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    private static final long RAMP_TOTAL_TIME_MS = RAMP_DOWN_END_MS;
1141ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    private final float ampMax;
1142ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    private final float freqMax = 80;
1143ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    private Interpolator sliderInterpolator = new FastOutSlowInInterpolator();
1144ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1145ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    VibrateInterpolator(Context context) {
1146ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      ampMax = DpUtil.dpToPx(context, 1 /* dp */);
1147ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
1148ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1149ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    @Override
1150ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    public float getInterpolation(float t) {
1151ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      float slider = 0;
1152ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      float time = t * RAMP_TOTAL_TIME_MS;
1153ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1154ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      // Calculate the slider value based on RAMP_UP and RAMP_DOWN times. Between RAMP_UP and
1155ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      // RAMP_DOWN, the slider remains the maximum value of 1.
1156ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      if (time > RAMP_UP_BEGIN_MS && time < RAMP_UP_END_MS) {
1157ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        // Ramp up.
1158ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        slider =
1159ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            sliderInterpolator.getInterpolation(
1160ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian                (time - RAMP_UP_BEGIN_MS) / (float) RAMP_UP_DURATION_MS);
1161ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      } else if ((time >= RAMP_UP_END_MS) && time <= RAMP_DOWN_BEGIN_MS) {
1162ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        // Vibrate at maximum
1163ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        slider = 1;
1164ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      } else if (time > RAMP_DOWN_BEGIN_MS && time < RAMP_DOWN_END_MS) {
1165ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        // Ramp down.
1166ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        slider =
1167ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            1
1168ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian                - sliderInterpolator.getInterpolation(
1169ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian                    (time - RAMP_DOWN_BEGIN_MS) / (float) RAMP_DOWN_DURATION_MS);
1170ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      }
1171ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1172ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      float ampNormalized = ampMax * slider;
1173ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      float freqNormalized = freqMax * slider;
1174ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
1175ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      return (float) (ampNormalized * Math.sin(time * freqNormalized));
1176ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
1177ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
1178ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian}
1179