1 /*
2  * Copyright (C) 2019 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.assist;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorSet;
21 import android.animation.ObjectAnimator;
22 import android.os.Handler;
23 import android.util.Log;
24 import android.util.MathUtils;
25 import android.view.View;
26 import android.view.animation.AccelerateInterpolator;
27 import android.view.animation.Interpolator;
28 import android.view.animation.PathInterpolator;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.systemui.CornerHandleView;
32 import com.android.systemui.R;
33 import com.android.systemui.statusbar.phone.NavigationBarTransitions;
34 
35 /**
36  * A class for managing Assistant handle show, hide and animation.
37  */
38 public class AssistHandleViewController implements NavigationBarTransitions.DarkIntensityListener {
39 
40     private static final boolean DEBUG = false;
41     private static final String TAG = "AssistHandleViewController";
42 
43     private Handler mHandler;
44     private CornerHandleView mAssistHintLeft;
45     private CornerHandleView mAssistHintRight;
46     private int mBottomOffset;
47 
48     @VisibleForTesting
49     boolean mAssistHintVisible;
50     @VisibleForTesting
51     boolean mAssistHintBlocked = false;
52 
AssistHandleViewController(Handler handler, View navBar)53     public AssistHandleViewController(Handler handler, View navBar) {
54         mHandler = handler;
55         mAssistHintLeft = navBar.findViewById(R.id.assist_hint_left);
56         mAssistHintRight = navBar.findViewById(R.id.assist_hint_right);
57     }
58 
59     @Override
onDarkIntensity(float darkIntensity)60     public void onDarkIntensity(float darkIntensity) {
61         mAssistHintLeft.updateDarkness(darkIntensity);
62         mAssistHintRight.updateDarkness(darkIntensity);
63     }
64 
65     /**
66      * Set the bottom offset.
67      *
68      * @param bottomOffset the bottom offset to translate.
69      */
setBottomOffset(int bottomOffset)70     public void setBottomOffset(int bottomOffset) {
71         if (mBottomOffset != bottomOffset) {
72             mBottomOffset = bottomOffset;
73             if (mAssistHintVisible) {
74                 // If assist handles are visible, hide them without animation and then make them
75                 // show once again (with corrected bottom offset).
76                 hideAssistHandles();
77                 setAssistHintVisible(true);
78             }
79         }
80     }
81 
82     /**
83      * Controls the visibility of the assist gesture handles.
84      *
85      * @param visible whether the handles should be shown
86      */
setAssistHintVisible(boolean visible)87     public void setAssistHintVisible(boolean visible) {
88         if (!mHandler.getLooper().isCurrentThread()) {
89             mHandler.post(() -> setAssistHintVisible(visible));
90             return;
91         }
92 
93         if (mAssistHintBlocked && visible) {
94             if (DEBUG) {
95                 Log.v(TAG, "Assist hint blocked, cannot make it visible");
96             }
97             return;
98         }
99 
100         if (mAssistHintVisible != visible) {
101             mAssistHintVisible = visible;
102             fade(mAssistHintLeft, mAssistHintVisible, /* isLeft = */ true);
103             fade(mAssistHintRight, mAssistHintVisible, /* isLeft = */ false);
104         }
105     }
106 
107     /**
108      * Prevents the assist hint from becoming visible even if `mAssistHintVisible` is true.
109      */
setAssistHintBlocked(boolean blocked)110     public void setAssistHintBlocked(boolean blocked) {
111         if (!mHandler.getLooper().isCurrentThread()) {
112             mHandler.post(() -> setAssistHintBlocked(blocked));
113             return;
114         }
115 
116         mAssistHintBlocked = blocked;
117         if (mAssistHintVisible && mAssistHintBlocked) {
118             hideAssistHandles();
119         }
120     }
121 
hideAssistHandles()122     private void hideAssistHandles() {
123         mAssistHintLeft.setVisibility(View.GONE);
124         mAssistHintRight.setVisibility(View.GONE);
125         mAssistHintVisible = false;
126     }
127 
128     /**
129      * Returns an animator that animates the given view from start to end over durationMs. Start and
130      * end represent total animation progress: 0 is the start, 1 is the end, 1.1 would be an
131      * overshoot.
132      */
getHandleAnimator(View view, float start, float end, boolean isLeft, long durationMs, Interpolator interpolator)133     Animator getHandleAnimator(View view, float start, float end, boolean isLeft, long durationMs,
134             Interpolator interpolator) {
135         // Note that lerp does allow overshoot, in cases where start and end are outside of [0,1].
136         float scaleStart = MathUtils.lerp(2f, 1f, start);
137         float scaleEnd = MathUtils.lerp(2f, 1f, end);
138         Animator scaleX = ObjectAnimator.ofFloat(view, View.SCALE_X, scaleStart, scaleEnd);
139         Animator scaleY = ObjectAnimator.ofFloat(view, View.SCALE_Y, scaleStart, scaleEnd);
140         float translationStart = MathUtils.lerp(0.2f, 0f, start);
141         float translationEnd = MathUtils.lerp(0.2f, 0f, end);
142         int xDirection = isLeft ? -1 : 1;
143         Animator translateX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
144                 xDirection * translationStart * view.getWidth(),
145                 xDirection * translationEnd * view.getWidth());
146         Animator translateY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y,
147                 translationStart * view.getHeight() + mBottomOffset,
148                 translationEnd * view.getHeight() + mBottomOffset);
149 
150         AnimatorSet set = new AnimatorSet();
151         set.play(scaleX).with(scaleY);
152         set.play(scaleX).with(translateX);
153         set.play(scaleX).with(translateY);
154         set.setDuration(durationMs);
155         set.setInterpolator(interpolator);
156         return set;
157     }
158 
fade(View view, boolean fadeIn, boolean isLeft)159     private void fade(View view, boolean fadeIn, boolean isLeft) {
160         if (fadeIn) {
161             view.animate().cancel();
162             view.setAlpha(1f);
163             view.setVisibility(View.VISIBLE);
164 
165             // A piecewise spring-like interpolation.
166             // End value in one animator call must match the start value in the next, otherwise
167             // there will be a discontinuity.
168             AnimatorSet anim = new AnimatorSet();
169             Animator first = getHandleAnimator(view, 0, 1.1f, isLeft, 750,
170                     new PathInterpolator(0, 0.45f, .67f, 1f));
171             Interpolator secondInterpolator = new PathInterpolator(0.33f, 0, 0.67f, 1f);
172             Animator second = getHandleAnimator(view, 1.1f, 0.97f, isLeft, 400,
173                     secondInterpolator);
174             Animator third = getHandleAnimator(view, 0.97f, 1.02f, isLeft, 400,
175                     secondInterpolator);
176             Animator fourth = getHandleAnimator(view, 1.02f, 1f, isLeft, 400,
177                     secondInterpolator);
178             anim.play(first).before(second);
179             anim.play(second).before(third);
180             anim.play(third).before(fourth);
181             anim.start();
182         } else {
183             view.animate().cancel();
184             view.animate()
185                 .setInterpolator(new AccelerateInterpolator(1.5f))
186                 .setDuration(250)
187                 .alpha(0f);
188         }
189 
190     }
191 }
192