1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.incallui.answer.impl.hint;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.AnimatorSet;
22import android.animation.ObjectAnimator;
23import android.content.Context;
24import android.graphics.drawable.Drawable;
25import android.support.annotation.DimenRes;
26import android.support.annotation.NonNull;
27import android.support.v4.view.animation.FastOutSlowInInterpolator;
28import android.util.TypedValue;
29import android.view.LayoutInflater;
30import android.view.View;
31import android.view.ViewGroup;
32import android.view.animation.Interpolator;
33import android.view.animation.LinearInterpolator;
34import android.widget.ImageView;
35import android.widget.TextView;
36import com.android.dialer.common.Assert;
37
38/**
39 * An Answer hint that animates a {@link Drawable} payload with animation similar to {@link
40 * DotAnswerHint}.
41 */
42public final class PawAnswerHint implements AnswerHint {
43
44  private static final long FADE_IN_DELAY_SCALE_MILLIS = 380;
45  private static final long FADE_IN_DURATION_SCALE_MILLIS = 200;
46  private static final long FADE_IN_DELAY_ALPHA_MILLIS = 340;
47  private static final long FADE_IN_DURATION_ALPHA_MILLIS = 50;
48
49  private static final long SWIPE_UP_DURATION_ALPHA_MILLIS = 500;
50
51  private static final long FADE_OUT_DELAY_SCALE_SMALL_MILLIS = 90;
52  private static final long FADE_OUT_DURATION_SCALE_MILLIS = 100;
53  private static final long FADE_OUT_DELAY_ALPHA_MILLIS = 130;
54  private static final long FADE_OUT_DURATION_ALPHA_MILLIS = 170;
55
56  private static final float IMAGE_SCALE = 1.5f;
57  private static final float FADE_SCALE = 2.0f;
58
59  private final Context context;
60  private final Drawable payload;
61  private final long puckUpDurationMillis;
62  private final long puckUpDelayMillis;
63
64  private View puck;
65  private View payloadView;
66  private View answerHintContainer;
67  private AnimatorSet answerGestureHintAnim;
68
69  public PawAnswerHint(
70      @NonNull Context context,
71      @NonNull Drawable payload,
72      long puckUpDurationMillis,
73      long puckUpDelayMillis) {
74    this.context = Assert.isNotNull(context);
75    this.payload = Assert.isNotNull(payload);
76    this.puckUpDurationMillis = puckUpDurationMillis;
77    this.puckUpDelayMillis = puckUpDelayMillis;
78  }
79
80  @Override
81  public void onCreateView(
82      LayoutInflater inflater, ViewGroup container, View puck, TextView hintText) {
83    this.puck = puck;
84    View view = inflater.inflate(R.layout.paw_hint, container, true);
85    answerHintContainer = view.findViewById(R.id.answer_hint_container);
86    payloadView = view.findViewById(R.id.paw_image);
87    hintText.setTextSize(
88        TypedValue.COMPLEX_UNIT_PX, context.getResources().getDimension(R.dimen.hint_text_size));
89    ((ImageView) payloadView).setImageDrawable(payload);
90  }
91
92  @Override
93  public void onBounceStart() {
94    if (answerGestureHintAnim == null) {
95      answerGestureHintAnim = new AnimatorSet();
96      int[] puckLocation = new int[2];
97      puck.getLocationInWindow(puckLocation);
98      answerHintContainer.setY(puckLocation[1] + getDimension(R.dimen.hint_initial_offset));
99
100      Animator fadeIn = createFadeIn();
101
102      Animator swipeUp =
103          ObjectAnimator.ofFloat(
104              answerHintContainer,
105              View.TRANSLATION_Y,
106              puckLocation[1] - getDimension(R.dimen.hint_offset));
107      swipeUp.setInterpolator(new FastOutSlowInInterpolator());
108      swipeUp.setDuration(SWIPE_UP_DURATION_ALPHA_MILLIS);
109
110      Animator fadeOut = createFadeOut();
111
112      answerGestureHintAnim.play(fadeIn).after(puckUpDelayMillis);
113      answerGestureHintAnim.play(swipeUp).after(fadeIn);
114      // The fade out should start fading the alpha just as the puck is dropping. Scaling will start
115      // a bit earlier.
116      answerGestureHintAnim
117          .play(fadeOut)
118          .after(puckUpDelayMillis + puckUpDurationMillis - FADE_OUT_DELAY_ALPHA_MILLIS);
119
120      fadeIn.addListener(
121          new AnimatorListenerAdapter() {
122            @Override
123            public void onAnimationStart(Animator animation) {
124              super.onAnimationStart(animation);
125              payloadView.setAlpha(0);
126              payloadView.setScaleX(1);
127              payloadView.setScaleY(1);
128              answerHintContainer.setY(puckLocation[1] + getDimension(R.dimen.hint_initial_offset));
129              answerHintContainer.setVisibility(View.VISIBLE);
130            }
131          });
132    }
133
134    answerGestureHintAnim.start();
135  }
136
137  private Animator createFadeIn() {
138    AnimatorSet set = new AnimatorSet();
139    set.play(createFadeInScaleAndAlpha(payloadView));
140    return set;
141  }
142
143  private static Animator createFadeInScaleAndAlpha(View target) {
144    Animator scale =
145        createUniformScaleAnimator(
146            target,
147            FADE_SCALE,
148            IMAGE_SCALE,
149            FADE_IN_DURATION_SCALE_MILLIS,
150            FADE_IN_DELAY_SCALE_MILLIS,
151            new LinearInterpolator());
152    Animator alpha =
153        createAlphaAnimator(
154            target,
155            0f,
156            1.0f,
157            FADE_IN_DURATION_ALPHA_MILLIS,
158            FADE_IN_DELAY_ALPHA_MILLIS,
159            new LinearInterpolator());
160    AnimatorSet set = new AnimatorSet();
161    set.play(scale).with(alpha);
162    return set;
163  }
164
165  private Animator createFadeOut() {
166    AnimatorSet set = new AnimatorSet();
167    set.play(createFadeOutScaleAndAlpha(payloadView, FADE_OUT_DELAY_SCALE_SMALL_MILLIS));
168    return set;
169  }
170
171  private static Animator createFadeOutScaleAndAlpha(View target, long scaleDelay) {
172    Animator scale =
173        createUniformScaleAnimator(
174            target,
175            IMAGE_SCALE,
176            FADE_SCALE,
177            FADE_OUT_DURATION_SCALE_MILLIS,
178            scaleDelay,
179            new LinearInterpolator());
180    Animator alpha =
181        createAlphaAnimator(
182            target,
183            1.0f,
184            0.0f,
185            FADE_OUT_DURATION_ALPHA_MILLIS,
186            FADE_OUT_DELAY_ALPHA_MILLIS,
187            new LinearInterpolator());
188    AnimatorSet set = new AnimatorSet();
189    set.play(scale).with(alpha);
190    return set;
191  }
192
193  @Override
194  public void onBounceEnd() {
195    if (answerGestureHintAnim != null) {
196      answerGestureHintAnim.end();
197      answerGestureHintAnim = null;
198      answerHintContainer.setVisibility(View.GONE);
199    }
200  }
201
202  @Override
203  public void onAnswered() {
204    // Do nothing
205  }
206
207  private float getDimension(@DimenRes int id) {
208    return context.getResources().getDimension(id);
209  }
210
211  private static Animator createUniformScaleAnimator(
212      View target,
213      float scaleBegin,
214      float scaleEnd,
215      long duration,
216      long delay,
217      Interpolator interpolator) {
218    Animator scaleX = ObjectAnimator.ofFloat(target, View.SCALE_X, scaleBegin, scaleEnd);
219    Animator scaleY = ObjectAnimator.ofFloat(target, View.SCALE_Y, scaleBegin, scaleEnd);
220    scaleX.setDuration(duration);
221    scaleY.setDuration(duration);
222    scaleX.setInterpolator(interpolator);
223    scaleY.setInterpolator(interpolator);
224    AnimatorSet set = new AnimatorSet();
225    set.play(scaleX).with(scaleY).after(delay);
226    return set;
227  }
228
229  private static Animator createAlphaAnimator(
230      View target, float begin, float end, long duration, long delay, Interpolator interpolator) {
231    Animator alpha = ObjectAnimator.ofFloat(target, View.ALPHA, begin, end);
232    alpha.setDuration(duration);
233    alpha.setInterpolator(interpolator);
234    alpha.setStartDelay(delay);
235    return alpha;
236  }
237}
238