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 
17 package com.android.incallui.answer.impl.hint;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.content.Context;
24 import android.graphics.drawable.Drawable;
25 import android.support.annotation.DimenRes;
26 import android.support.annotation.NonNull;
27 import android.support.v4.view.animation.FastOutSlowInInterpolator;
28 import android.util.TypedValue;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.animation.Interpolator;
33 import android.view.animation.LinearInterpolator;
34 import android.widget.ImageView;
35 import android.widget.TextView;
36 import 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  */
42 public 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 
PawAnswerHint( @onNull Context context, @NonNull Drawable payload, long puckUpDurationMillis, long puckUpDelayMillis)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
onCreateView( LayoutInflater inflater, ViewGroup container, View puck, TextView hintText)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
onBounceStart()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 
createFadeIn()137   private Animator createFadeIn() {
138     AnimatorSet set = new AnimatorSet();
139     set.play(createFadeInScaleAndAlpha(payloadView));
140     return set;
141   }
142 
createFadeInScaleAndAlpha(View target)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 
createFadeOut()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 
createFadeOutScaleAndAlpha(View target, long scaleDelay)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
onBounceEnd()194   public void onBounceEnd() {
195     if (answerGestureHintAnim != null) {
196       answerGestureHintAnim.end();
197       answerGestureHintAnim = null;
198       answerHintContainer.setVisibility(View.GONE);
199     }
200   }
201 
202   @Override
onAnswered()203   public void onAnswered() {
204     // Do nothing
205   }
206 
getDimension(@imenRes int id)207   private float getDimension(@DimenRes int id) {
208     return context.getResources().getDimension(id);
209   }
210 
createUniformScaleAnimator( View target, float scaleBegin, float scaleEnd, long duration, long delay, Interpolator interpolator)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 
createAlphaAnimator( View target, float begin, float end, long duration, long delay, Interpolator interpolator)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