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

Commit a7578af1 authored by Ben Lin's avatar Ben Lin Committed by Android (Google) Code Review
Browse files

Merge "Refactor/Clean up on PiP dismiss code."

parents d62f1478 8e9a3806
Loading
Loading
Loading
Loading
+295 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.wm.shell.pip.phone;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.TransitionDrawable;
import android.os.Handler;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringForce;

import com.android.wm.shell.R;
import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.common.DismissCircleView;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.pip.PipUiEventLogger;

import kotlin.Unit;

/**
 * Handler of all Magnetized Object related code for PiP.
 */
public class PipDismissTargetHandler {

    /* The multiplier to apply scale the target size by when applying the magnetic field radius */
    private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f;

    /** Duration of the dismiss scrim fading in/out. */
    private static final int DISMISS_TRANSITION_DURATION_MS = 200;

    /**
     * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
     * PIP.
     */
    private final MagnetizedObject<Rect> mMagnetizedPip;

    /**
     * Container for the dismiss circle, so that it can be animated within the container via
     * translation rather than within the WindowManager via slow layout animations.
     */
    private final ViewGroup mTargetViewContainer;

    /** Circle view used to render the dismiss target. */
    private final DismissCircleView mTargetView;

    /**
     * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
     */
    private final MagnetizedObject.MagneticTarget mMagneticTarget;

    /** PhysicsAnimator instance for animating the dismiss target in/out. */
    private final PhysicsAnimator<View> mMagneticTargetAnimator;

    /** Default configuration to use for springing the dismiss target in/out. */
    private final PhysicsAnimator.SpringConfig mTargetSpringConfig =
            new PhysicsAnimator.SpringConfig(
                    SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);

    /**
     * Runnable that can be posted delayed to show the target. This needs to be saved as a member
     * variable so we can pass it to removeCallbacks.
     */
    private Runnable mShowTargetAction = this::showDismissTargetMaybe;

    // Allow dragging the PIP to a location to close it
    private final boolean mEnableDismissDragToEdge;

    private int mDismissAreaHeight;

    private final Context mContext;
    private final PipMotionHelper mMotionHelper;
    private final PipUiEventLogger mPipUiEventLogger;
    private final WindowManager mWindowManager;
    private final Handler mHandler;

