1 /*
2  * Copyright (C) 2014 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.qs;
18 
19 import android.animation.Animator;
20 import android.animation.Animator.AnimatorListener;
21 import android.animation.AnimatorListenerAdapter;
22 import android.graphics.drawable.TransitionDrawable;
23 import android.view.View;
24 import android.view.ViewAnimationUtils;
25 
26 import androidx.annotation.Nullable;
27 
28 /** Helper for quick settings detail panel clip animations. Currently used by the customizer **/
29 public class QSDetailClipper {
30 
31     private final View mDetail;
32     private final TransitionDrawable mBackground;
33 
34     @Nullable
35     private Animator mAnimator;
36 
QSDetailClipper(View detail)37     public QSDetailClipper(View detail) {
38         mDetail = detail;
39         mBackground = (TransitionDrawable) detail.getBackground();
40     }
41 
42     /**
43      * @param x x position where animation should originate
44      * @param y y position where animation should originate
45      * @param in whether animating in or out
46      * @param listener Animation listener. Called whether or not {@code animate} is true.
47      * @return the duration of the circular animator
48      */
animateCircularClip(int x, int y, boolean in, AnimatorListener listener)49     public long animateCircularClip(int x, int y, boolean in, AnimatorListener listener) {
50         return updateCircularClip(true /* animate */, x, y, in, listener);
51     }
52 
53     /**
54      * @param animate whether or not animation has a duration of 0. Either way, {@code listener}
55      *               will be called.
56      * @param x x position where animation should originate
57      * @param y y position where animation should originate
58      * @param in whether animating in or out
59      * @param listener Animation listener. Called whether or not {@code animate} is true.
60      * @return the duration of the circular animator
61      */
updateCircularClip(boolean animate, int x, int y, boolean in, AnimatorListener listener)62     public long updateCircularClip(boolean animate, int x, int y, boolean in,
63             AnimatorListener listener) {
64         if (mAnimator != null) {
65             mAnimator.cancel();
66         }
67         final int w = mDetail.getWidth() - x;
68         final int h = mDetail.getHeight() - y;
69         int innerR = 0;
70         if (x < 0 || w < 0 || y < 0 || h < 0) {
71             innerR = Math.abs(x);
72             innerR = Math.min(innerR, Math.abs(y));
73             innerR = Math.min(innerR, Math.abs(w));
74             innerR = Math.min(innerR, Math.abs(h));
75         }
76         int r = (int) Math.ceil(Math.sqrt(x * x + y * y));
77         r = (int) Math.max(r, Math.ceil(Math.sqrt(w * w + y * y)));
78         r = (int) Math.max(r, Math.ceil(Math.sqrt(w * w + h * h)));
79         r = (int) Math.max(r, Math.ceil(Math.sqrt(x * x + h * h)));
80         if (in) {
81             mAnimator = ViewAnimationUtils.createCircularReveal(mDetail, x, y, innerR, r);
82         } else {
83             mAnimator = ViewAnimationUtils.createCircularReveal(mDetail, x, y, r, innerR);
84         }
85         mAnimator.setDuration(animate ? (long) (mAnimator.getDuration() * 1.5) : 0);
86         if (listener != null) {
87             mAnimator.addListener(listener);
88         }
89         if (in) {
90             mBackground.startTransition(animate ? (int) (mAnimator.getDuration() * 0.6) : 0);
91             mAnimator.addListener(mVisibleOnStart);
92         } else {
93             mDetail.postDelayed(mReverseBackground,
94                     animate ? (long) (mAnimator.getDuration() * 0.65) : 0);
95             mAnimator.addListener(mGoneOnEnd);
96         }
97         mAnimator.start();
98         return mAnimator.getDuration();
99     }
100 
101     private final Runnable mReverseBackground = new Runnable() {
102         @Override
103         public void run() {
104             if (mAnimator != null) {
105                 mBackground.reverseTransition((int)(mAnimator.getDuration() * 0.35));
106             }
107         }
108     };
109 
110     private final AnimatorListenerAdapter mVisibleOnStart = new AnimatorListenerAdapter() {
111         @Override
112         public void onAnimationStart(Animator animation) {
113             mDetail.setVisibility(View.VISIBLE);
114         }
115 
116         public void onAnimationEnd(Animator animation) {
117             mAnimator = null;
118         }
119     };
120 
121     private final AnimatorListenerAdapter mGoneOnEnd = new AnimatorListenerAdapter() {
122         @Override
123         public void onAnimationEnd(Animator animation) {
124             mDetail.setVisibility(View.GONE);
125             mBackground.resetTransition();
126             mAnimator = null;
127         };
128     };
129 
showBackground()130     public void showBackground() {
131         mBackground.showSecondLayer();
132     }
133 
134     /**
135      * Cancels the animator if it's running.
136      */
cancelAnimator()137     public void cancelAnimator() {
138         if (mAnimator != null) {
139             mAnimator.cancel();
140         }
141     }
142 }
143