blob: 5010f319f8b430d184f34330d45ef619bad02781 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.assist;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.os.Handler;
import android.util.Log;
import android.util.MathUtils;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.CornerHandleView;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.NavigationBarTransitions;
/**
* A class for managing Assistant handle show, hide and animation.
*/
public class AssistHandleViewController implements NavigationBarTransitions.DarkIntensityListener {
private static final boolean DEBUG = false;
private static final String TAG = "AssistHandleViewController";
private Handler mHandler;
private CornerHandleView mAssistHintLeft;
private CornerHandleView mAssistHintRight;
private int mBottomOffset;
@VisibleForTesting
boolean mAssistHintVisible;
@VisibleForTesting
boolean mAssistHintBlocked = false;
public AssistHandleViewController(Handler handler, View navBar) {
mHandler = handler;
mAssistHintLeft = navBar.findViewById(R.id.assist_hint_left);
mAssistHintRight = navBar.findViewById(R.id.assist_hint_right);
}
@Override
public void onDarkIntensity(float darkIntensity) {
mAssistHintLeft.updateDarkness(darkIntensity);
mAssistHintRight.updateDarkness(darkIntensity);
}
/**
* Set the bottom offset.
*
* @param bottomOffset the bottom offset to translate.
*/
public void setBottomOffset(int bottomOffset) {
if (mBottomOffset != bottomOffset) {
mBottomOffset = bottomOffset;
if (mAssistHintVisible) {
// If assist handles are visible, hide them without animation and then make them
// show once again (with corrected bottom offset).
hideAssistHandles();
setAssistHintVisible(true);
}
}
}
/**
* Controls the visibility of the assist gesture handles.
*
* @param visible whether the handles should be shown
*/
public void setAssistHintVisible(boolean visible) {
if (!mHandler.getLooper().isCurrentThread()) {
mHandler.post(() -> setAssistHintVisible(visible));
return;
}
if (mAssistHintBlocked && visible) {
if (DEBUG) {
Log.v(TAG, "Assist hint blocked, cannot make it visible");
}
return;
}
if (mAssistHintVisible != visible) {
mAssistHintVisible = visible;
fade(mAssistHintLeft, mAssistHintVisible, /* isLeft = */ true);
fade(mAssistHintRight, mAssistHintVisible, /* isLeft = */ false);
}
}
/**
* Prevents the assist hint from becoming visible even if `mAssistHintVisible` is true.
*/
public void setAssistHintBlocked(boolean blocked) {
if (!mHandler.getLooper().isCurrentThread()) {
mHandler.post(() -> setAssistHintBlocked(blocked));
return;
}
mAssistHintBlocked = blocked;
if (mAssistHintVisible && mAssistHintBlocked) {
hideAssistHandles();
}
}
private void hideAssistHandles() {
mAssistHintLeft.setVisibility(View.GONE);
mAssistHintRight.setVisibility(View.GONE);
mAssistHintVisible = false;
}
/**
* Returns an animator that animates the given view from start to end over durationMs. Start and
* end represent total animation progress: 0 is the start, 1 is the end, 1.1 would be an
* overshoot.
*/
Animator getHandleAnimator(View view, float start, float end, boolean isLeft, long durationMs,
Interpolator interpolator) {
// Note that lerp does allow overshoot, in cases where start and end are outside of [0,1].
float scaleStart = MathUtils.lerp(2f, 1f, start);
float scaleEnd = MathUtils.lerp(2f, 1f, end);
Animator scaleX = ObjectAnimator.ofFloat(view, View.SCALE_X, scaleStart, scaleEnd);
Animator scaleY = ObjectAnimator.ofFloat(view, View.SCALE_Y, scaleStart, scaleEnd);
float translationStart = MathUtils.lerp(0.2f, 0f, start);
float translationEnd = MathUtils.lerp(0.2f, 0f, end);
int xDirection = isLeft ? -1 : 1;
Animator translateX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
xDirection * translationStart * view.getWidth(),
xDirection * translationEnd * view.getWidth());
Animator translateY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y,
translationStart * view.getHeight() + mBottomOffset,
translationEnd * view.getHeight() + mBottomOffset);
AnimatorSet set = new AnimatorSet();
set.play(scaleX).with(scaleY);
set.play(scaleX).with(translateX);
set.play(scaleX).with(translateY);
set.setDuration(durationMs);
set.setInterpolator(interpolator);
return set;
}
private void fade(View view, boolean fadeIn, boolean isLeft) {
if (fadeIn) {
view.animate().cancel();
view.setAlpha(1f);
view.setVisibility(View.VISIBLE);
// A piecewise spring-like interpolation.
// End value in one animator call must match the start value in the next, otherwise
// there will be a discontinuity.
AnimatorSet anim = new AnimatorSet();
Animator first = getHandleAnimator(view, 0, 1.1f, isLeft, 750,
new PathInterpolator(0, 0.45f, .67f, 1f));
Interpolator secondInterpolator = new PathInterpolator(0.33f, 0, 0.67f, 1f);
Animator second = getHandleAnimator(view, 1.1f, 0.97f, isLeft, 400,
secondInterpolator);
Animator third = getHandleAnimator(view, 0.97f, 1.02f, isLeft, 400,
secondInterpolator);
Animator fourth = getHandleAnimator(view, 1.02f, 1f, isLeft, 400,
secondInterpolator);
anim.play(first).before(second);
anim.play(second).before(third);
anim.play(third).before(fourth);
anim.start();
} else {
view.animate().cancel();
view.animate()
.setInterpolator(new AccelerateInterpolator(1.5f))
.setDuration(250)
.alpha(0f);
}
}
}