Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c48c9393 authored by Evan Rosky's avatar Evan Rosky
Browse files

Move DividerImeController into its own top-level class

First of all, this component is getting rather large so
just organizationally it makes sense to separate it.

Secondly, other pieces will need this injected (DividerView)
to fix some bugs, so it needs to be separated anyways.

Bug: 156775140
Test: just refactor, so check that nothing has changed
Change-Id: I30b9c746ba2cc01c5a38b174c1d06c003683411b
parent 1089ed94
Loading
Loading
Loading
Loading
+6 −307
Original line number Diff line number Diff line
@@ -18,15 +18,10 @@ package com.android.systemui.stackdivider;

import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;

import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.content.Context;
@@ -36,15 +31,11 @@ import android.os.Handler;
import android.provider.Settings;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.View;
import android.window.TaskOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowOrganizer;

import androidx.annotation.Nullable;

import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
@@ -81,7 +72,6 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
    static final boolean DEBUG = false;

    static final int DEFAULT_APP_TRANSITION_DURATION = 336;
    static final float ADJUSTED_NONFOCUS_DIM = 0.3f;

    private final Optional<Lazy<Recents>> mRecentsOptionalLazy;

@@ -139,303 +129,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
                }
            };

    private class DividerImeController implements DisplayImeController.ImePositionProcessor {
        /**
         * These are the y positions of the top of the IME surface when it is hidden and when it is
         * shown respectively. These are NOT necessarily the top of the visible IME itself.
         */
        private int mHiddenTop = 0;
        private int mShownTop = 0;

        // The following are target states (what we are curretly animating towards).
        /**
         * {@code true} if, at the end of the animation, the split task positions should be
         * adjusted by height of the IME. This happens when the secondary split is the IME target.
         */
        private boolean mTargetAdjusted = false;
        /**
         * {@code true} if, at the end of the animation, the IME should be shown/visible
         * regardless of what has focus.
         */
        private boolean mTargetShown = false;
        private float mTargetPrimaryDim = 0.f;
        private float mTargetSecondaryDim = 0.f;

        // The following are the current (most recent) states set during animation
        /** {@code true} if the secondary split has IME focus. */
        private boolean mSecondaryHasFocus = false;
        /** The dimming currently applied to the primary/secondary splits. */
        private float mLastPrimaryDim = 0.f;
        private float mLastSecondaryDim = 0.f;
        /** The most recent y position of the top of the IME surface */
        private int mLastAdjustTop = -1;

        // The following are states reached last time an animation fully completed.
        /** {@code true} if the IME was shown/visible by the last-completed animation. */
        private boolean mImeWasShown = false;
        /** {@code true} if the split positions were adjusted by the last-completed animation. */
        private boolean mAdjusted = false;

        /**
         * When some aspect of split-screen needs to animate independent from the IME,
         * this will be non-null and control split animation.
         */
        @Nullable
        private ValueAnimator mAnimation = null;

        private boolean mPaused = true;
        private boolean mPausedTargetAdjusted = false;

        private boolean getSecondaryHasFocus(int displayId) {
            WindowContainerToken imeSplit = TaskOrganizer.getImeTarget(displayId);
            return imeSplit != null
                    && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder());
        }

        private void updateDimTargets() {
            final boolean splitIsVisible = !mView.isHidden();
            mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible)
                    ? ADJUSTED_NONFOCUS_DIM : 0.f;
            mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible)
                    ? ADJUSTED_NONFOCUS_DIM : 0.f;
        }

        @Override
        public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
                boolean imeShouldShow, SurfaceControl.Transaction t) {
            if (!isDividerVisible()) {
                return;
            }
            final boolean splitIsVisible = !mView.isHidden();
            mSecondaryHasFocus = getSecondaryHasFocus(displayId);
            final boolean targetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus
                    && !mSplitLayout.mDisplayLayout.isLandscape();
            mHiddenTop = hiddenTop;
            mShownTop = shownTop;
            mTargetShown = imeShouldShow;
            if (mLastAdjustTop < 0) {
                mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop;
            } else if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) {
                if (mTargetAdjusted != targetAdjusted && targetAdjusted == mAdjusted) {
                    // Check for an "interruption" of an existing animation. In this case, we
                    // need to fake-flip the last-known state direction so that the animation
                    // completes in the other direction.
                    mAdjusted = mTargetAdjusted;
                } else if (targetAdjusted && mTargetAdjusted && mAdjusted) {
                    // Already fully adjusted for IME, but IME height has changed; so, force-start
                    // an async animation to the new IME height.
                    mAdjusted = false;
                }
            }
            if (mPaused) {
                mPausedTargetAdjusted = targetAdjusted;
                if (DEBUG) Slog.d(TAG, " ime starting but paused " + dumpState());
                return;
            }
            mTargetAdjusted = targetAdjusted;
            updateDimTargets();
            if (DEBUG) Slog.d(TAG, " ime starting. vis:" + splitIsVisible + "  " + dumpState());
            if (mAnimation != null || (mImeWasShown && imeShouldShow
                    && mTargetAdjusted != mAdjusted)) {
                // We need to animate adjustment independently of the IME position, so
                // start our own animation to drive adjustment. This happens when a
                // different split's editor has gained focus while the IME is still visible.
                startAsyncAnimation();
            }
            if (splitIsVisible) {
                // If split is hidden, we don't want to trigger any relayouts that would cause the
                // divider to show again.
                updateImeAdjustState();
            }
        }

        private void updateImeAdjustState() {
            // Reposition the server's secondary split position so that it evaluates
            // insets properly.
            WindowContainerTransaction wct = new WindowContainerTransaction();
            if (mTargetAdjusted) {
                mSplitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop);
                wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mAdjustedSecondary);
                // "Freeze" the configuration size so that the app doesn't get a config
                // or relaunch. This is required because normally nav-bar contributes
                // to configuration bounds (via nondecorframe).
                Rect adjustAppBounds = new Rect(mSplits.mSecondary.configuration
                        .windowConfiguration.getAppBounds());
                adjustAppBounds.offset(0, mSplitLayout.mAdjustedSecondary.top
                        - mSplitLayout.mSecondary.top);
                wct.setAppBounds(mSplits.mSecondary.token, adjustAppBounds);
                wct.setScreenSizeDp(mSplits.mSecondary.token,
                        mSplits.mSecondary.configuration.screenWidthDp,
                        mSplits.mSecondary.configuration.screenHeightDp);

                wct.setBounds(mSplits.mPrimary.token, mSplitLayout.mAdjustedPrimary);
                adjustAppBounds = new Rect(mSplits.mPrimary.configuration
                        .windowConfiguration.getAppBounds());
                adjustAppBounds.offset(0, mSplitLayout.mAdjustedPrimary.top
                        - mSplitLayout.mPrimary.top);
                wct.setAppBounds(mSplits.mPrimary.token, adjustAppBounds);
                wct.setScreenSizeDp(mSplits.mPrimary.token,
                        mSplits.mPrimary.configuration.screenWidthDp,
                        mSplits.mPrimary.configuration.screenHeightDp);
            } else {
                wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mSecondary);
                wct.setAppBounds(mSplits.mSecondary.token, null);
                wct.setScreenSizeDp(mSplits.mSecondary.token,
                        SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
                wct.setBounds(mSplits.mPrimary.token, mSplitLayout.mPrimary);
                wct.setAppBounds(mSplits.mPrimary.token, null);
                wct.setScreenSizeDp(mSplits.mPrimary.token,
                        SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
            }

            WindowOrganizer.applyTransaction(wct);

            // Update all the adjusted-for-ime states
            if (!mPaused) {
                mView.setAdjustedForIme(mTargetShown, mTargetShown
                        ? DisplayImeController.ANIMATION_DURATION_SHOW_MS
                        : DisplayImeController.ANIMATION_DURATION_HIDE_MS);
            }
            setAdjustedForIme(mTargetShown && !mPaused);
        }

        @Override
        public void onImePositionChanged(int displayId, int imeTop,
                SurfaceControl.Transaction t) {
            if (mAnimation != null || !isDividerVisible() || mPaused) {
                // Not synchronized with IME anymore, so return.
                return;
            }
            final float fraction = ((float) imeTop - mHiddenTop) / (mShownTop - mHiddenTop);
            final float progress = mTargetShown ? fraction : 1.f - fraction;
            onProgress(progress, t);
        }

        @Override
        public void onImeEndPositioning(int displayId, boolean cancelled,
                SurfaceControl.Transaction t) {
            if (mAnimation != null || !isDividerVisible() || mPaused) {
                // Not synchronized with IME anymore, so return.
                return;
            }
            onEnd(cancelled, t);
        }

        private void onProgress(float progress, SurfaceControl.Transaction t) {
            if (mTargetAdjusted != mAdjusted && !mPaused) {
                final float fraction = mTargetAdjusted ? progress : 1.f - progress;
                mLastAdjustTop = (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop);
                mSplitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop);
                mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary,
                        mSplitLayout.mAdjustedSecondary);
            }
            final float invProg = 1.f - progress;
            mView.setResizeDimLayer(t, true /* primary */,
                    mLastPrimaryDim * invProg + progress * mTargetPrimaryDim);
            mView.setResizeDimLayer(t, false /* primary */,
                    mLastSecondaryDim * invProg + progress * mTargetSecondaryDim);
        }

        private void onEnd(boolean cancelled, SurfaceControl.Transaction t) {
            if (!cancelled) {
                onProgress(1.f, t);
                mAdjusted = mTargetAdjusted;
                mImeWasShown = mTargetShown;
                mLastAdjustTop = mAdjusted ? mShownTop : mHiddenTop;
                mLastPrimaryDim = mTargetPrimaryDim;
                mLastSecondaryDim = mTargetSecondaryDim;
            }
        }

        private void startAsyncAnimation() {
            if (mAnimation != null) {
                mAnimation.cancel();
            }
            mAnimation = ValueAnimator.ofFloat(0.f, 1.f);
            mAnimation.setDuration(DisplayImeController.ANIMATION_DURATION_SHOW_MS);
            if (mTargetAdjusted != mAdjusted) {
                final float fraction =
                        ((float) mLastAdjustTop - mHiddenTop) / (mShownTop - mHiddenTop);
                final float progress = mTargetAdjusted ? fraction : 1.f - fraction;
                mAnimation.setCurrentFraction(progress);
            }

            mAnimation.addUpdateListener(animation -> {
                SurfaceControl.Transaction t = mTransactionPool.acquire();
                float value = (float) animation.getAnimatedValue();
                onProgress(value, t);
                t.apply();
                mTransactionPool.release(t);
            });
            mAnimation.setInterpolator(DisplayImeController.INTERPOLATOR);
            mAnimation.addListener(new AnimatorListenerAdapter() {
                private boolean mCancel = false;
                @Override
                public void onAnimationCancel(Animator animation) {
                    mCancel = true;
                }
                @Override
                public void onAnimationEnd(Animator animation) {
                    SurfaceControl.Transaction t = mTransactionPool.acquire();
                    onEnd(mCancel, t);
                    t.apply();
                    mTransactionPool.release(t);
                    mAnimation = null;
                }
            });
            mAnimation.start();
        }

        private String dumpState() {
            return "top:" + mHiddenTop + "->" + mShownTop
                    + " adj:" + mAdjusted + "->" + mTargetAdjusted + "(" + mLastAdjustTop + ")"
                    + " shw:" + mImeWasShown + "->" + mTargetShown
                    + " dims:" + mLastPrimaryDim + "," + mLastSecondaryDim
                    + "->" + mTargetPrimaryDim + "," + mTargetSecondaryDim
                    + " shf:" + mSecondaryHasFocus + " desync:" + (mAnimation != null)
                    + " paus:" + mPaused + "[" + mPausedTargetAdjusted + "]";
        }

        /** Completely aborts/resets adjustment state */
        public void pause(int displayId) {
            if (DEBUG) Slog.d(TAG, "ime pause posting " + dumpState());
            mHandler.post(() -> {
                if (DEBUG) Slog.d(TAG, "ime pause run posted " + dumpState());
                if (mPaused) {
                    return;
                }
                mPaused = true;
                mPausedTargetAdjusted = mTargetAdjusted;
                mTargetAdjusted = false;
                mTargetPrimaryDim = mTargetSecondaryDim = 0.f;
                updateImeAdjustState();
                startAsyncAnimation();
                if (mAnimation != null) {
                    mAnimation.end();
                }
            });
        }

        public void resume(int displayId) {
            if (DEBUG) Slog.d(TAG, "ime resume posting " + dumpState());
            mHandler.post(() -> {
                if (DEBUG) Slog.d(TAG, "ime resume run posted " + dumpState());
                if (!mPaused) {
                    return;
                }
                mPaused = false;
                mTargetAdjusted = mPausedTargetAdjusted;
                updateDimTargets();
                if ((mTargetAdjusted != mAdjusted) && !mMinimized && mView != null) {
                    // End unminimize animations since they conflict with adjustment animations.
                    mView.finishAnimations();
                }
                updateImeAdjustState();
                startAsyncAnimation();
            });
        }
    }
    private final DividerImeController mImePositionProcessor = new DividerImeController();
    private final DividerImeController mImePositionProcessor;

    private TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
        @Override
@@ -465,6 +159,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
        mRecentsOptionalLazy = recentsOptionalLazy;
        mForcedResizableController = new ForcedResizableInfoActivityController(context, this);
        mTransactionPool = transactionPool;
        mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler);
    }

    @Override
@@ -830,6 +525,10 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
        }
    }

    SplitDisplayLayout getSplitLayout() {
        return mSplitLayout;
    }

    /** @return the container token for the secondary split root task. */
    public WindowContainerToken getSecondaryRoot() {
        if (mSplits == null || mSplits.mSecondary == null) {
+368 −0

File added.

Preview size limit exceeded, changes collapsed.