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