    public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger,
            PipMotionHelper motionHelper, Handler handler) {
        mContext = context;
        mPipUiEventLogger = pipUiEventLogger;
        mMotionHelper = motionHelper;
        mHandler = handler;
        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);

        Resources res = context.getResources();
        mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
        mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);

        mTargetView = new DismissCircleView(context);
        mTargetViewContainer = new FrameLayout(context);
        mTargetViewContainer.setBackgroundDrawable(
                context.getDrawable(R.drawable.floating_dismiss_gradient_transition));
        mTargetViewContainer.setClipChildren(false);
        mTargetViewContainer.addView(mTargetView);

        mMagnetizedPip = mMotionHelper.getMagnetizedPip();
        mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
        updateMagneticTargetSize();

        mMagnetizedPip.setAnimateStuckToTarget(
                (target, velX, velY, flung, after) -> {
                    if (mEnableDismissDragToEdge) {
                        mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after);
                    }
                    return Unit.INSTANCE;
                });
        mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
            @Override
            public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
                // Show the dismiss target, in case the initial touch event occurred within the
                // magnetic field radius.
                if (mEnableDismissDragToEdge) {
                    showDismissTargetMaybe();
                }
            }

            @Override
            public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
                    float velX, float velY, boolean wasFlungOut) {
                if (wasFlungOut) {
                    mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */);
                    hideDismissTargetMaybe();
                } else {
                    mMotionHelper.setSpringingToTouch(true);
                }
            }

            @Override
            public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
                mMotionHelper.notifyDismissalPending();

                handler.post(() -> {
                    mMotionHelper.animateDismiss();
                    hideDismissTargetMaybe();
                });

                mPipUiEventLogger.log(
                        PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
            }
        });

        mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
    }

    /**
     * Potentially start consuming future motion events if PiP is currently near the magnetized
     * object.
     */
    public boolean maybeConsumeMotionEvent(MotionEvent ev) {
        return mMagnetizedPip.maybeConsumeMotionEvent(ev);
    }

    /**
     * Update the magnet size.
     */
    public void updateMagneticTargetSize() {
        if (mTargetView == null) {
            return;
        }

        final Resources res = mContext.getResources();
        final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
        mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
        final FrameLayout.LayoutParams newParams =
                new FrameLayout.LayoutParams(targetSize, targetSize);
        newParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
        newParams.bottomMargin = mContext.getResources().getDimensionPixelSize(
                R.dimen.floating_dismiss_bottom_margin);
        mTargetView.setLayoutParams(newParams);

        // Set the magnetic field radius equal to the target size from the center of the target
        mMagneticTarget.setMagneticFieldRadiusPx(
                (int) (targetSize * MAGNETIC_FIELD_RADIUS_MULTIPLIER));
    }

    /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
    public void createOrUpdateDismissTarget() {
        if (!mTargetViewContainer.isAttachedToWindow()) {
            mHandler.removeCallbacks(mShowTargetAction);
            mMagneticTargetAnimator.cancel();

            mTargetViewContainer.setVisibility(View.INVISIBLE);

            try {
                mWindowManager.addView(mTargetViewContainer, getDismissTargetLayoutParams());
            } catch (IllegalStateException e) {
                // This shouldn't happen, but if the target is already added, just update its layout
                // params.
                mWindowManager.updateViewLayout(
                        mTargetViewContainer, getDismissTargetLayoutParams());
            }
        } else {
            mWindowManager.updateViewLayout(mTargetViewContainer, getDismissTargetLayoutParams());
        }
    }

    /** Returns layout params for the dismiss target, using the latest display metrics. */
    private WindowManager.LayoutParams getDismissTargetLayoutParams() {
        final Point windowSize = new Point();
        mWindowManager.getDefaultDisplay().getRealSize(windowSize);

        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.MATCH_PARENT,
                mDismissAreaHeight,
                0, windowSize.y - mDismissAreaHeight,
                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT);

        lp.setTitle("pip-dismiss-overlay");
        lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
        lp.setFitInsetsTypes(0 /* types */);

        return lp;
    }

    /** Makes the dismiss target visible and animates it in, if it isn't already visible. */
    public void showDismissTargetMaybe() {
        if (!mEnableDismissDragToEdge) {
            return;
        }

        createOrUpdateDismissTarget();

        if (mTargetViewContainer.getVisibility() != View.VISIBLE) {

            mTargetView.setTranslationY(mTargetViewContainer.getHeight());
            mTargetViewContainer.setVisibility(View.VISIBLE);

            // Cancel in case we were in the middle of animating it out.
            mMagneticTargetAnimator.cancel();
            mMagneticTargetAnimator
                    .spring(DynamicAnimation.TRANSLATION_Y, 0f, mTargetSpringConfig)
                    .start();

            ((TransitionDrawable) mTargetViewContainer.getBackground()).startTransition(
                    DISMISS_TRANSITION_DURATION_MS);
        }
    }

    /** Animates the magnetic dismiss target out and then sets it to GONE. */
    public void hideDismissTargetMaybe() {
        if (!mEnableDismissDragToEdge) {
            return;
        }

        mHandler.removeCallbacks(mShowTargetAction);
        mMagneticTargetAnimator
                .spring(DynamicAnimation.TRANSLATION_Y,
                        mTargetViewContainer.getHeight(),
                        mTargetSpringConfig)
                .withEndActions(() -> mTargetViewContainer.setVisibility(View.GONE))
                .start();

        ((TransitionDrawable) mTargetViewContainer.getBackground()).reverseTransition(
                DISMISS_TRANSITION_DURATION_MS);
    }

    /**
     * Removes the dismiss target and cancels any pending callbacks to show it.
     */
    public void cleanUpDismissTarget() {
        mHandler.removeCallbacks(mShowTargetAction);

        if (mTargetViewContainer.isAttachedToWindow()) {
            mWindowManager.removeViewImmediate(mTargetViewContainer);
        }
    }
}
+0 −14
Original line number Diff line number Diff line
@@ -155,20 +155,6 @@ public class PipMenuActivityController {
        }
    }

    /**
     * Updates the appearance of the menu and scrim on top of the PiP while dismissing.
     */
    public void setDismissFraction(float fraction) {
        final boolean isMenuVisible = isMenuVisible();
        if (DEBUG) {
            Log.d(TAG, "setDismissFraction() isMenuVisible=" + isMenuVisible
                    + " fraction=" + fraction);
        }
        if (isMenuVisible) {
            mPipMenuView.updateDismissFraction(fraction);
        }
    }

    /**
     * Similar to {@link #showMenu(int, Rect, boolean, boolean, boolean)} but only show the menu
     * upon PiP window transition is finished.
+0 −19
Original line number Diff line number Diff line
@@ -429,25 +429,6 @@ public class PipMenuView extends FrameLayout {
        }
    }

    void updateDismissFraction(float fraction) {
        int alpha;
        final float menuAlpha = 1 - fraction;
        if (mMenuState == MENU_STATE_FULL) {
            mMenuContainer.setAlpha(menuAlpha);
            mSettingsButton.setAlpha(menuAlpha);
            mDismissButton.setAlpha(menuAlpha);
            final float interpolatedAlpha =
                    MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction;
            alpha = (int) (interpolatedAlpha * 255);
        } else {
            if (mMenuState == MENU_STATE_CLOSE) {
                mDismissButton.setAlpha(menuAlpha);
            }
            alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
        }
        mBackgroundDrawable.setAlpha(alpha);
    }

    private void notifyMenuStateChange(int menuState, boolean resize, Runnable callback) {
        mMenuState = menuState;
        mController.onMenuStateChanged(menuState, resize, callback);
+5 −13
Original line number Diff line number Diff line
@@ -385,23 +385,20 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
     * Flings the PiP to the closest snap target.
     */
    void flingToSnapTarget(
            float velocityX, float velocityY,
            @Nullable Runnable updateAction, @Nullable Runnable endAction) {
        movetoTarget(velocityX, velocityY, updateAction, endAction, false /* isStash */);
            float velocityX, float velocityY, @Nullable Runnable endAction) {
        movetoTarget(velocityX, velocityY, endAction, false /* isStash */);
    }

    /**
     * Stash PiP to the closest edge.
     */
    void stashToEdge(
            float velocityX, float velocityY,
            @Nullable Runnable updateAction, @Nullable Runnable endAction) {
        movetoTarget(velocityX, velocityY, updateAction, endAction, true /* isStash */);
            float velocityX, float velocityY, @Nullable Runnable endAction) {
        movetoTarget(velocityX, velocityY, endAction, true /* isStash */);
    }

    private void movetoTarget(
            float velocityX, float velocityY,
            @Nullable Runnable updateAction, @Nullable Runnable endAction, boolean isStash) {
            float velocityX, float velocityY, @Nullable Runnable endAction, boolean isStash) {
        // If we're flinging to a snap target now, we're not springing to catch up to the touch
        // location now.
        mSpringingToTouch = false;
@@ -416,11 +413,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
                        FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig)
                .withEndActions(endAction);

        if (updateAction != null) {
            mTemporaryBoundsPhysicsAnimator.addUpdateListener(
                    (target, values) -> updateAction.run());
        }

        final float offset = ((float) mBounds.width()) * (1.0f - STASH_RATIO);
        final float leftEdge = isStash ? mMovementBounds.left - offset : mMovementBounds.left;
        final float rightEdge = isStash ?  mMovementBounds.right + offset : mMovementBounds.right;
+12 −259

File changed.

Preview size limit exceeded, changes collapsed.