script-astra/Android/Sdk/sources/android-35/android/view/ImeBackAnimationController.java

294 lines
13 KiB
Java
Raw Normal View History

2025-01-20 15:15:20 +00:00
/*
* Copyright (C) 2024 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 android.view;
import static android.view.InsetsController.ANIMATION_TYPE_USER;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Insets;
import android.util.Log;
import android.view.animation.BackGestureInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.view.inputmethod.ImeTracker;
import android.window.BackEvent;
import android.window.OnBackAnimationCallback;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import java.io.PrintWriter;
/**
* Controller for IME predictive back animation
*
* @hide
*/
public class ImeBackAnimationController implements OnBackAnimationCallback {
private static final String TAG = "ImeBackAnimationController";
private static final int POST_COMMIT_DURATION_MS = 200;
private static final int POST_COMMIT_CANCEL_DURATION_MS = 50;
private static final float PEEK_FRACTION = 0.1f;
private static final Interpolator BACK_GESTURE = new BackGestureInterpolator();
private static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
0.05f, 0.7f, 0.1f, 1f);
private static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(0.3f, 0f, 1f, 1f);
private final InsetsController mInsetsController;
private final ViewRootImpl mViewRoot;
private WindowInsetsAnimationController mWindowInsetsAnimationController = null;
private ValueAnimator mPostCommitAnimator = null;
private float mLastProgress = 0f;
private boolean mTriggerBack = false;
private boolean mIsPreCommitAnimationInProgress = false;
private int mStartRootScrollY = 0;
public ImeBackAnimationController(ViewRootImpl viewRoot, InsetsController insetsController) {
mInsetsController = insetsController;
mViewRoot = viewRoot;
}
@Override
public void onBackStarted(@NonNull BackEvent backEvent) {
if (!isBackAnimationAllowed()) {
// There is no good solution for a predictive back animation if the app uses
// adjustResize, since we can't relayout the whole app for every frame. We also don't
// want to reveal any black areas behind the IME. Therefore let's not play any animation
// in that case for now.
Log.d(TAG, "onBackStarted -> not playing predictive back animation due to softinput"
+ " mode adjustResize AND no animation callback registered");
return;
}
if (isHideAnimationInProgress()) {
// If IME is currently animating away, skip back gesture
return;
}
mIsPreCommitAnimationInProgress = true;
if (mWindowInsetsAnimationController != null) {
// There's still an active animation controller. This means that a cancel post commit
// animation of an earlier back gesture is still in progress. Let's cancel it and let
// the new gesture seamlessly take over.
resetPostCommitAnimator();
setPreCommitProgress(0f);
return;
}
mInsetsController.controlWindowInsetsAnimation(ime(), /*cancellationSignal*/ null,
new WindowInsetsAnimationControlListener() {
@Override
public void onReady(@NonNull WindowInsetsAnimationController controller,
@WindowInsets.Type.InsetsType int types) {
mWindowInsetsAnimationController = controller;
if (isAdjustPan()) mStartRootScrollY = mViewRoot.mScrollY;
if (mIsPreCommitAnimationInProgress) {
setPreCommitProgress(mLastProgress);
} else {
// gesture has already finished before IME became ready to animate
startPostCommitAnim(mTriggerBack);
}
}
@Override
public void onFinished(@NonNull WindowInsetsAnimationController controller) {
reset();
}
@Override
public void onCancelled(@Nullable WindowInsetsAnimationController controller) {
reset();
}
}, /*fromIme*/ false, /*durationMs*/ -1, /*interpolator*/ null, ANIMATION_TYPE_USER,
/*fromPredictiveBack*/ true);
}
@Override
public void onBackProgressed(@NonNull BackEvent backEvent) {
mLastProgress = backEvent.getProgress();
setPreCommitProgress(mLastProgress);
}
@Override
public void onBackCancelled() {
if (!isBackAnimationAllowed()) return;
startPostCommitAnim(/*hideIme*/ false);
}
@Override
public void onBackInvoked() {
if (!isBackAnimationAllowed() || !mIsPreCommitAnimationInProgress) {
// play regular hide animation if back-animation is not allowed or if insets control has
// been cancelled by the system (this can happen in split screen for example)
mInsetsController.hide(ime());
return;
}
startPostCommitAnim(/*hideIme*/ true);
}
private void setPreCommitProgress(float progress) {
if (isHideAnimationInProgress()) return;
if (mWindowInsetsAnimationController != null) {
float hiddenY = mWindowInsetsAnimationController.getHiddenStateInsets().bottom;
float shownY = mWindowInsetsAnimationController.getShownStateInsets().bottom;
float imeHeight = shownY - hiddenY;
float interpolatedProgress = BACK_GESTURE.getInterpolation(progress);
int newY = (int) (imeHeight - interpolatedProgress * (imeHeight * PEEK_FRACTION));
if (mStartRootScrollY != 0) {
mViewRoot.setScrollY(
(int) (mStartRootScrollY * (1 - interpolatedProgress * PEEK_FRACTION)));
}
mWindowInsetsAnimationController.setInsetsAndAlpha(Insets.of(0, 0, 0, newY), 1f,
progress);
}
}
private void startPostCommitAnim(boolean triggerBack) {
mIsPreCommitAnimationInProgress = false;
if (mWindowInsetsAnimationController == null || isHideAnimationInProgress()) {
mTriggerBack = triggerBack;
return;
}
mTriggerBack = triggerBack;
int currentBottomInset = mWindowInsetsAnimationController.getCurrentInsets().bottom;
int targetBottomInset;
if (triggerBack) {
targetBottomInset = mWindowInsetsAnimationController.getHiddenStateInsets().bottom;
} else {
targetBottomInset = mWindowInsetsAnimationController.getShownStateInsets().bottom;
}
mPostCommitAnimator = ValueAnimator.ofFloat(currentBottomInset, targetBottomInset);
mPostCommitAnimator.setInterpolator(
triggerBack ? STANDARD_ACCELERATE : EMPHASIZED_DECELERATE);
mPostCommitAnimator.addUpdateListener(animation -> {
int bottomInset = (int) ((float) animation.getAnimatedValue());
if (mWindowInsetsAnimationController != null) {
mWindowInsetsAnimationController.setInsetsAndAlpha(Insets.of(0, 0, 0, bottomInset),
1f, animation.getAnimatedFraction());
} else {
reset();
}
});
mPostCommitAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
if (mIsPreCommitAnimationInProgress) {
// this means a new gesture has started while the cancel-post-commit-animation
// was in progress. Let's not reset anything and let the new user gesture take
// over seamlessly
return;
}
if (mWindowInsetsAnimationController != null) {
mWindowInsetsAnimationController.finish(!triggerBack);
}
reset();
}
});
mPostCommitAnimator.setDuration(
triggerBack ? POST_COMMIT_DURATION_MS : POST_COMMIT_CANCEL_DURATION_MS);
mPostCommitAnimator.start();
if (triggerBack) {
mInsetsController.setPredictiveBackImeHideAnimInProgress(true);
notifyHideIme();
}
if (mStartRootScrollY != 0 && !triggerBack) {
// This causes RootView to update its scroll back to the panned position
mInsetsController.getHost().notifyInsetsChanged();
}
}
private void notifyHideIme() {
ImeTracker.Token statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
ImeTracker.ORIGIN_CLIENT,
SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL, true);
// This notifies the IME that it is being hidden. In response, the IME will unregister the
// animation callback, such that new back gestures happening during the post-commit phase of
// the hide animation can already dispatch to a new callback.
// Note that the IME will call hide() in InsetsController. InsetsController will not animate
// that hide request if it sees that ImeBackAnimationController is already animating
// the IME away
mInsetsController.getHost().getInputMethodManager()
.notifyImeHidden(mInsetsController.getHost().getWindowToken(), statsToken);
// requesting IME as invisible during post-commit
mInsetsController.setRequestedVisibleTypes(0, ime());
// Changes the animation state. This also notifies RootView of changed insets, which causes
// it to reset its scrollY to 0f (animated) if it was panned
mInsetsController.onAnimationStateChanged(ime(), /*running*/ true);
}
private void reset() {
mWindowInsetsAnimationController = null;
resetPostCommitAnimator();
mLastProgress = 0f;
mTriggerBack = false;
mIsPreCommitAnimationInProgress = false;
mInsetsController.setPredictiveBackImeHideAnimInProgress(false);
mStartRootScrollY = 0;
}
private void resetPostCommitAnimator() {
if (mPostCommitAnimator != null) {
mPostCommitAnimator.cancel();
mPostCommitAnimator = null;
}
}
private boolean isBackAnimationAllowed() {
// back animation is allowed in all cases except when softInputMode is adjust_resize AND
// there is no app-registered WindowInsetsAnimationCallback.
return (mViewRoot.mWindowAttributes.softInputMode & SOFT_INPUT_MASK_ADJUST)
!= SOFT_INPUT_ADJUST_RESIZE
|| (mViewRoot.mView != null && mViewRoot.mView.hasWindowInsetsAnimationCallback());
}
private boolean isAdjustPan() {
return (mViewRoot.mWindowAttributes.softInputMode & SOFT_INPUT_MASK_ADJUST)
== SOFT_INPUT_ADJUST_PAN;
}
private boolean isHideAnimationInProgress() {
return mPostCommitAnimator != null && mTriggerBack;
}
/**
* Dump information about this ImeBackAnimationController
*
* @param prefix the prefix that will be prepended to each line of the produced output
* @param writer the writer that will receive the resulting text
*/
public void dump(String prefix, PrintWriter writer) {
final String innerPrefix = prefix + " ";
writer.println(prefix + "ImeBackAnimationController:");
writer.println(innerPrefix + "mLastProgress=" + mLastProgress);
writer.println(innerPrefix + "mTriggerBack=" + mTriggerBack);
writer.println(innerPrefix + "mIsPreCommitAnimationInProgress="
+ mIsPreCommitAnimationInProgress);
writer.println(innerPrefix + "mStartRootScrollY=" + mStartRootScrollY);
writer.println(innerPrefix + "isBackAnimationAllowed=" + isBackAnimationAllowed());
writer.println(innerPrefix + "isAdjustPan=" + isAdjustPan());
writer.println(innerPrefix + "isHideAnimationInProgress="
+ isHideAnimationInProgress());
}
}