1 /*
2  * Copyright (C) 2015 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.systemui.volume;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.TimeInterpolator;
22 import android.animation.ValueAnimator;
23 import android.animation.ValueAnimator.AnimatorUpdateListener;
24 import android.app.Dialog;
25 import android.content.DialogInterface;
26 import android.content.DialogInterface.OnDismissListener;
27 import android.content.DialogInterface.OnShowListener;
28 import android.os.Handler;
29 import android.util.Log;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.animation.PathInterpolator;
33 
34 public class VolumeDialogMotion {
35     private static final String TAG = Util.logTag(VolumeDialogMotion.class);
36 
37     private static final float ANIMATION_SCALE = 1.0f;
38     private static final int PRE_DISMISS_DELAY = 50;
39 
40     private final Dialog mDialog;
41     private final View mDialogView;
42     private final ViewGroup mContents;  // volume rows + zen footer
43     private final View mChevron;
44     private final Handler mHandler = new Handler();
45     private final Callback mCallback;
46 
47     private boolean mAnimating;  // show or dismiss animation is running
48     private boolean mShowing;  // show animation is running
49     private boolean mDismissing;  // dismiss animation is running
50     private ValueAnimator mChevronPositionAnimator;
51     private ValueAnimator mContentsPositionAnimator;
52 
VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents, View chevron, Callback callback)53     public VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents, View chevron,
54             Callback callback) {
55         mDialog = dialog;
56         mDialogView = dialogView;
57         mContents = contents;
58         mChevron = chevron;
59         mCallback = callback;
60         mDialog.setOnDismissListener(new OnDismissListener() {
61             @Override
62             public void onDismiss(DialogInterface dialog) {
63                 if (D.BUG) Log.d(TAG, "mDialog.onDismiss");
64             }
65         });
66         mDialog.setOnShowListener(new OnShowListener() {
67             @Override
68             public void onShow(DialogInterface dialog) {
69                 if (D.BUG) Log.d(TAG, "mDialog.onShow");
70                 final int h = mDialogView.getHeight();
71                 mDialogView.setTranslationY(-h);
72                 startShowAnimation();
73             }
74         });
75     }
76 
isAnimating()77     public boolean isAnimating() {
78         return mAnimating;
79     }
80 
setShowing(boolean showing)81     private void setShowing(boolean showing) {
82         if (showing == mShowing) return;
83         mShowing = showing;
84         if (D.BUG) Log.d(TAG, "mShowing = " + mShowing);
85         updateAnimating();
86     }
87 
setDismissing(boolean dismissing)88     private void setDismissing(boolean dismissing) {
89         if (dismissing == mDismissing) return;
90         mDismissing = dismissing;
91         if (D.BUG) Log.d(TAG, "mDismissing = " + mDismissing);
92         updateAnimating();
93     }
94 
updateAnimating()95     private void updateAnimating() {
96         final boolean animating = mShowing || mDismissing;
97         if (animating == mAnimating) return;
98         mAnimating = animating;
99         if (D.BUG) Log.d(TAG, "mAnimating = " + mAnimating);
100         if (mCallback != null) {
101             mCallback.onAnimatingChanged(mAnimating);
102         }
103     }
104 
startShow()105     public void startShow() {
106         if (D.BUG) Log.d(TAG, "startShow");
107         if (mShowing) return;
108         setShowing(true);
109         if (mDismissing) {
110             mDialogView.animate().cancel();
111             setDismissing(false);
112             startShowAnimation();
113             return;
114         }
115         if (D.BUG) Log.d(TAG, "mDialog.show()");
116         mDialog.show();
117     }
118 
chevronDistance()119     private int chevronDistance() {
120         return mChevron.getHeight() / 6;
121     }
122 
chevronPosY()123     private int chevronPosY() {
124         final Object tag = mChevron == null ? null : mChevron.getTag();
125         return tag == null ? 0 : (Integer) tag;
126     }
127 
startShowAnimation()128     private void startShowAnimation() {
129         if (D.BUG) Log.d(TAG, "startShowAnimation");
130         mDialogView.animate()
131                 .translationY(0)
132                 .setDuration(scaledDuration(300))
133                 .setInterpolator(new LogDecelerateInterpolator())
134                 .setListener(null)
135                 .setUpdateListener(new AnimatorUpdateListener() {
136                     @Override
137                     public void onAnimationUpdate(ValueAnimator animation) {
138                         if (mChevronPositionAnimator == null) return;
139                         // reposition chevron
140                         final float v = (Float) mChevronPositionAnimator.getAnimatedValue();
141                         final int posY = chevronPosY();
142                         mChevron.setTranslationY(posY + v + -mDialogView.getTranslationY());
143                     }})
144                 .start();
145 
146         mContentsPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0)
147                 .setDuration(scaledDuration(400));
148         mContentsPositionAnimator.addListener(new AnimatorListenerAdapter() {
149             private boolean mCancelled;
150 
151             @Override
152             public void onAnimationEnd(Animator animation) {
153                 if (mCancelled) return;
154                 if (D.BUG) Log.d(TAG, "show.onAnimationEnd");
155                 setShowing(false);
156             }
157             @Override
158             public void onAnimationCancel(Animator animation) {
159                 if (D.BUG) Log.d(TAG, "show.onAnimationCancel");
160                 mCancelled = true;
161             }
162         });
163         mContentsPositionAnimator.addUpdateListener(new AnimatorUpdateListener() {
164             @Override
165             public void onAnimationUpdate(ValueAnimator animation) {
166                 float v = (Float) animation.getAnimatedValue();
167                 mContents.setTranslationY(v + -mDialogView.getTranslationY());
168             }
169         });
170         mContentsPositionAnimator.setInterpolator(new LogDecelerateInterpolator());
171         mContentsPositionAnimator.start();
172 
173         mContents.setAlpha(0);
174         mContents.animate()
175                 .alpha(1)
176                 .setDuration(scaledDuration(150))
177                 .setInterpolator(new PathInterpolator(0f, 0f, .2f, 1f))
178                 .start();
179 
180         mChevronPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0)
181                 .setDuration(scaledDuration(250));
182         mChevronPositionAnimator.setInterpolator(new PathInterpolator(.4f, 0f, .2f, 1f));
183         mChevronPositionAnimator.start();
184 
185         mChevron.setAlpha(0);
186         mChevron.animate()
187                 .alpha(1)
188                 .setStartDelay(scaledDuration(50))
189                 .setDuration(scaledDuration(150))
190                 .setInterpolator(new PathInterpolator(.4f, 0f, 1f, 1f))
191                 .start();
192     }
193 
startDismiss(final Runnable onComplete)194     public void startDismiss(final Runnable onComplete) {
195         if (D.BUG) Log.d(TAG, "startDismiss");
196         if (mDismissing) return;
197         setDismissing(true);
198         if (mShowing) {
199             mDialogView.animate().cancel();
200             if (mContentsPositionAnimator != null) {
201                 mContentsPositionAnimator.cancel();
202             }
203             mContents.animate().cancel();
204             if (mChevronPositionAnimator != null) {
205                 mChevronPositionAnimator.cancel();
206             }
207             mChevron.animate().cancel();
208             setShowing(false);
209         }
210         mDialogView.animate()
211                 .translationY(-mDialogView.getHeight())
212                 .setDuration(scaledDuration(250))
213                 .setInterpolator(new LogAccelerateInterpolator())
214                 .setUpdateListener(new AnimatorUpdateListener() {
215                     @Override
216                     public void onAnimationUpdate(ValueAnimator animation) {
217                         mContents.setTranslationY(-mDialogView.getTranslationY());
218                         final int posY = chevronPosY();
219                         mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
220                     }
221                 })
222                 .setListener(new AnimatorListenerAdapter() {
223                     private boolean mCancelled;
224                     @Override
225                     public void onAnimationEnd(Animator animation) {
226                         if (mCancelled) return;
227                         if (D.BUG) Log.d(TAG, "dismiss.onAnimationEnd");
228                         mHandler.postDelayed(new Runnable() {
229                             @Override
230                             public void run() {
231                                 if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
232                                 mDialog.dismiss();
233                                 onComplete.run();
234                                 setDismissing(false);
235                             }
236                         }, PRE_DISMISS_DELAY);
237 
238                     }
239                     @Override
240                     public void onAnimationCancel(Animator animation) {
241                         if (D.BUG) Log.d(TAG, "dismiss.onAnimationCancel");
242                         mCancelled = true;
243                     }
244                 }).start();
245     }
246 
scaledDuration(int base)247     private static int scaledDuration(int base) {
248         return (int) (base * ANIMATION_SCALE);
249     }
250 
251     private static final class LogDecelerateInterpolator implements TimeInterpolator {
252         private final float mBase;
253         private final float mDrift;
254         private final float mTimeScale;
255         private final float mOutputScale;
256 
LogDecelerateInterpolator()257         private LogDecelerateInterpolator() {
258             this(400f, 1.4f, 0);
259         }
260 
LogDecelerateInterpolator(float base, float timeScale, float drift)261         private LogDecelerateInterpolator(float base, float timeScale, float drift) {
262             mBase = base;
263             mDrift = drift;
264             mTimeScale = 1f / timeScale;
265 
266             mOutputScale = 1f / computeLog(1f);
267         }
268 
computeLog(float t)269         private float computeLog(float t) {
270             return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
271         }
272 
273         @Override
getInterpolation(float t)274         public float getInterpolation(float t) {
275             return computeLog(t) * mOutputScale;
276         }
277     }
278 
279     private static final class LogAccelerateInterpolator implements TimeInterpolator {
280         private final int mBase;
281         private final int mDrift;
282         private final float mLogScale;
283 
LogAccelerateInterpolator()284         private LogAccelerateInterpolator() {
285             this(100, 0);
286         }
287 
LogAccelerateInterpolator(int base, int drift)288         private LogAccelerateInterpolator(int base, int drift) {
289             mBase = base;
290             mDrift = drift;
291             mLogScale = 1f / computeLog(1, mBase, mDrift);
292         }
293 
computeLog(float t, int base, int drift)294         private static float computeLog(float t, int base, int drift) {
295             return (float) -Math.pow(base, -t) + 1 + (drift * t);
296         }
297 
298         @Override
getInterpolation(float t)299         public float getInterpolation(float t) {
300             return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale;
301         }
302     }
303 
304     public interface Callback {
onAnimatingChanged(boolean animating)305         void onAnimatingChanged(boolean animating);
306     }
307 }
308