Merge "Modify PIP to use the magnetic target from Bubbles." into rvc-dev am: 1ff0d350eb
Change-Id: I0ff7a81d24a9c6dbffcc712c5b5f6c6b17db3bc5
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/pip_dismiss_gradient_height"
|
||||
android:layout_height="@dimen/floating_dismiss_gradient_height"
|
||||
android:layout_gravity="bottom|center_horizontal">
|
||||
|
||||
<FrameLayout
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2016 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.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/pip_dismiss_gradient_height"
|
||||
android:alpha="0">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pip_dismiss_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:text="@string/pip_phone_dismiss_hint"
|
||||
android:textColor="#FFFFFFFF"
|
||||
android:textSize="14sp"
|
||||
android:shadowColor="@android:color/black"
|
||||
android:shadowDx="-2"
|
||||
android:shadowDy="2"
|
||||
android:shadowRadius="0.01" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -955,7 +955,7 @@
|
||||
<dimen name="recents_quick_scrub_onboarding_margin_start">8dp</dimen>
|
||||
|
||||
<!-- The height of the gradient indicating the dismiss edge when moving a PIP. -->
|
||||
<dimen name="pip_dismiss_gradient_height">176dp</dimen>
|
||||
<dimen name="floating_dismiss_gradient_height">176dp</dimen>
|
||||
|
||||
<!-- The bottom margin of the PIP drag to dismiss info text shown when moving a PIP. -->
|
||||
<dimen name="pip_dismiss_text_bottom_margin">24dp</dimen>
|
||||
|
||||
@@ -514,7 +514,7 @@ public class BubbleStackView extends FrameLayout {
|
||||
mDismissTargetContainer = new FrameLayout(context);
|
||||
mDismissTargetContainer.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
MATCH_PARENT,
|
||||
getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height),
|
||||
getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height),
|
||||
Gravity.BOTTOM));
|
||||
mDismissTargetContainer.setClipChildren(false);
|
||||
mDismissTargetContainer.addView(targetView);
|
||||
@@ -523,7 +523,7 @@ public class BubbleStackView extends FrameLayout {
|
||||
|
||||
// Start translated down so the target springs up.
|
||||
targetView.setTranslationY(
|
||||
getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height));
|
||||
getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height));
|
||||
|
||||
// Save the MagneticTarget instance for the newly set up view - we'll add this to the
|
||||
// MagnetizedObjects.
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.pip.phone;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.android.systemui.Interpolators;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.shared.system.WindowManagerWrapper;
|
||||
|
||||
/**
|
||||
* Displays the dismiss UI and target for floating objects.
|
||||
*/
|
||||
public class PipDismissViewController {
|
||||
|
||||
// This delay controls how long to wait before we show the target when the user first moves
|
||||
// the PIP, to prevent the target from animating if the user just wants to fling the PIP
|
||||
public static final int SHOW_TARGET_DELAY = 100;
|
||||
private static final int SHOW_TARGET_DURATION = 350;
|
||||
private static final int HIDE_TARGET_DURATION = 225;
|
||||
|
||||
private Context mContext;
|
||||
private WindowManager mWindowManager;
|
||||
private View mDismissView;
|
||||
|
||||
// Used for dismissing a bubble -- bubble should be in the target to be considered a dismiss
|
||||
private View mTargetView;
|
||||
private int mTargetSlop;
|
||||
private Point mWindowSize;
|
||||
private int[] mLoc = new int[2];
|
||||
private boolean mIntersecting;
|
||||
private Vibrator mVibe;
|
||||
|
||||
public PipDismissViewController(Context context) {
|
||||
mContext = context;
|
||||
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
mVibe = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the dismiss target for showing via {@link #showDismissTarget()}.
|
||||
*/
|
||||
public void createDismissTarget() {
|
||||
if (mDismissView == null) {
|
||||
// Determine sizes for the view
|
||||
final Rect stableInsets = new Rect();
|
||||
WindowManagerWrapper.getInstance().getStableInsets(stableInsets);
|
||||
mWindowSize = new Point();
|
||||
mWindowManager.getDefaultDisplay().getRealSize(mWindowSize);
|
||||
final int gradientHeight = mContext.getResources().getDimensionPixelSize(
|
||||
R.dimen.pip_dismiss_gradient_height);
|
||||
final int bottomMargin = mContext.getResources().getDimensionPixelSize(
|
||||
R.dimen.pip_dismiss_text_bottom_margin);
|
||||
mTargetSlop = mContext.getResources().getDimensionPixelSize(
|
||||
R.dimen.bubble_dismiss_slop);
|
||||
|
||||
// Create a new view for the dismiss target
|
||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
mDismissView = inflater.inflate(R.layout.pip_dismiss_view, null);
|
||||
mDismissView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
mDismissView.forceHasOverlappingRendering(false);
|
||||
|
||||
// Set the gradient background
|
||||
Drawable gradient = mContext.getResources().getDrawable(R.drawable.pip_dismiss_scrim);
|
||||
gradient.setAlpha((int) (255 * 0.85f));
|
||||
mDismissView.setBackground(gradient);
|
||||
|
||||
// Adjust bottom margins of the text
|
||||
mTargetView = mDismissView.findViewById(R.id.pip_dismiss_text);
|
||||
FrameLayout.LayoutParams tlp = (FrameLayout.LayoutParams) mTargetView.getLayoutParams();
|
||||
tlp.bottomMargin = stableInsets.bottom + bottomMargin;
|
||||
mTargetView.setLayoutParams(tlp);
|
||||
|
||||
// Add the target to the window
|
||||
LayoutParams lp = new LayoutParams(
|
||||
LayoutParams.MATCH_PARENT, gradientHeight,
|
||||
0, mWindowSize.y - gradientHeight,
|
||||
LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
|
||||
LayoutParams.FLAG_LAYOUT_IN_SCREEN
|
||||
| LayoutParams.FLAG_NOT_TOUCHABLE
|
||||
| LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||
PixelFormat.TRANSLUCENT);
|
||||
lp.setTitle("pip-dismiss-overlay");
|
||||
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
|
||||
lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
|
||||
lp.setFitInsetsTypes(0 /* types */);
|
||||
mWindowManager.addView(mDismissView, lp);
|
||||
}
|
||||
mDismissView.animate().cancel();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the dismiss target based on location of the view, only used for bubbles not for PIP.
|
||||
*
|
||||
* @return whether the view is within the dismiss target.
|
||||
*/
|
||||
public boolean updateTarget(View view) {
|
||||
if (mDismissView == null) {
|
||||
return false;
|
||||
}
|
||||
if (mDismissView.getAlpha() > 0) {
|
||||
view.getLocationOnScreen(mLoc);
|
||||
Rect viewRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + view.getWidth(),
|
||||
mLoc[1] + view.getHeight());
|
||||
mTargetView.getLocationOnScreen(mLoc);
|
||||
Rect targetRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + mTargetView.getWidth(),
|
||||
mLoc[1] + mTargetView.getHeight());
|
||||
expandRect(targetRect, mTargetSlop);
|
||||
boolean intersecting = targetRect.intersect(viewRect);
|
||||
if (intersecting != mIntersecting) {
|
||||
// TODO: is this the right effect?
|
||||
mVibe.vibrate(VibrationEffect.get(intersecting
|
||||
? VibrationEffect.EFFECT_CLICK
|
||||
: VibrationEffect.EFFECT_TICK));
|
||||
}
|
||||
mIntersecting = intersecting;
|
||||
return intersecting;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the dismiss target.
|
||||
*/
|
||||
public void showDismissTarget() {
|
||||
mDismissView.animate()
|
||||
.alpha(1f)
|
||||
.setInterpolator(Interpolators.LINEAR)
|
||||
.setStartDelay(SHOW_TARGET_DELAY)
|
||||
.setDuration(SHOW_TARGET_DURATION)
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides and destroys the dismiss target.
|
||||
*/
|
||||
public void destroyDismissTarget() {
|
||||
if (mDismissView != null) {
|
||||
mDismissView.animate()
|
||||
.alpha(0f)
|
||||
.setInterpolator(Interpolators.LINEAR)
|
||||
.setStartDelay(0)
|
||||
.setDuration(HIDE_TARGET_DURATION)
|
||||
.withEndAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mWindowManager.removeViewImmediate(mDismissView);
|
||||
mDismissView = null;
|
||||
}
|
||||
})
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void expandRect(Rect outRect, int expandAmount) {
|
||||
outRect.left = Math.max(0, outRect.left - expandAmount);
|
||||
outRect.top = Math.max(0, outRect.top - expandAmount);
|
||||
outRect.right = Math.min(mWindowSize.x, outRect.right + expandAmount);
|
||||
outRect.bottom = Math.min(mWindowSize.y, outRect.bottom + expandAmount);
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,6 @@ import android.annotation.Nullable;
|
||||
import android.app.ActivityManager.StackInfo;
|
||||
import android.app.IActivityTaskManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Debug;
|
||||
import android.os.RemoteException;
|
||||
@@ -40,6 +38,7 @@ import com.android.systemui.statusbar.FlingAnimationUtils;
|
||||
import com.android.systemui.util.FloatingContentCoordinator;
|
||||
import com.android.systemui.util.animation.FloatProperties;
|
||||
import com.android.systemui.util.animation.PhysicsAnimator;
|
||||
import com.android.systemui.util.magnetictarget.MagnetizedObject;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.function.Consumer;
|
||||
@@ -61,9 +60,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
/** Friction to use for PIP when it moves via physics fling animations. */
|
||||
private static final float DEFAULT_FRICTION = 2f;
|
||||
|
||||
// The fraction of the stack height that the user has to drag offscreen to dismiss the PiP
|
||||
private static final float DISMISS_OFFSCREEN_FRACTION = 0.3f;
|
||||
|
||||
private final Context mContext;
|
||||
private final IActivityTaskManager mActivityTaskManager;
|
||||
private final PipTaskOrganizer mPipTaskOrganizer;
|
||||
@@ -103,7 +99,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
/**
|
||||
* Update listener that resizes the PIP to {@link #mAnimatedBounds}.
|
||||
*/
|
||||
private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener =
|
||||
final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener =
|
||||
(target, values) -> resizePipUnchecked(mAnimatedBounds);
|
||||
|
||||
/** FlingConfig instances provided to PhysicsAnimator for fling gestures. */
|
||||
@@ -122,6 +118,13 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
|
||||
private final Consumer<Rect> mUpdateBoundsCallback = mBounds::set;
|
||||
|
||||
/**
|
||||
* Whether we're springing to the touch event location (vs. moving it to that position
|
||||
* instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was
|
||||
* 'stuck' in the target and needs to catch up to the touch location.
|
||||
*/
|
||||
private boolean mSpringingToTouch = false;
|
||||
|
||||
public PipMotionHelper(Context context, IActivityTaskManager activityTaskManager,
|
||||
PipTaskOrganizer pipTaskOrganizer, PipMenuActivityController menuController,
|
||||
PipSnapAlgorithm snapAlgorithm, FlingAnimationUtils flingAnimationUtils,
|
||||
@@ -211,9 +214,35 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
mFloatingContentCoordinator.onContentMoved(this);
|
||||
}
|
||||
|
||||
cancelAnimations();
|
||||
resizePipUnchecked(toBounds);
|
||||
mBounds.set(toBounds);
|
||||
if (!mSpringingToTouch) {
|
||||
// If we are moving PIP directly to the touch event locations, cancel any animations and
|
||||
// move PIP to the given bounds.
|
||||
cancelAnimations();
|
||||
resizePipUnchecked(toBounds);
|
||||
mBounds.set(toBounds);
|
||||
} else {
|
||||
// If PIP is 'catching up' after being stuck in the dismiss target, update the animation
|
||||
// to spring towards the new touch location.
|
||||
mAnimatedBoundsPhysicsAnimator
|
||||
.spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig)
|
||||
.spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig)
|
||||
.withEndActions(() -> mSpringingToTouch = false);
|
||||
|
||||
startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */);
|
||||
}
|
||||
}
|
||||
|
||||
/** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */
|
||||
void setSpringingToTouch(boolean springingToTouch) {
|
||||
if (springingToTouch) {
|
||||
mAnimatedBounds.set(mBounds);
|
||||
}
|
||||
|
||||
mSpringingToTouch = springingToTouch;
|
||||
}
|
||||
|
||||
void prepareForAnimation() {
|
||||
mAnimatedBounds.set(mBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,25 +306,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
return mBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the PiP at the current bounds should be dismissed.
|
||||
*/
|
||||
boolean shouldDismissPip() {
|
||||
Point displaySize = new Point();
|
||||
mContext.getDisplay().getRealSize(displaySize);
|
||||
final int y = displaySize.y - mStableInsets.bottom;
|
||||
if (mBounds.bottom > y) {
|
||||
float offscreenFraction = (float) (mBounds.bottom - y) / mBounds.height();
|
||||
return offscreenFraction >= DISMISS_OFFSCREEN_FRACTION;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flings the PiP to the closest snap target.
|
||||
*/
|
||||
void flingToSnapTarget(
|
||||
float velocityX, float velocityY, Runnable updateAction, @Nullable Runnable endAction) {
|
||||
float velocityX, float velocityY,
|
||||
@Nullable Runnable updateAction, @Nullable Runnable endAction) {
|
||||
mAnimatedBounds.set(mBounds);
|
||||
mAnimatedBoundsPhysicsAnimator
|
||||
.flingThenSpring(
|
||||
@@ -303,9 +319,13 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
true /* flingMustReachMinOrMax */)
|
||||
.flingThenSpring(
|
||||
FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig)
|
||||
.addUpdateListener((target, values) -> updateAction.run())
|
||||
.withEndActions(endAction);
|
||||
|
||||
if (updateAction != null) {
|
||||
mAnimatedBoundsPhysicsAnimator.addUpdateListener(
|
||||
(target, values) -> updateAction.run());
|
||||
}
|
||||
|
||||
final float xEndValue = velocityX < 0 ? mMovementBounds.left : mMovementBounds.right;
|
||||
final float estimatedFlingYEndValue =
|
||||
PhysicsAnimator.estimateFlingEndValue(mBounds.top, velocityY, mFlingConfigY);
|
||||
@@ -338,16 +358,14 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
* Animates the dismissal of the PiP off the edge of the screen.
|
||||
*/
|
||||
void animateDismiss(float velocityX, float velocityY, @Nullable Runnable updateAction) {
|
||||
final float velocity = PointF.length(velocityX, velocityY);
|
||||
final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
|
||||
final Point dismissEndPoint = getDismissEndPoint(mBounds, velocityX, velocityY, isFling);
|
||||
|
||||
mAnimatedBounds.set(mBounds);
|
||||
|
||||
// Animate to the dismiss end point, and then dismiss PIP.
|
||||
// Animate off the bottom of the screen, then dismiss PIP.
|
||||
mAnimatedBoundsPhysicsAnimator
|
||||
.spring(FloatProperties.RECT_X, dismissEndPoint.x, velocityX, mSpringConfig)
|
||||
.spring(FloatProperties.RECT_Y, dismissEndPoint.y, velocityY, mSpringConfig)
|
||||
.spring(FloatProperties.RECT_Y,
|
||||
mBounds.bottom + mBounds.height(),
|
||||
velocityY,
|
||||
mSpringConfig)
|
||||
.withEndActions(this::dismissPip);
|
||||
|
||||
// If we were provided with an update action, run it whenever there's an update.
|
||||
@@ -356,7 +374,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
(target, values) -> updateAction.run());
|
||||
}
|
||||
|
||||
startBoundsAnimator(dismissEndPoint.x /* toX */, dismissEndPoint.y /* toY */);
|
||||
startBoundsAnimator(mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -408,6 +426,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
private void cancelAnimations() {
|
||||
mAnimatedBoundsPhysicsAnimator.cancel();
|
||||
mAnimatingToBounds.setEmpty();
|
||||
mSpringingToTouch = false;
|
||||
}
|
||||
|
||||
/** Set new fling configs whose min/max values respect the given movement bounds. */
|
||||
@@ -426,7 +445,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
* the 'real' bounds to equal the final animated bounds.
|
||||
*/
|
||||
private void startBoundsAnimator(float toX, float toY) {
|
||||
cancelAnimations();
|
||||
if (!mSpringingToTouch) {
|
||||
cancelAnimations();
|
||||
}
|
||||
|
||||
// Set animatingToBounds directly to avoid allocating a new Rect, but then call
|
||||
// setAnimatingToBounds to run the normal logic for changing animatingToBounds.
|
||||
@@ -484,47 +505,29 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the coordinates the PIP should animate to based on the direction of velocity when
|
||||
* dismissing.
|
||||
* Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the
|
||||
* magnetic dismiss target so it can calculate PIP's size and position.
|
||||
*/
|
||||
private Point getDismissEndPoint(Rect pipBounds, float velX, float velY, boolean isFling) {
|
||||
Point displaySize = new Point();
|
||||
mContext.getDisplay().getRealSize(displaySize);
|
||||
final float bottomBound = displaySize.y + pipBounds.height() * .1f;
|
||||
if (isFling && velX != 0 && velY != 0) {
|
||||
// Line is defined by: y = mx + b, m = slope, b = y-intercept
|
||||
// Find the slope
|
||||
final float slope = velY / velX;
|
||||
// Sub in slope and PiP position to solve for y-intercept: b = y - mx
|
||||
final float yIntercept = pipBounds.top - slope * pipBounds.left;
|
||||
// Now find the point on this line when y = bottom bound: x = (y - b) / m
|
||||
final float x = (bottomBound - yIntercept) / slope;
|
||||
return new Point((int) x, (int) bottomBound);
|
||||
} else {
|
||||
// If it wasn't a fling the velocity on 'up' is not reliable for direction of movement,
|
||||
// just animate downwards.
|
||||
return new Point(pipBounds.left, (int) bottomBound);
|
||||
}
|
||||
}
|
||||
MagnetizedObject<Rect> getMagnetizedPip() {
|
||||
return new MagnetizedObject<Rect>(
|
||||
mContext, mAnimatedBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) {
|
||||
@Override
|
||||
public float getWidth(@NonNull Rect animatedPipBounds) {
|
||||
return animatedPipBounds.width();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the gesture it towards the dismiss area based on the velocity when
|
||||
* dismissing.
|
||||
*/
|
||||
public boolean isGestureToDismissArea(Rect pipBounds, float velX, float velY,
|
||||
boolean isFling) {
|
||||
Point endpoint = getDismissEndPoint(pipBounds, velX, velY, isFling);
|
||||
// Center the point
|
||||
endpoint.x += pipBounds.width() / 2;
|
||||
endpoint.y += pipBounds.height() / 2;
|
||||
@Override
|
||||
public float getHeight(@NonNull Rect animatedPipBounds) {
|
||||
return animatedPipBounds.height();
|
||||
}
|
||||
|
||||
// The dismiss area is the middle third of the screen, half the PIP's height from the bottom
|
||||
Point size = new Point();
|
||||
mContext.getDisplay().getRealSize(size);
|
||||
final int left = size.x / 3;
|
||||
Rect dismissArea = new Rect(left, size.y - (pipBounds.height() / 2), left * 2,
|
||||
size.y + pipBounds.height());
|
||||
return dismissArea.contains(endpoint.x, endpoint.y);
|
||||
@Override
|
||||
public void getLocationOnScreen(
|
||||
@NonNull Rect animatedPipBounds, @NonNull int[] loc) {
|
||||
loc[0] = animatedPipBounds.left;
|
||||
loc[1] = animatedPipBounds.top;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw, String prefix) {
|
||||
|
||||
@@ -20,11 +20,13 @@ import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STAT
|
||||
import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
|
||||
import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.IActivityManager;
|
||||
import android.app.IActivityTaskManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
@@ -33,14 +35,23 @@ import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.Size;
|
||||
import android.view.Gravity;
|
||||
import android.view.IPinnedStackController;
|
||||
import android.view.InputEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.accessibility.AccessibilityWindowInfo;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.os.logging.MetricsLoggerWrapper;
|
||||
@@ -51,7 +62,10 @@ import com.android.systemui.pip.PipTaskOrganizer;
|
||||
import com.android.systemui.shared.system.InputConsumerController;
|
||||
import com.android.systemui.statusbar.FlingAnimationUtils;
|
||||
import com.android.systemui.util.DeviceConfigProxy;
|
||||
import com.android.systemui.util.DismissCircleView;
|
||||
import com.android.systemui.util.FloatingContentCoordinator;
|
||||
import com.android.systemui.util.animation.PhysicsAnimator;
|
||||
import com.android.systemui.util.magnetictarget.MagnetizedObject;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -62,9 +76,6 @@ import java.io.PrintWriter;
|
||||
public class PipTouchHandler {
|
||||
private static final String TAG = "PipTouchHandler";
|
||||
|
||||
// Allow the PIP to be flung from anywhere on the screen to the bottom to be dismissed.
|
||||
private static final boolean ENABLE_FLING_DISMISS = false;
|
||||
|
||||
private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 225;
|
||||
private static final int BOTTOM_OFFSET_BUFFER_DP = 1;
|
||||
|
||||
@@ -73,17 +84,45 @@ public class PipTouchHandler {
|
||||
// Allow PIP to resize to a slightly bigger state upon touch
|
||||
private final boolean mEnableResize;
|
||||
private final Context mContext;
|
||||
private final WindowManager mWindowManager;
|
||||
private final IActivityManager mActivityManager;
|
||||
private final PipBoundsHandler mPipBoundsHandler;
|
||||
private PipResizeGestureHandler mPipResizeGestureHandler;
|
||||
private IPinnedStackController mPinnedStackController;
|
||||
|
||||
private final PipMenuActivityController mMenuController;
|
||||
private final PipDismissViewController mDismissViewController;
|
||||
private final PipSnapAlgorithm mSnapAlgorithm;
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
private boolean mShowPipMenuOnAnimationEnd = false;
|
||||
|
||||
/**
|
||||
* MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
|
||||
* PIP.
|
||||
*/
|
||||
private 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 ViewGroup mTargetViewContainer;
|
||||
|
||||
/** Circle view used to render the dismiss target. */
|
||||
private DismissCircleView mTargetView;
|
||||
|
||||
/**
|
||||
* MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
|
||||
*/
|
||||
private MagnetizedObject.MagneticTarget mMagneticTarget;
|
||||
|
||||
/** PhysicsAnimator instance for animating the dismiss target in/out. */
|
||||
private PhysicsAnimator<View> mMagneticTargetAnimator;
|
||||
|
||||
/** Default configuration to use for springing the dismiss target in/out. */
|
||||
private final PhysicsAnimator.SpringConfig mTargetSpringConfig =
|
||||
new PhysicsAnimator.SpringConfig(
|
||||
SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_NO_BOUNCY);
|
||||
|
||||
// The current movement bounds
|
||||
private Rect mMovementBounds = new Rect();
|
||||
// The current resized bounds, changed by user resize.
|
||||
@@ -104,21 +143,20 @@ public class PipTouchHandler {
|
||||
private int mDeferResizeToNormalBoundsUntilRotation = -1;
|
||||
private int mDisplayRotation;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
private Handler mHandler = new Handler();
|
||||
private Runnable mShowDismissAffordance = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mEnableDismissDragToEdge) {
|
||||
mDismissViewController.showDismissTarget();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Behaviour states
|
||||
private int mMenuState = MENU_STATE_NONE;
|
||||
private boolean mIsImeShowing;
|
||||
private int mImeHeight;
|
||||
private int mImeOffset;
|
||||
private int mDismissAreaHeight;
|
||||
private boolean mIsShelfShowing;
|
||||
private int mShelfHeight;
|
||||
private int mMovementBoundsExtraOffsets;
|
||||
@@ -168,6 +206,7 @@ public class PipTouchHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
public PipTouchHandler(Context context, IActivityManager activityManager,
|
||||
IActivityTaskManager activityTaskManager, PipMenuActivityController menuController,
|
||||
InputConsumerController inputConsumerController,
|
||||
@@ -180,9 +219,9 @@ public class PipTouchHandler {
|
||||
mContext = context;
|
||||
mActivityManager = activityManager;
|
||||
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
|
||||
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
|
||||
mMenuController = menuController;
|
||||
mMenuController.addListener(new PipMenuListener());
|
||||
mDismissViewController = new PipDismissViewController(context);
|
||||
mSnapAlgorithm = pipSnapAlgorithm;
|
||||
mFlingAnimationUtils = new FlingAnimationUtils(context.getResources().getDisplayMetrics(),
|
||||
2.5f);
|
||||
@@ -200,6 +239,7 @@ public class PipTouchHandler {
|
||||
mExpandedShortestEdgeSize = res.getDimensionPixelSize(
|
||||
R.dimen.pip_expanded_shortest_edge_size);
|
||||
mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
|
||||
mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
|
||||
|
||||
mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
|
||||
mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
|
||||
@@ -212,6 +252,56 @@ public class PipTouchHandler {
|
||||
mFloatingContentCoordinator = floatingContentCoordinator;
|
||||
mConnection = new PipAccessibilityInteractionConnection(mMotionHelper,
|
||||
this::onAccessibilityShowMenu, mHandler);
|
||||
|
||||
final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
|
||||
mTargetView = new DismissCircleView(context);
|
||||
final FrameLayout.LayoutParams newParams =
|
||||
new FrameLayout.LayoutParams(targetSize, targetSize);
|
||||
newParams.gravity = Gravity.CENTER;
|
||||
mTargetView.setLayoutParams(newParams);
|
||||
|
||||
mTargetViewContainer = new FrameLayout(context);
|
||||
mTargetViewContainer.setClipChildren(false);
|
||||
mTargetViewContainer.addView(mTargetView);
|
||||
|
||||
mMagnetizedPip = mMotionHelper.getMagnetizedPip();
|
||||
mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
|
||||
mMagnetizedPip.setPhysicsAnimatorUpdateListener(mMotionHelper.mResizePipUpdateListener);
|
||||
mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
|
||||
@Override
|
||||
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
mMotionHelper.prepareForAnimation();
|
||||
|
||||
// Show the dismiss target, in case the initial touch event occurred within the
|
||||
// magnetic field radius.
|
||||
showDismissTargetMaybe();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
|
||||
float velX, float velY, boolean wasFlungOut) {
|
||||
if (wasFlungOut) {
|
||||
mMotionHelper.flingToSnapTarget(velX, velY, null, null);
|
||||
hideDismissTarget();
|
||||
} else {
|
||||
mMotionHelper.setSpringingToTouch(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
mHandler.post(() -> {
|
||||
mMotionHelper.animateDismiss(0, 0, null);
|
||||
hideDismissTarget();
|
||||
});
|
||||
|
||||
|
||||
MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext,
|
||||
PipUtils.getTopPipActivity(mContext, mActivityManager));
|
||||
}
|
||||
});
|
||||
|
||||
mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
|
||||
}
|
||||
|
||||
public void setTouchGesture(PipTouchGesture gesture) {
|
||||
@@ -231,7 +321,8 @@ public class PipTouchHandler {
|
||||
}
|
||||
|
||||
public void onActivityPinned() {
|
||||
cleanUpDismissTarget();
|
||||
createDismissTargetMaybe();
|
||||
|
||||
mShowPipMenuOnAnimationEnd = true;
|
||||
mPipResizeGestureHandler.onActivityPinned();
|
||||
mFloatingContentCoordinator.onContentAdded(mMotionHelper);
|
||||
@@ -264,6 +355,10 @@ public class PipTouchHandler {
|
||||
public void onConfigurationChanged() {
|
||||
mMotionHelper.onConfigurationChanged();
|
||||
mMotionHelper.synchronizePinnedStackBounds();
|
||||
|
||||
// Recreate the dismiss target for the new orientation.
|
||||
cleanUpDismissTarget();
|
||||
createDismissTargetMaybe();
|
||||
}
|
||||
|
||||
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
|
||||
@@ -351,6 +446,74 @@ public class PipTouchHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
|
||||
private void createDismissTargetMaybe() {
|
||||
if (!mTargetViewContainer.isAttachedToWindow()) {
|
||||
mHandler.removeCallbacks(mShowTargetAction);
|
||||
mMagneticTargetAnimator.cancel();
|
||||
|
||||
final Point windowSize = new Point();
|
||||
mWindowManager.getDefaultDisplay().getRealSize(windowSize);
|
||||
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 */);
|
||||
|
||||
mTargetViewContainer.setVisibility(View.INVISIBLE);
|
||||
mWindowManager.addView(mTargetViewContainer, lp);
|
||||
}
|
||||
}
|
||||
|
||||
/** Makes the dismiss target visible and animates it in, if it isn't already visible. */
|
||||
private void showDismissTargetMaybe() {
|
||||
createDismissTargetMaybe();
|
||||
|
||||
if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
|
||||
|
||||
mTargetView.setTranslationY(mTargetViewContainer.getHeight());
|
||||
mTargetViewContainer.setVisibility(View.VISIBLE);
|
||||
|
||||
// Set the magnetic field radius to half of PIP's width.
|
||||
mMagneticTarget.setMagneticFieldRadiusPx(mMotionHelper.getBounds().width());
|
||||
|
||||
// Cancel in case we were in the middle of animating it out.
|
||||
mMagneticTargetAnimator.cancel();
|
||||
mMagneticTargetAnimator
|
||||
.spring(DynamicAnimation.TRANSLATION_Y, 0f, mTargetSpringConfig)
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
/** Animates the magnetic dismiss target out and then sets it to GONE. */
|
||||
private void hideDismissTarget() {
|
||||
mHandler.removeCallbacks(mShowTargetAction);
|
||||
mMagneticTargetAnimator
|
||||
.spring(DynamicAnimation.TRANSLATION_Y,
|
||||
mTargetViewContainer.getHeight(),
|
||||
mTargetSpringConfig)
|
||||
.withEndActions(() -> mTargetViewContainer.setVisibility(View.GONE))
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the dismiss target and cancels any pending callbacks to show it.
|
||||
*/
|
||||
private void cleanUpDismissTarget() {
|
||||
mHandler.removeCallbacks(mShowTargetAction);
|
||||
|
||||
if (mTargetViewContainer.isAttachedToWindow()) {
|
||||
mWindowManager.removeView(mTargetViewContainer);
|
||||
}
|
||||
}
|
||||
|
||||
private void onRegistrationChanged(boolean isRegistered) {
|
||||
mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered
|
||||
? mConnection : null);
|
||||
@@ -375,8 +538,24 @@ public class PipTouchHandler {
|
||||
if (mPinnedStackController == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MotionEvent ev = (MotionEvent) inputEvent;
|
||||
|
||||
if (mMagnetizedPip.maybeConsumeMotionEvent(ev)) {
|
||||
// If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
|
||||
// to the touch state. Touch state needs a DOWN event in order to later process MOVE
|
||||
// events it'll receive if the object is dragged out of the magnetic field.
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
mTouchState.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
// Continue tracking velocity when the object is in the magnetic field, since we want to
|
||||
// respect touch input velocity if the object is dragged out and then flung.
|
||||
mTouchState.addMovementToVelocityTracker(ev);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Update the touch state
|
||||
mTouchState.onTouchEvent(ev);
|
||||
|
||||
@@ -600,17 +779,13 @@ public class PipTouchHandler {
|
||||
mDelta.set(0f, 0f);
|
||||
mStartPosition.set(bounds.left, bounds.top);
|
||||
mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
|
||||
mMotionHelper.setSpringingToTouch(false);
|
||||
|
||||
// If the menu is still visible then just poke the menu
|
||||
// so that it will timeout after the user stops touching it
|
||||
if (mMenuState != MENU_STATE_NONE) {
|
||||
mMenuController.pokeMenu();
|
||||
}
|
||||
|
||||
if (mEnableDismissDragToEdge) {
|
||||
mDismissViewController.createDismissTarget();
|
||||
mHandler.postDelayed(mShowDismissAffordance, SHOW_DISMISS_AFFORDANCE_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -623,8 +798,10 @@ public class PipTouchHandler {
|
||||
mSavedSnapFraction = -1f;
|
||||
|
||||
if (mEnableDismissDragToEdge) {
|
||||
mHandler.removeCallbacks(mShowDismissAffordance);
|
||||
mDismissViewController.showDismissTarget();
|
||||
if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
|
||||
mHandler.removeCallbacks(mShowTargetAction);
|
||||
showDismissTargetMaybe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,10 +821,6 @@ public class PipTouchHandler {
|
||||
mTmpBounds.offsetTo((int) left, (int) top);
|
||||
mMotionHelper.movePip(mTmpBounds, true /* isDragging */);
|
||||
|
||||
if (mEnableDismissDragToEdge) {
|
||||
updateDismissFraction();
|
||||
}
|
||||
|
||||
final PointF curPos = touchState.getLastTouchPosition();
|
||||
if (mMovementWithinDismiss) {
|
||||
// Track if movement remains near the bottom edge to identify swipe to dismiss
|
||||
@@ -661,9 +834,7 @@ public class PipTouchHandler {
|
||||
@Override
|
||||
public boolean onUp(PipTouchState touchState) {
|
||||
if (mEnableDismissDragToEdge) {
|
||||
// Clean up the dismiss target regardless of the touch state in case the touch
|
||||
// enabled state changes while the user is interacting
|
||||
cleanUpDismissTarget();
|
||||
hideDismissTarget();
|
||||
}
|
||||
|
||||
if (!touchState.isUserInteracting()) {
|
||||
@@ -671,26 +842,8 @@ public class PipTouchHandler {
|
||||
}
|
||||
|
||||
final PointF vel = touchState.getVelocity();
|
||||
final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y);
|
||||
final float velocity = PointF.length(vel.x, vel.y);
|
||||
final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
|
||||
final boolean isUpWithinDimiss = ENABLE_FLING_DISMISS
|
||||
&& touchState.getLastTouchPosition().y >= mMovementBounds.bottom
|
||||
&& mMotionHelper.isGestureToDismissArea(mMotionHelper.getBounds(), vel.x,
|
||||
vel.y, isFling);
|
||||
final boolean isFlingToBot = isFling && vel.y > 0 && !isHorizontal
|
||||
&& (mMovementWithinDismiss || isUpWithinDimiss);
|
||||
if (mEnableDismissDragToEdge) {
|
||||
// Check if the user dragged or flung the PiP offscreen to dismiss it
|
||||
if (mMotionHelper.shouldDismissPip() || isFlingToBot) {
|
||||
MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext,
|
||||
PipUtils.getTopPipActivity(mContext, mActivityManager));
|
||||
mMotionHelper.animateDismiss(
|
||||
vel.x, vel.y,
|
||||
PipTouchHandler.this::updateDismissFraction /* updateAction */);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (touchState.isDragging()) {
|
||||
Runnable endAction = null;
|
||||
@@ -748,14 +901,6 @@ public class PipTouchHandler {
|
||||
isMenuExpanded && willResizeMenu() ? mExpandedShortestEdgeSize : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the dismiss target and cancels any pending callbacks to show it.
|
||||
*/
|
||||
private void cleanUpDismissTarget() {
|
||||
mHandler.removeCallbacks(mShowDismissAffordance);
|
||||
mDismissViewController.destroyDismissTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the menu will resize as a part of showing the full menu.
|
||||
*/
|
||||
|
||||
@@ -92,7 +92,7 @@ public class PipTouchState {
|
||||
|
||||
// Initialize the velocity tracker
|
||||
initOrResetVelocityTracker();
|
||||
addMovement(ev);
|
||||
addMovementToVelocityTracker(ev);
|
||||
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
if (DEBUG) {
|
||||
@@ -120,7 +120,7 @@ public class PipTouchState {
|
||||
}
|
||||
|
||||
// Update the velocity tracker
|
||||
addMovement(ev);
|
||||
addMovementToVelocityTracker(ev);
|
||||
int pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
if (pointerIndex == -1) {
|
||||
Log.e(TAG, "Invalid active pointer id on MOVE: " + mActivePointerId);
|
||||
@@ -151,7 +151,7 @@ public class PipTouchState {
|
||||
}
|
||||
|
||||
// Update the velocity tracker
|
||||
addMovement(ev);
|
||||
addMovementToVelocityTracker(ev);
|
||||
|
||||
int pointerIndex = ev.getActionIndex();
|
||||
int pointerId = ev.getPointerId(pointerIndex);
|
||||
@@ -174,7 +174,7 @@ public class PipTouchState {
|
||||
}
|
||||
|
||||
// Update the velocity tracker
|
||||
addMovement(ev);
|
||||
addMovementToVelocityTracker(ev);
|
||||
mVelocityTracker.computeCurrentVelocity(1000,
|
||||
mViewConfig.getScaledMaximumFlingVelocity());
|
||||
mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
|
||||
@@ -318,6 +318,20 @@ public class PipTouchState {
|
||||
return -1;
|
||||
}
|
||||
|
||||
void addMovementToVelocityTracker(MotionEvent event) {
|
||||
if (mVelocityTracker == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add movement to velocity tracker using raw screen X and Y coordinates instead
|
||||
// of window coordinates because the window frame may be moving at the same time.
|
||||
float deltaX = event.getRawX() - event.getX();
|
||||
float deltaY = event.getRawY() - event.getY();
|
||||
event.offsetLocation(deltaX, deltaY);
|
||||
mVelocityTracker.addMovement(event);
|
||||
event.offsetLocation(-deltaX, -deltaY);
|
||||
}
|
||||
|
||||
private void initOrResetVelocityTracker() {
|
||||
if (mVelocityTracker == null) {
|
||||
mVelocityTracker = VelocityTracker.obtain();
|
||||
@@ -333,16 +347,6 @@ public class PipTouchState {
|
||||
}
|
||||
}
|
||||
|
||||
private void addMovement(MotionEvent event) {
|
||||
// Add movement to velocity tracker using raw screen X and Y coordinates instead
|
||||
// of window coordinates because the window frame may be moving at the same time.
|
||||
float deltaX = event.getRawX() - event.getX();
|
||||
float deltaY = event.getRawY() - event.getY();
|
||||
event.offsetLocation(deltaX, deltaY);
|
||||
mVelocityTracker.addMovement(event);
|
||||
event.offsetLocation(-deltaX, -deltaY);
|
||||
}
|
||||
|
||||
public void dump(PrintWriter pw, String prefix) {
|
||||
final String innerPrefix = prefix + " ";
|
||||
pw.println(prefix + TAG);
|
||||
|
||||
@@ -159,6 +159,18 @@ abstract class MagnetizedObject<T : Any>(
|
||||
*/
|
||||
lateinit var magnetListener: MagnetizedObject.MagnetListener
|
||||
|
||||
/**
|
||||
* Optional update listener to provide to the PhysicsAnimator that is used to spring the object
|
||||
* into the target.
|
||||
*/
|
||||
var physicsAnimatorUpdateListener: PhysicsAnimator.UpdateListener<T>? = null
|
||||
|
||||
/**
|
||||
* Optional end listener to provide to the PhysicsAnimator that is used to spring the object
|
||||
* into the target.
|
||||
*/
|
||||
var physicsAnimatorEndListener: PhysicsAnimator.EndListener<T>? = null
|
||||
|
||||
/**
|
||||
* Sets whether forcefully flinging the object vertically towards a target causes it to be
|
||||
* attracted to the target and then released immediately, despite never being dragged within the
|
||||
@@ -479,6 +491,14 @@ abstract class MagnetizedObject<T : Any>(
|
||||
.spring(yProperty, yProperty.getValue(underlyingObject) + yDiff, velY,
|
||||
springConfig)
|
||||
|
||||
if (physicsAnimatorUpdateListener != null) {
|
||||
animator.addUpdateListener(physicsAnimatorUpdateListener!!)
|
||||
}
|
||||
|
||||
if (physicsAnimatorEndListener != null) {
|
||||
animator.addEndListener(physicsAnimatorEndListener!!)
|
||||
}
|
||||
|
||||
if (after != null) {
|
||||
animator.withEndActions(after)
|
||||
}
|
||||
@@ -560,13 +580,15 @@ abstract class MagnetizedObject<T : Any>(
|
||||
private val tempLoc = IntArray(2)
|
||||
|
||||
fun updateLocationOnScreen() {
|
||||
targetView.getLocationOnScreen(tempLoc)
|
||||
targetView.post {
|
||||
targetView.getLocationOnScreen(tempLoc)
|
||||
|
||||
// Add half of the target size to get the center, and subtract translation since the
|
||||
// target could be animating in while we're doing this calculation.
|
||||
centerOnScreen.set(
|
||||
tempLoc[0] + targetView.width / 2f - targetView.translationX,
|
||||
tempLoc[1] + targetView.height / 2f - targetView.translationY)
|
||||
// Add half of the target size to get the center, and subtract translation since the
|
||||
// target could be animating in while we're doing this calculation.
|
||||
centerOnScreen.set(
|
||||
tempLoc[0] + targetView.width / 2f - targetView.translationX,
|
||||
tempLoc[1] + targetView.height / 2f - targetView.translationY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,10 @@ class MagnetizedObjectTest : SysuiTestCase() {
|
||||
location[1] = targetCenterY - targetSize / 2 // y = 800
|
||||
}
|
||||
}.`when`(targetView).getLocationOnScreen(ArgumentMatchers.any())
|
||||
doAnswer { invocation ->
|
||||
(invocation.arguments[0] as Runnable).run()
|
||||
true
|
||||
}.`when`(targetView).post(ArgumentMatchers.any())
|
||||
`when`(targetView.context).thenReturn(context)
|
||||
|
||||
magneticTarget = MagnetizedObject.MagneticTarget(targetView, magneticFieldRadius)
|
||||
@@ -407,6 +411,10 @@ class MagnetizedObjectTest : SysuiTestCase() {
|
||||
`when`(secondTargetView.context).thenReturn(context)
|
||||
`when`(secondTargetView.width).thenReturn(targetSize) // width = 200
|
||||
`when`(secondTargetView.height).thenReturn(targetSize) // height = 200
|
||||
doAnswer { invocation ->
|
||||
(invocation.arguments[0] as Runnable).run()
|
||||
true
|
||||
}.`when`(secondTargetView).post(ArgumentMatchers.any())
|
||||
doAnswer { invocation ->
|
||||
(invocation.arguments[0] as IntArray).also { location ->
|
||||
// Return the top left of the target.
|
||||
|
||||
Reference in New Issue
Block a user