Merge "Animation for bubbles home gesture" into tm-qpr-dev
This commit is contained in:
@@ -52,6 +52,19 @@ public class Interpolators {
|
|||||||
*/
|
*/
|
||||||
public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
|
public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
|
||||||
|
* is disappearing e.g. when moving off screen.
|
||||||
|
*/
|
||||||
|
public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
|
||||||
|
0.3f, 0f, 0.8f, 0.15f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
|
||||||
|
* is appearing e.g. when coming from off screen
|
||||||
|
*/
|
||||||
|
public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
|
||||||
|
0.05f, 0.7f, 0.1f, 1f);
|
||||||
/**
|
/**
|
||||||
* Interpolator to be used when animating a move based on a click. Pair with enough duration.
|
* Interpolator to be used when animating a move based on a click. Pair with enough duration.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1512,7 +1512,7 @@ public class BubbleController {
|
|||||||
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
|
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
|
||||||
mBubblePositioner.setImeVisible(imeVisible, imeHeight);
|
mBubblePositioner.setImeVisible(imeVisible, imeHeight);
|
||||||
if (mStackView != null) {
|
if (mStackView != null) {
|
||||||
mStackView.animateForIme(imeVisible);
|
mStackView.setImeVisible(imeVisible);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ public class BubbleDebugConfig {
|
|||||||
static final boolean DEBUG_OVERFLOW = false;
|
static final boolean DEBUG_OVERFLOW = false;
|
||||||
static final boolean DEBUG_USER_EDUCATION = false;
|
static final boolean DEBUG_USER_EDUCATION = false;
|
||||||
static final boolean DEBUG_POSITIONER = false;
|
static final boolean DEBUG_POSITIONER = false;
|
||||||
|
public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
|
||||||
|
static final boolean DEBUG_BUBBLE_GESTURE = false;
|
||||||
|
public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;
|
||||||
|
|
||||||
private static final boolean FORCE_SHOW_USER_EDUCATION = false;
|
private static final boolean FORCE_SHOW_USER_EDUCATION = false;
|
||||||
private static final String FORCE_SHOW_USER_EDUCATION_SETTING =
|
private static final String FORCE_SHOW_USER_EDUCATION_SETTING =
|
||||||
|
|||||||
@@ -43,10 +43,13 @@ import android.graphics.CornerPathEffect;
|
|||||||
import android.graphics.Outline;
|
import android.graphics.Outline;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Picture;
|
import android.graphics.Picture;
|
||||||
|
import android.graphics.PointF;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.ShapeDrawable;
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.FloatProperty;
|
||||||
|
import android.util.IntProperty;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -75,6 +78,48 @@ import java.io.PrintWriter;
|
|||||||
public class BubbleExpandedView extends LinearLayout {
|
public class BubbleExpandedView extends LinearLayout {
|
||||||
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
|
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
|
||||||
|
|
||||||
|
/** {@link IntProperty} for updating bottom clip */
|
||||||
|
public static final IntProperty<BubbleExpandedView> BOTTOM_CLIP_PROPERTY =
|
||||||
|
new IntProperty<BubbleExpandedView>("bottomClip") {
|
||||||
|
@Override
|
||||||
|
public void setValue(BubbleExpandedView expandedView, int value) {
|
||||||
|
expandedView.setBottomClip(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer get(BubbleExpandedView expandedView) {
|
||||||
|
return expandedView.mBottomClip;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** {@link FloatProperty} for updating taskView or overflow alpha */
|
||||||
|
public static final FloatProperty<BubbleExpandedView> CONTENT_ALPHA =
|
||||||
|
new FloatProperty<BubbleExpandedView>("contentAlpha") {
|
||||||
|
@Override
|
||||||
|
public void setValue(BubbleExpandedView expandedView, float value) {
|
||||||
|
expandedView.setContentAlpha(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(BubbleExpandedView expandedView) {
|
||||||
|
return expandedView.getContentAlpha();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** {@link FloatProperty} for updating manage button alpha */
|
||||||
|
public static final FloatProperty<BubbleExpandedView> MANAGE_BUTTON_ALPHA =
|
||||||
|
new FloatProperty<BubbleExpandedView>("manageButtonAlpha") {
|
||||||
|
@Override
|
||||||
|
public void setValue(BubbleExpandedView expandedView, float value) {
|
||||||
|
expandedView.mManageButton.setAlpha(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float get(BubbleExpandedView expandedView) {
|
||||||
|
return expandedView.mManageButton.getAlpha();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// The triangle pointing to the expanded view
|
// The triangle pointing to the expanded view
|
||||||
private View mPointerView;
|
private View mPointerView;
|
||||||
@Nullable private int[] mExpandedViewContainerLocation;
|
@Nullable private int[] mExpandedViewContainerLocation;
|
||||||
@@ -90,7 +135,7 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether we want the {@code TaskView}'s content to be visible (alpha = 1f). If
|
* Whether we want the {@code TaskView}'s content to be visible (alpha = 1f). If
|
||||||
* {@link #mIsAlphaAnimating} is true, this may not reflect the {@code TaskView}'s actual alpha
|
* {@link #mIsAnimating} is true, this may not reflect the {@code TaskView}'s actual alpha
|
||||||
* value until the animation ends.
|
* value until the animation ends.
|
||||||
*/
|
*/
|
||||||
private boolean mIsContentVisible = false;
|
private boolean mIsContentVisible = false;
|
||||||
@@ -99,12 +144,13 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
* Whether we're animating the {@code TaskView}'s alpha value. If so, we will hold off on
|
* Whether we're animating the {@code TaskView}'s alpha value. If so, we will hold off on
|
||||||
* applying alpha changes from {@link #setContentVisibility} until the animation ends.
|
* applying alpha changes from {@link #setContentVisibility} until the animation ends.
|
||||||
*/
|
*/
|
||||||
private boolean mIsAlphaAnimating = false;
|
private boolean mIsAnimating = false;
|
||||||
|
|
||||||
private int mPointerWidth;
|
private int mPointerWidth;
|
||||||
private int mPointerHeight;
|
private int mPointerHeight;
|
||||||
private float mPointerRadius;
|
private float mPointerRadius;
|
||||||
private float mPointerOverlap;
|
private float mPointerOverlap;
|
||||||
|
private final PointF mPointerPos = new PointF();
|
||||||
private CornerPathEffect mPointerEffect;
|
private CornerPathEffect mPointerEffect;
|
||||||
private ShapeDrawable mCurrentPointer;
|
private ShapeDrawable mCurrentPointer;
|
||||||
private ShapeDrawable mTopPointer;
|
private ShapeDrawable mTopPointer;
|
||||||
@@ -113,11 +159,13 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
private float mCornerRadius = 0f;
|
private float mCornerRadius = 0f;
|
||||||
private int mBackgroundColorFloating;
|
private int mBackgroundColorFloating;
|
||||||
private boolean mUsingMaxHeight;
|
private boolean mUsingMaxHeight;
|
||||||
|
private int mTopClip = 0;
|
||||||
|
private int mBottomClip = 0;
|
||||||
@Nullable private Bubble mBubble;
|
@Nullable private Bubble mBubble;
|
||||||
private PendingIntent mPendingIntent;
|
private PendingIntent mPendingIntent;
|
||||||
// TODO(b/170891664): Don't use a flag, set the BubbleOverflow object instead
|
// TODO(b/170891664): Don't use a flag, set the BubbleOverflow object instead
|
||||||
private boolean mIsOverflow;
|
private boolean mIsOverflow;
|
||||||
|
private boolean mIsClipping;
|
||||||
|
|
||||||
private BubbleController mController;
|
private BubbleController mController;
|
||||||
private BubbleStackView mStackView;
|
private BubbleStackView mStackView;
|
||||||
@@ -268,7 +316,8 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
mExpandedViewContainer.setOutlineProvider(new ViewOutlineProvider() {
|
mExpandedViewContainer.setOutlineProvider(new ViewOutlineProvider() {
|
||||||
@Override
|
@Override
|
||||||
public void getOutline(View view, Outline outline) {
|
public void getOutline(View view, Outline outline) {
|
||||||
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
|
Rect clip = new Rect(0, mTopClip, view.getWidth(), view.getHeight() - mBottomClip);
|
||||||
|
outline.setRoundRect(clip, mCornerRadius);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mExpandedViewContainer.setClipToOutline(true);
|
mExpandedViewContainer.setClipToOutline(true);
|
||||||
@@ -429,7 +478,7 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
* ordering surfaces during animations. When content is drawn on top of the app (e.g. bubble
|
* ordering surfaces during animations. When content is drawn on top of the app (e.g. bubble
|
||||||
* being dragged out, the manage menu) this is set to false, otherwise it should be true.
|
* being dragged out, the manage menu) this is set to false, otherwise it should be true.
|
||||||
*/
|
*/
|
||||||
void setSurfaceZOrderedOnTop(boolean onTop) {
|
public void setSurfaceZOrderedOnTop(boolean onTop) {
|
||||||
if (mTaskView == null) {
|
if (mTaskView == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -510,12 +559,12 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether we are currently animating the {@code TaskView}'s alpha value. If this is set to
|
* Whether we are currently animating the {@code TaskView}. If this is set to
|
||||||
* true, calls to {@link #setContentVisibility} will not be applied until this is set to false
|
* true, calls to {@link #setContentVisibility} will not be applied until this is set to false
|
||||||
* again.
|
* again.
|
||||||
*/
|
*/
|
||||||
void setAlphaAnimating(boolean animating) {
|
public void setAnimating(boolean animating) {
|
||||||
mIsAlphaAnimating = animating;
|
mIsAnimating = animating;
|
||||||
|
|
||||||
// If we're done animating, apply the correct
|
// If we're done animating, apply the correct
|
||||||
if (!animating) {
|
if (!animating) {
|
||||||
@@ -524,15 +573,128 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the alpha of the underlying {@code TaskView}, since changing the expanded view's alpha
|
* Get alpha from underlying {@code TaskView} if this view is for a bubble.
|
||||||
* does not affect the {@code TaskView} since it uses a Surface.
|
* Or get alpha for the overflow view if this view is for overflow.
|
||||||
|
*
|
||||||
|
* @return alpha for the content being shown
|
||||||
*/
|
*/
|
||||||
void setTaskViewAlpha(float alpha) {
|
public float getContentAlpha() {
|
||||||
|
if (mIsOverflow) {
|
||||||
|
return mOverflowView.getAlpha();
|
||||||
|
}
|
||||||
if (mTaskView != null) {
|
if (mTaskView != null) {
|
||||||
|
return mTaskView.getAlpha();
|
||||||
|
}
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set alpha of the underlying {@code TaskView} if this view is for a bubble.
|
||||||
|
* Or set alpha for the overflow view if this view is for overflow.
|
||||||
|
*
|
||||||
|
* Changing expanded view's alpha does not affect the {@code TaskView} since it uses a Surface.
|
||||||
|
*/
|
||||||
|
public void setContentAlpha(float alpha) {
|
||||||
|
if (mIsOverflow) {
|
||||||
|
mOverflowView.setAlpha(alpha);
|
||||||
|
} else if (mTaskView != null) {
|
||||||
mTaskView.setAlpha(alpha);
|
mTaskView.setAlpha(alpha);
|
||||||
}
|
}
|
||||||
mPointerView.setAlpha(alpha);
|
}
|
||||||
setAlpha(alpha);
|
|
||||||
|
/**
|
||||||
|
* Set translation Y for the expanded view content.
|
||||||
|
* Excludes manage button and pointer.
|
||||||
|
*/
|
||||||
|
public void setContentTranslationY(float translationY) {
|
||||||
|
mExpandedViewContainer.setTranslationY(translationY);
|
||||||
|
|
||||||
|
// Left or right pointer can become detached when moving the view up
|
||||||
|
if (translationY <= 0 && (isShowingLeftPointer() || isShowingRightPointer())) {
|
||||||
|
// Y coordinate where the pointer would start to get detached from the expanded view.
|
||||||
|
// Takes into account bottom clipping and rounded corners
|
||||||
|
float detachPoint =
|
||||||
|
mExpandedViewContainer.getBottom() - mBottomClip - mCornerRadius + translationY;
|
||||||
|
float pointerBottom = mPointerPos.y + mPointerHeight;
|
||||||
|
// If pointer bottom is past detach point, move it in by that many pixels
|
||||||
|
float horizontalShift = 0;
|
||||||
|
if (pointerBottom > detachPoint) {
|
||||||
|
horizontalShift = pointerBottom - detachPoint;
|
||||||
|
}
|
||||||
|
if (isShowingLeftPointer()) {
|
||||||
|
// Move left pointer right
|
||||||
|
movePointerBy(horizontalShift, 0);
|
||||||
|
} else {
|
||||||
|
// Move right pointer left
|
||||||
|
movePointerBy(-horizontalShift, 0);
|
||||||
|
}
|
||||||
|
// Hide pointer if it is moved by entire width
|
||||||
|
mPointerView.setVisibility(
|
||||||
|
horizontalShift > mPointerWidth ? View.INVISIBLE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update alpha value for the manage button
|
||||||
|
*/
|
||||||
|
public void setManageButtonAlpha(float alpha) {
|
||||||
|
mManageButton.setAlpha(alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set {@link #setTranslationY(float) translationY} for the manage button
|
||||||
|
*/
|
||||||
|
public void setManageButtonTranslationY(float translationY) {
|
||||||
|
mManageButton.setTranslationY(translationY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set top clipping for the view
|
||||||
|
*/
|
||||||
|
public void setTopClip(int clip) {
|
||||||
|
mTopClip = clip;
|
||||||
|
onContainerClipUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set bottom clipping for the view
|
||||||
|
*/
|
||||||
|
public void setBottomClip(int clip) {
|
||||||
|
mBottomClip = clip;
|
||||||
|
onContainerClipUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onContainerClipUpdate() {
|
||||||
|
if (mTopClip == 0 && mBottomClip == 0) {
|
||||||
|
if (mIsClipping) {
|
||||||
|
mIsClipping = false;
|
||||||
|
if (mTaskView != null) {
|
||||||
|
mTaskView.setClipBounds(null);
|
||||||
|
mTaskView.setEnableSurfaceClipping(false);
|
||||||
|
}
|
||||||
|
mExpandedViewContainer.invalidateOutline();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!mIsClipping) {
|
||||||
|
mIsClipping = true;
|
||||||
|
if (mTaskView != null) {
|
||||||
|
mTaskView.setEnableSurfaceClipping(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mExpandedViewContainer.invalidateOutline();
|
||||||
|
if (mTaskView != null) {
|
||||||
|
mTaskView.setClipBounds(new Rect(0, mTopClip, mTaskView.getWidth(),
|
||||||
|
mTaskView.getHeight() - mBottomClip));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move pointer from base position
|
||||||
|
*/
|
||||||
|
public void movePointerBy(float x, float y) {
|
||||||
|
mPointerView.setTranslationX(mPointerPos.x + x);
|
||||||
|
mPointerView.setTranslationY(mPointerPos.y + y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -543,13 +705,13 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
* Note that this contents visibility doesn't affect visibility at {@link android.view.View},
|
* Note that this contents visibility doesn't affect visibility at {@link android.view.View},
|
||||||
* and setting {@code false} actually means rendering the contents in transparent.
|
* and setting {@code false} actually means rendering the contents in transparent.
|
||||||
*/
|
*/
|
||||||
void setContentVisibility(boolean visibility) {
|
public void setContentVisibility(boolean visibility) {
|
||||||
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
|
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
|
||||||
Log.d(TAG, "setContentVisibility: visibility=" + visibility
|
Log.d(TAG, "setContentVisibility: visibility=" + visibility
|
||||||
+ " bubble=" + getBubbleKey());
|
+ " bubble=" + getBubbleKey());
|
||||||
}
|
}
|
||||||
mIsContentVisible = visibility;
|
mIsContentVisible = visibility;
|
||||||
if (mTaskView != null && !mIsAlphaAnimating) {
|
if (mTaskView != null && !mIsAnimating) {
|
||||||
mTaskView.setAlpha(visibility ? 1f : 0f);
|
mTaskView.setAlpha(visibility ? 1f : 0f);
|
||||||
mPointerView.setAlpha(visibility ? 1f : 0f);
|
mPointerView.setAlpha(visibility ? 1f : 0f);
|
||||||
}
|
}
|
||||||
@@ -560,6 +722,44 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
return mTaskView;
|
return mTaskView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public BubbleOverflowContainerView getOverflow() {
|
||||||
|
return mOverflowView;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return content height: taskView or overflow.
|
||||||
|
* Takes into account clippings set by {@link #setTopClip(int)} and {@link #setBottomClip(int)}
|
||||||
|
*
|
||||||
|
* @return if bubble is for overflow, return overflow height, otherwise return taskView height
|
||||||
|
*/
|
||||||
|
public int getContentHeight() {
|
||||||
|
if (mIsOverflow) {
|
||||||
|
return mOverflowView.getHeight() - mTopClip - mBottomClip;
|
||||||
|
}
|
||||||
|
if (mTaskView != null) {
|
||||||
|
return mTaskView.getHeight() - mTopClip - mBottomClip;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return bottom position of the content on screen
|
||||||
|
*
|
||||||
|
* @return if bubble is for overflow, return value for overflow, otherwise taskView
|
||||||
|
*/
|
||||||
|
public int getContentBottomOnScreen() {
|
||||||
|
Rect out = new Rect();
|
||||||
|
if (mIsOverflow) {
|
||||||
|
mOverflowView.getBoundsOnScreen(out);
|
||||||
|
}
|
||||||
|
if (mTaskView != null) {
|
||||||
|
mTaskView.getBoundsOnScreen(out);
|
||||||
|
}
|
||||||
|
return out.bottom;
|
||||||
|
}
|
||||||
|
|
||||||
int getTaskId() {
|
int getTaskId() {
|
||||||
return mTaskId;
|
return mTaskId;
|
||||||
}
|
}
|
||||||
@@ -728,27 +928,47 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
post(() -> {
|
post(() -> {
|
||||||
mCurrentPointer = showVertically ? onLeft ? mLeftPointer : mRightPointer : mTopPointer;
|
mCurrentPointer = showVertically ? onLeft ? mLeftPointer : mRightPointer : mTopPointer;
|
||||||
updatePointerView();
|
updatePointerView();
|
||||||
float pointerY;
|
|
||||||
float pointerX;
|
|
||||||
if (showVertically) {
|
if (showVertically) {
|
||||||
pointerY = bubbleCenter - (mPointerWidth / 2f);
|
mPointerPos.y = bubbleCenter - (mPointerWidth / 2f);
|
||||||
pointerX = onLeft
|
mPointerPos.x = onLeft
|
||||||
? -mPointerHeight + mPointerOverlap
|
? -mPointerHeight + mPointerOverlap
|
||||||
: getWidth() - mPaddingRight - mPointerOverlap;
|
: getWidth() - mPaddingRight - mPointerOverlap;
|
||||||
} else {
|
} else {
|
||||||
pointerY = mPointerOverlap;
|
mPointerPos.y = mPointerOverlap;
|
||||||
pointerX = bubbleCenter - (mPointerWidth / 2f);
|
mPointerPos.x = bubbleCenter - (mPointerWidth / 2f);
|
||||||
}
|
}
|
||||||
if (animate) {
|
if (animate) {
|
||||||
mPointerView.animate().translationX(pointerX).translationY(pointerY).start();
|
mPointerView.animate().translationX(mPointerPos.x).translationY(
|
||||||
|
mPointerPos.y).start();
|
||||||
} else {
|
} else {
|
||||||
mPointerView.setTranslationY(pointerY);
|
mPointerView.setTranslationY(mPointerPos.y);
|
||||||
mPointerView.setTranslationX(pointerX);
|
mPointerView.setTranslationX(mPointerPos.x);
|
||||||
mPointerView.setVisibility(VISIBLE);
|
mPointerView.setVisibility(VISIBLE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if pointer is shown on the left
|
||||||
|
*/
|
||||||
|
public boolean isShowingLeftPointer() {
|
||||||
|
return mCurrentPointer == mLeftPointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if pointer is shown on the right
|
||||||
|
*/
|
||||||
|
public boolean isShowingRightPointer() {
|
||||||
|
return mCurrentPointer == mRightPointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return width of the current pointer
|
||||||
|
*/
|
||||||
|
public int getPointerWidth() {
|
||||||
|
return mPointerWidth;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Position of the manage button displayed in the expanded view. Used for placing user
|
* Position of the manage button displayed in the expanded view. Used for placing user
|
||||||
* education about the manage button.
|
* education about the manage button.
|
||||||
|
|||||||
@@ -344,6 +344,14 @@ public class BubblePositioner {
|
|||||||
return mImeVisible ? mImeHeight : 0;
|
return mImeVisible ? mImeHeight : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Return top position of the IME if it's visible */
|
||||||
|
public int getImeTop() {
|
||||||
|
if (mImeVisible) {
|
||||||
|
return getScreenRect().bottom - getImeHeight() - getInsets().bottom;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/** Sets whether the IME is visible. **/
|
/** Sets whether the IME is visible. **/
|
||||||
public void setImeVisible(boolean visible, int height) {
|
public void setImeVisible(boolean visible, int height) {
|
||||||
mImeVisible = visible;
|
mImeVisible = visible;
|
||||||
@@ -706,4 +714,21 @@ public class BubblePositioner {
|
|||||||
public void setPinnedLocation(PointF point) {
|
public void setPinnedLocation(PointF point) {
|
||||||
mPinLocation = point;
|
mPinLocation = point;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigation bar has an area where system gestures can be started from.
|
||||||
|
*
|
||||||
|
* @return {@link Rect} for system navigation bar gesture zone
|
||||||
|
*/
|
||||||
|
public Rect getNavBarGestureZone() {
|
||||||
|
// Gesture zone height from the bottom
|
||||||
|
int gestureZoneHeight = mContext.getResources().getDimensionPixelSize(
|
||||||
|
com.android.internal.R.dimen.navigation_bar_gesture_height);
|
||||||
|
Rect screen = getScreenRect();
|
||||||
|
return new Rect(
|
||||||
|
screen.left,
|
||||||
|
screen.bottom - gestureZoneHeight,
|
||||||
|
screen.right,
|
||||||
|
screen.bottom);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import android.graphics.Rect;
|
|||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.graphics.drawable.ColorDrawable;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.SystemProperties;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Choreographer;
|
import android.view.Choreographer;
|
||||||
@@ -56,6 +57,7 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewOutlineProvider;
|
import android.view.ViewOutlineProvider;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.view.WindowManagerPolicyConstants;
|
||||||
import android.view.accessibility.AccessibilityNodeInfo;
|
import android.view.accessibility.AccessibilityNodeInfo;
|
||||||
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
|
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
@@ -75,8 +77,12 @@ import com.android.internal.util.FrameworkStatsLog;
|
|||||||
import com.android.wm.shell.R;
|
import com.android.wm.shell.R;
|
||||||
import com.android.wm.shell.animation.Interpolators;
|
import com.android.wm.shell.animation.Interpolators;
|
||||||
import com.android.wm.shell.animation.PhysicsAnimator;
|
import com.android.wm.shell.animation.PhysicsAnimator;
|
||||||
|
import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
|
||||||
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
|
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
|
||||||
import com.android.wm.shell.bubbles.animation.ExpandedAnimationController;
|
import com.android.wm.shell.bubbles.animation.ExpandedAnimationController;
|
||||||
|
import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationController;
|
||||||
|
import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationControllerImpl;
|
||||||
|
import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationControllerStub;
|
||||||
import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout;
|
import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout;
|
||||||
import com.android.wm.shell.bubbles.animation.StackAnimationController;
|
import com.android.wm.shell.bubbles.animation.StackAnimationController;
|
||||||
import com.android.wm.shell.common.FloatingContentCoordinator;
|
import com.android.wm.shell.common.FloatingContentCoordinator;
|
||||||
@@ -97,6 +103,12 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
public class BubbleStackView extends FrameLayout
|
public class BubbleStackView extends FrameLayout
|
||||||
implements ViewTreeObserver.OnComputeInternalInsetsListener {
|
implements ViewTreeObserver.OnComputeInternalInsetsListener {
|
||||||
|
/**
|
||||||
|
* Set to {@code true} to enable home gesture handling in bubbles
|
||||||
|
*/
|
||||||
|
public static final boolean HOME_GESTURE_ENABLED =
|
||||||
|
SystemProperties.getBoolean("persist.wm.debug.bubbles_home_gesture", false);
|
||||||
|
|
||||||
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
|
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
|
||||||
|
|
||||||
/** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
|
/** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
|
||||||
@@ -148,7 +160,7 @@ public class BubbleStackView extends FrameLayout
|
|||||||
* Handler to use for all delayed animations - this way, we can easily cancel them before
|
* Handler to use for all delayed animations - this way, we can easily cancel them before
|
||||||
* starting a new animation.
|
* starting a new animation.
|
||||||
*/
|
*/
|
||||||
private final ShellExecutor mDelayedAnimationExecutor;
|
private final ShellExecutor mMainExecutor;
|
||||||
private Runnable mDelayedAnimation;
|
private Runnable mDelayedAnimation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -197,6 +209,7 @@ public class BubbleStackView extends FrameLayout
|
|||||||
private PhysicsAnimationLayout mBubbleContainer;
|
private PhysicsAnimationLayout mBubbleContainer;
|
||||||
private StackAnimationController mStackAnimationController;
|
private StackAnimationController mStackAnimationController;
|
||||||
private ExpandedAnimationController mExpandedAnimationController;
|
private ExpandedAnimationController mExpandedAnimationController;
|
||||||
|
private ExpandedViewAnimationController mExpandedViewAnimationController;
|
||||||
|
|
||||||
private View mScrim;
|
private View mScrim;
|
||||||
private View mManageMenuScrim;
|
private View mManageMenuScrim;
|
||||||
@@ -276,6 +289,9 @@ public class BubbleStackView extends FrameLayout
|
|||||||
*/
|
*/
|
||||||
private int mPointerIndexDown = -1;
|
private int mPointerIndexDown = -1;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private BubblesNavBarGestureTracker mBubblesNavBarGestureTracker;
|
||||||
|
|
||||||
/** Description of current animation controller state. */
|
/** Description of current animation controller state. */
|
||||||
public void dump(PrintWriter pw, String[] args) {
|
public void dump(PrintWriter pw, String[] args) {
|
||||||
pw.println("Stack view state:");
|
pw.println("Stack view state:");
|
||||||
@@ -693,6 +709,67 @@ public class BubbleStackView extends FrameLayout
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Touch listener set on the whole view that forwards event to the swipe up listener. */
|
||||||
|
private final RelativeTouchListener mContainerSwipeListener = new RelativeTouchListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
|
||||||
|
// Pass move event on to swipe listener
|
||||||
|
mSwipeUpListener.onDown(ev.getX(), ev.getY());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
|
||||||
|
float viewInitialY, float dx, float dy) {
|
||||||
|
// Pass move event on to swipe listener
|
||||||
|
mSwipeUpListener.onMove(dx, dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
|
||||||
|
float viewInitialY, float dx, float dy, float velX, float velY) {
|
||||||
|
// Pass up even on to swipe listener
|
||||||
|
mSwipeUpListener.onUp(velX, velY);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** MotionEventListener that listens from home gesture swipe event. */
|
||||||
|
private final MotionEventListener mSwipeUpListener = new MotionEventListener() {
|
||||||
|
@Override
|
||||||
|
public void onDown(float x, float y) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMove(float dx, float dy) {
|
||||||
|
if ((mManageEduView != null && mManageEduView.getVisibility() == VISIBLE)
|
||||||
|
|| isStackEduShowing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mShowingManage) {
|
||||||
|
showManageMenu(false /* show */);
|
||||||
|
}
|
||||||
|
// Only allow up
|
||||||
|
float collapsed = Math.min(dy, 0);
|
||||||
|
mExpandedViewAnimationController.updateDrag((int) -collapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancel() {
|
||||||
|
mExpandedViewAnimationController.animateBackToExpanded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUp(float velX, float velY) {
|
||||||
|
mExpandedViewAnimationController.setSwipeVelocity(velY);
|
||||||
|
if (mExpandedViewAnimationController.shouldCollapse()) {
|
||||||
|
// Update data first and start the animation when we are processing change
|
||||||
|
mBubbleData.setExpanded(false);
|
||||||
|
} else {
|
||||||
|
mExpandedViewAnimationController.animateBackToExpanded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/** Click listener set on the flyout, which expands the stack when the flyout is tapped. */
|
/** Click listener set on the flyout, which expands the stack when the flyout is tapped. */
|
||||||
private OnClickListener mFlyoutClickListener = new OnClickListener() {
|
private OnClickListener mFlyoutClickListener = new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -766,7 +843,7 @@ public class BubbleStackView extends FrameLayout
|
|||||||
ShellExecutor mainExecutor) {
|
ShellExecutor mainExecutor) {
|
||||||
super(context);
|
super(context);
|
||||||
|
|
||||||
mDelayedAnimationExecutor = mainExecutor;
|
mMainExecutor = mainExecutor;
|
||||||
mBubbleController = bubbleController;
|
mBubbleController = bubbleController;
|
||||||
mBubbleData = data;
|
mBubbleData = data;
|
||||||
|
|
||||||
@@ -796,6 +873,14 @@ public class BubbleStackView extends FrameLayout
|
|||||||
|
|
||||||
mExpandedAnimationController = new ExpandedAnimationController(mPositioner,
|
mExpandedAnimationController = new ExpandedAnimationController(mPositioner,
|
||||||
onBubbleAnimatedOut, this);
|
onBubbleAnimatedOut, this);
|
||||||
|
|
||||||
|
if (HOME_GESTURE_ENABLED) {
|
||||||
|
mExpandedViewAnimationController =
|
||||||
|
new ExpandedViewAnimationControllerImpl(context, mPositioner);
|
||||||
|
} else {
|
||||||
|
mExpandedViewAnimationController = new ExpandedViewAnimationControllerStub();
|
||||||
|
}
|
||||||
|
|
||||||
mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
|
mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
|
||||||
|
|
||||||
// Force LTR by default since most of the Bubbles UI is positioned manually by the user, or
|
// Force LTR by default since most of the Bubbles UI is positioned manually by the user, or
|
||||||
@@ -971,7 +1056,7 @@ public class BubbleStackView extends FrameLayout
|
|||||||
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
||||||
// We need to be Z ordered on top in order for alpha animations to work.
|
// We need to be Z ordered on top in order for alpha animations to work.
|
||||||
mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true);
|
mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true);
|
||||||
mExpandedBubble.getExpandedView().setAlphaAnimating(true);
|
mExpandedBubble.getExpandedView().setAnimating(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -985,14 +1070,15 @@ public class BubbleStackView extends FrameLayout
|
|||||||
// = 0f remains in effect.
|
// = 0f remains in effect.
|
||||||
&& !mExpandedViewTemporarilyHidden) {
|
&& !mExpandedViewTemporarilyHidden) {
|
||||||
mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
|
mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
|
||||||
mExpandedBubble.getExpandedView().setAlphaAnimating(false);
|
mExpandedBubble.getExpandedView().setAnimating(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
|
mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
|
||||||
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
||||||
mExpandedBubble.getExpandedView().setTaskViewAlpha(
|
float alpha = (float) valueAnimator.getAnimatedValue();
|
||||||
(float) valueAnimator.getAnimatedValue());
|
mExpandedBubble.getExpandedView().setContentAlpha(alpha);
|
||||||
|
mExpandedBubble.getExpandedView().setAlpha(alpha);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1795,6 +1881,7 @@ public class BubbleStackView extends FrameLayout
|
|||||||
private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) {
|
private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) {
|
||||||
final BubbleViewProvider previouslySelected = mExpandedBubble;
|
final BubbleViewProvider previouslySelected = mExpandedBubble;
|
||||||
mExpandedBubble = bubbleToSelect;
|
mExpandedBubble = bubbleToSelect;
|
||||||
|
mExpandedViewAnimationController.setExpandedView(mExpandedBubble.getExpandedView());
|
||||||
|
|
||||||
if (mIsExpanded) {
|
if (mIsExpanded) {
|
||||||
hideCurrentInputMethod();
|
hideCurrentInputMethod();
|
||||||
@@ -1848,7 +1935,12 @@ public class BubbleStackView extends FrameLayout
|
|||||||
mBubbleController.getSysuiProxy().onStackExpandChanged(shouldExpand);
|
mBubbleController.getSysuiProxy().onStackExpandChanged(shouldExpand);
|
||||||
|
|
||||||
if (mIsExpanded) {
|
if (mIsExpanded) {
|
||||||
|
stopMonitoringSwipeUpGesture();
|
||||||
|
if (HOME_GESTURE_ENABLED) {
|
||||||
animateCollapse();
|
animateCollapse();
|
||||||
|
} else {
|
||||||
|
animateCollapseWithScale();
|
||||||
|
}
|
||||||
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
|
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
|
||||||
} else {
|
} else {
|
||||||
animateExpansion();
|
animateExpansion();
|
||||||
@@ -1856,10 +1948,37 @@ public class BubbleStackView extends FrameLayout
|
|||||||
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
|
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
|
||||||
logBubbleEvent(mExpandedBubble,
|
logBubbleEvent(mExpandedBubble,
|
||||||
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
|
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
|
||||||
|
if (HOME_GESTURE_ENABLED) {
|
||||||
|
startMonitoringSwipeUpGesture();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
notifyExpansionChanged(mExpandedBubble, mIsExpanded);
|
notifyExpansionChanged(mExpandedBubble, mIsExpanded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startMonitoringSwipeUpGesture() {
|
||||||
|
stopMonitoringSwipeUpGesture();
|
||||||
|
|
||||||
|
if (isGestureNavEnabled()) {
|
||||||
|
mBubblesNavBarGestureTracker = new BubblesNavBarGestureTracker(mContext, mPositioner);
|
||||||
|
mBubblesNavBarGestureTracker.start(mSwipeUpListener);
|
||||||
|
setOnTouchListener(mContainerSwipeListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isGestureNavEnabled() {
|
||||||
|
return mContext.getResources().getInteger(
|
||||||
|
com.android.internal.R.integer.config_navBarInteractionMode)
|
||||||
|
== WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopMonitoringSwipeUpGesture() {
|
||||||
|
if (mBubblesNavBarGestureTracker != null) {
|
||||||
|
mBubblesNavBarGestureTracker.stop();
|
||||||
|
mBubblesNavBarGestureTracker = null;
|
||||||
|
setOnTouchListener(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when back press occurs while bubbles are expanded.
|
* Called when back press occurs while bubbles are expanded.
|
||||||
*/
|
*/
|
||||||
@@ -2072,11 +2191,12 @@ public class BubbleStackView extends FrameLayout
|
|||||||
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
|
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
|
||||||
|
|
||||||
if (mExpandedBubble.getExpandedView() != null) {
|
if (mExpandedBubble.getExpandedView() != null) {
|
||||||
mExpandedBubble.getExpandedView().setTaskViewAlpha(0f);
|
mExpandedBubble.getExpandedView().setContentAlpha(0f);
|
||||||
|
mExpandedBubble.getExpandedView().setAlpha(0f);
|
||||||
|
|
||||||
// We'll be starting the alpha animation after a slight delay, so set this flag early
|
// We'll be starting the alpha animation after a slight delay, so set this flag early
|
||||||
// here.
|
// here.
|
||||||
mExpandedBubble.getExpandedView().setAlphaAnimating(true);
|
mExpandedBubble.getExpandedView().setAnimating(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
mDelayedAnimation = () -> {
|
mDelayedAnimation = () -> {
|
||||||
@@ -2114,10 +2234,10 @@ public class BubbleStackView extends FrameLayout
|
|||||||
})
|
})
|
||||||
.start();
|
.start();
|
||||||
};
|
};
|
||||||
mDelayedAnimationExecutor.executeDelayed(mDelayedAnimation, startDelay);
|
mMainExecutor.executeDelayed(mDelayedAnimation, startDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void animateCollapse() {
|
private void animateCollapseWithScale() {
|
||||||
cancelDelayedExpandCollapseSwitchAnimations();
|
cancelDelayedExpandCollapseSwitchAnimations();
|
||||||
|
|
||||||
if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
|
if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
|
||||||
@@ -2217,6 +2337,68 @@ public class BubbleStackView extends FrameLayout
|
|||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void animateCollapse() {
|
||||||
|
cancelDelayedExpandCollapseSwitchAnimations();
|
||||||
|
|
||||||
|
if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
|
||||||
|
mManageEduView.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
mIsExpanded = false;
|
||||||
|
mIsExpansionAnimating = true;
|
||||||
|
|
||||||
|
showScrim(false);
|
||||||
|
|
||||||
|
mBubbleContainer.cancelAllAnimations();
|
||||||
|
|
||||||
|
// If we were in the middle of swapping, the animating-out surface would have been scaling
|
||||||
|
// to zero - finish it off.
|
||||||
|
PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
|
||||||
|
mAnimatingOutSurfaceContainer.setScaleX(0f);
|
||||||
|
mAnimatingOutSurfaceContainer.setScaleY(0f);
|
||||||
|
|
||||||
|
// Let the expanded animation controller know that it shouldn't animate child adds/reorders
|
||||||
|
// since we're about to animate collapsed.
|
||||||
|
mExpandedAnimationController.notifyPreparingToCollapse();
|
||||||
|
|
||||||
|
final Runnable collapseBackToStack = () -> mExpandedAnimationController.collapseBackToStack(
|
||||||
|
mStackAnimationController
|
||||||
|
.getStackPositionAlongNearestHorizontalEdge()
|
||||||
|
/* collapseTo */,
|
||||||
|
() -> mBubbleContainer.setActiveController(mStackAnimationController));
|
||||||
|
|
||||||
|
final Runnable after = () -> {
|
||||||
|
final BubbleViewProvider previouslySelected = mExpandedBubble;
|
||||||
|
// TODO(b/231350255): investigate why this call is needed here
|
||||||
|
beforeExpandedViewAnimation();
|
||||||
|
if (mManageEduView != null) {
|
||||||
|
mManageEduView.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_BUBBLE_STACK_VIEW) {
|
||||||
|
Log.d(TAG, "animateCollapse");
|
||||||
|
Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
|
||||||
|
mExpandedBubble));
|
||||||
|
}
|
||||||
|
updateOverflowVisibility();
|
||||||
|
updateZOrder();
|
||||||
|
updateBadges(true /* setBadgeForCollapsedStack */);
|
||||||
|
afterExpandedViewAnimation();
|
||||||
|
if (previouslySelected != null) {
|
||||||
|
previouslySelected.setTaskViewVisibility(false);
|
||||||
|
}
|
||||||
|
mExpandedViewAnimationController.reset();
|
||||||
|
};
|
||||||
|
mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after);
|
||||||
|
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
||||||
|
// When the animation completes, we should no longer be showing the content.
|
||||||
|
// This won't actually update content visibility immediately, if we are currently
|
||||||
|
// animating. But updates the internal state for the content to be hidden after
|
||||||
|
// animation completes.
|
||||||
|
mExpandedBubble.getExpandedView().setContentVisibility(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void animateSwitchBubbles() {
|
private void animateSwitchBubbles() {
|
||||||
// If we're no longer expanded, this is meaningless.
|
// If we're no longer expanded, this is meaningless.
|
||||||
if (!mIsExpanded) {
|
if (!mIsExpanded) {
|
||||||
@@ -2278,7 +2460,7 @@ public class BubbleStackView extends FrameLayout
|
|||||||
|
|
||||||
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
|
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
|
||||||
|
|
||||||
mDelayedAnimationExecutor.executeDelayed(() -> {
|
mMainExecutor.executeDelayed(() -> {
|
||||||
if (!mIsExpanded) {
|
if (!mIsExpanded) {
|
||||||
mIsBubbleSwitchAnimating = false;
|
mIsBubbleSwitchAnimating = false;
|
||||||
return;
|
return;
|
||||||
@@ -2309,7 +2491,7 @@ public class BubbleStackView extends FrameLayout
|
|||||||
* animating flags for those animations.
|
* animating flags for those animations.
|
||||||
*/
|
*/
|
||||||
private void cancelDelayedExpandCollapseSwitchAnimations() {
|
private void cancelDelayedExpandCollapseSwitchAnimations() {
|
||||||
mDelayedAnimationExecutor.removeCallbacks(mDelayedAnimation);
|
mMainExecutor.removeCallbacks(mDelayedAnimation);
|
||||||
|
|
||||||
mIsExpansionAnimating = false;
|
mIsExpansionAnimating = false;
|
||||||
mIsBubbleSwitchAnimating = false;
|
mIsBubbleSwitchAnimating = false;
|
||||||
@@ -2333,9 +2515,18 @@ public class BubbleStackView extends FrameLayout
|
|||||||
/**
|
/**
|
||||||
* Updates the stack based for IME changes. When collapsed it'll move the stack if it
|
* Updates the stack based for IME changes. When collapsed it'll move the stack if it
|
||||||
* overlaps where they IME would be. When expanded it'll shift the expanded bubbles
|
* overlaps where they IME would be. When expanded it'll shift the expanded bubbles
|
||||||
* if they might overlap with the IME (this only happens for large screens).
|
* if they might overlap with the IME (this only happens for large screens)
|
||||||
|
* and clip the expanded view.
|
||||||
*/
|
*/
|
||||||
public void animateForIme(boolean visible) {
|
public void setImeVisible(boolean visible) {
|
||||||
|
if (HOME_GESTURE_ENABLED) {
|
||||||
|
setImeVisibleInternal(visible);
|
||||||
|
} else {
|
||||||
|
setImeVisibleWithoutClipping(visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setImeVisibleWithoutClipping(boolean visible) {
|
||||||
if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
|
if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
|
||||||
// This will update the animation so the bubbles move to position for the IME
|
// This will update the animation so the bubbles move to position for the IME
|
||||||
mExpandedAnimationController.expandFromStack(() -> {
|
mExpandedAnimationController.expandFromStack(() -> {
|
||||||
@@ -2386,6 +2577,62 @@ public class BubbleStackView extends FrameLayout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setImeVisibleInternal(boolean visible) {
|
||||||
|
if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
|
||||||
|
// This will update the animation so the bubbles move to position for the IME
|
||||||
|
mExpandedAnimationController.expandFromStack(() -> {
|
||||||
|
updatePointerPosition(false /* forIme */);
|
||||||
|
afterExpandedViewAnimation();
|
||||||
|
mExpandedViewAnimationController.animateForImeVisibilityChange(visible);
|
||||||
|
} /* after */);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mIsExpanded && getBubbleCount() > 0) {
|
||||||
|
final float stackDestinationY =
|
||||||
|
mStackAnimationController.animateForImeVisibility(visible);
|
||||||
|
|
||||||
|
// How far the stack is animating due to IME, we'll just animate the flyout by that
|
||||||
|
// much too.
|
||||||
|
final float stackDy =
|
||||||
|
stackDestinationY - mStackAnimationController.getStackPosition().y;
|
||||||
|
|
||||||
|
// If the flyout is visible, translate it along with the bubble stack.
|
||||||
|
if (mFlyout.getVisibility() == VISIBLE) {
|
||||||
|
PhysicsAnimator.getInstance(mFlyout)
|
||||||
|
.spring(DynamicAnimation.TRANSLATION_Y,
|
||||||
|
mFlyout.getTranslationY() + stackDy,
|
||||||
|
FLYOUT_IME_ANIMATION_SPRING_CONFIG)
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIsExpanded) {
|
||||||
|
mExpandedViewAnimationController.animateForImeVisibilityChange(visible);
|
||||||
|
if (mPositioner.showBubblesVertically()
|
||||||
|
&& mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
||||||
|
float selectedY = mPositioner.getExpandedBubbleXY(getState().selectedIndex,
|
||||||
|
getState()).y;
|
||||||
|
float newExpandedViewTop = mPositioner.getExpandedViewY(mExpandedBubble, selectedY);
|
||||||
|
mExpandedBubble.getExpandedView().setImeVisible(visible);
|
||||||
|
if (!mExpandedBubble.getExpandedView().isUsingMaxHeight()) {
|
||||||
|
mExpandedViewContainer.animate().translationY(newExpandedViewTop);
|
||||||
|
}
|
||||||
|
List<Animator> animList = new ArrayList();
|
||||||
|
for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
|
||||||
|
View child = mBubbleContainer.getChildAt(i);
|
||||||
|
float transY = mPositioner.getExpandedBubbleXY(i, getState()).y;
|
||||||
|
ObjectAnimator anim = ObjectAnimator.ofFloat(child, TRANSLATION_Y, transY);
|
||||||
|
animList.add(anim);
|
||||||
|
}
|
||||||
|
updatePointerPosition(true /* forIme */);
|
||||||
|
AnimatorSet set = new AnimatorSet();
|
||||||
|
set.playTogether(animList);
|
||||||
|
set.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||||
if (ev.getAction() != MotionEvent.ACTION_DOWN && ev.getActionIndex() != mPointerIndexDown) {
|
if (ev.getAction() != MotionEvent.ACTION_DOWN && ev.getActionIndex() != mPointerIndexDown) {
|
||||||
@@ -2821,7 +3068,7 @@ public class BubbleStackView extends FrameLayout
|
|||||||
&& mExpandedBubble.getExpandedView() != null) {
|
&& mExpandedBubble.getExpandedView() != null) {
|
||||||
BubbleExpandedView bev = mExpandedBubble.getExpandedView();
|
BubbleExpandedView bev = mExpandedBubble.getExpandedView();
|
||||||
bev.setContentVisibility(false);
|
bev.setContentVisibility(false);
|
||||||
bev.setAlphaAnimating(!mIsExpansionAnimating);
|
bev.setAnimating(!mIsExpansionAnimating);
|
||||||
mExpandedViewContainerMatrix.setScaleX(0f);
|
mExpandedViewContainerMatrix.setScaleX(0f);
|
||||||
mExpandedViewContainerMatrix.setScaleY(0f);
|
mExpandedViewContainerMatrix.setScaleY(0f);
|
||||||
mExpandedViewContainerMatrix.setTranslate(0f, 0f);
|
mExpandedViewContainerMatrix.setTranslate(0f, 0f);
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.bubbles;
|
||||||
|
|
||||||
|
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
|
||||||
|
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.hardware.input.InputManager;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Choreographer;
|
||||||
|
import android.view.InputChannel;
|
||||||
|
import android.view.InputEventReceiver;
|
||||||
|
import android.view.InputMonitor;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up tracking bubbles gestures that begin in navigation bar
|
||||||
|
*/
|
||||||
|
class BubblesNavBarGestureTracker {
|
||||||
|
|
||||||
|
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubblesGestureTracker" : TAG_BUBBLES;
|
||||||
|
|
||||||
|
private static final String GESTURE_MONITOR = "bubbles-gesture";
|
||||||
|
private final Context mContext;
|
||||||
|
private final BubblePositioner mPositioner;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private InputMonitor mInputMonitor;
|
||||||
|
@Nullable
|
||||||
|
private InputEventReceiver mInputEventReceiver;
|
||||||
|
|
||||||
|
BubblesNavBarGestureTracker(Context context, BubblePositioner positioner) {
|
||||||
|
mContext = context;
|
||||||
|
mPositioner = positioner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start tracking gestures
|
||||||
|
*
|
||||||
|
* @param listener listener that is notified of touch events
|
||||||
|
*/
|
||||||
|
void start(MotionEventListener listener) {
|
||||||
|
if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
|
||||||
|
Log.d(TAG, "start monitoring bubbles swipe up gesture");
|
||||||
|
}
|
||||||
|
|
||||||
|
stopInternal();
|
||||||
|
|
||||||
|
mInputMonitor = InputManager.getInstance().monitorGestureInput(GESTURE_MONITOR,
|
||||||
|
mContext.getDisplayId());
|
||||||
|
InputChannel inputChannel = mInputMonitor.getInputChannel();
|
||||||
|
|
||||||
|
BubblesNavBarMotionEventHandler motionEventHandler =
|
||||||
|
new BubblesNavBarMotionEventHandler(mContext, mPositioner,
|
||||||
|
this::onInterceptTouch, listener);
|
||||||
|
mInputEventReceiver = new BubblesNavBarInputEventReceiver(inputChannel,
|
||||||
|
Choreographer.getInstance(), motionEventHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
|
||||||
|
Log.d(TAG, "stop monitoring bubbles swipe up gesture");
|
||||||
|
}
|
||||||
|
stopInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopInternal() {
|
||||||
|
if (mInputEventReceiver != null) {
|
||||||
|
mInputEventReceiver.dispose();
|
||||||
|
mInputEventReceiver = null;
|
||||||
|
}
|
||||||
|
if (mInputMonitor != null) {
|
||||||
|
mInputMonitor.dispose();
|
||||||
|
mInputMonitor = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onInterceptTouch() {
|
||||||
|
if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
|
||||||
|
Log.d(TAG, "intercept touch event");
|
||||||
|
}
|
||||||
|
if (mInputMonitor != null) {
|
||||||
|
mInputMonitor.pilferPointers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.bubbles;
|
||||||
|
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.view.BatchedInputEventReceiver;
|
||||||
|
import android.view.Choreographer;
|
||||||
|
import android.view.InputChannel;
|
||||||
|
import android.view.InputEvent;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bubbles {@link BatchedInputEventReceiver} for monitoring touches from navbar gesture area
|
||||||
|
*/
|
||||||
|
class BubblesNavBarInputEventReceiver extends BatchedInputEventReceiver {
|
||||||
|
|
||||||
|
private final BubblesNavBarMotionEventHandler mMotionEventHandler;
|
||||||
|
|
||||||
|
BubblesNavBarInputEventReceiver(InputChannel inputChannel,
|
||||||
|
Choreographer choreographer, BubblesNavBarMotionEventHandler motionEventHandler) {
|
||||||
|
super(inputChannel, Looper.myLooper(), choreographer);
|
||||||
|
mMotionEventHandler = motionEventHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputEvent(InputEvent event) {
|
||||||
|
boolean handled = false;
|
||||||
|
try {
|
||||||
|
if (!(event instanceof MotionEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handled = mMotionEventHandler.onMotionEvent((MotionEvent) event);
|
||||||
|
} finally {
|
||||||
|
finishInputEvent(event, handled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.bubbles;
|
||||||
|
|
||||||
|
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
|
||||||
|
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
|
||||||
|
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.PointF;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.VelocityTracker;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles {@link MotionEvent}s for bubbles that begin in the nav bar area
|
||||||
|
*/
|
||||||
|
class BubblesNavBarMotionEventHandler {
|
||||||
|
private static final String TAG =
|
||||||
|
TAG_WITH_CLASS_NAME ? "BubblesNavBarMotionEventHandler" : TAG_BUBBLES;
|
||||||
|
private static final int VELOCITY_UNITS = 1000;
|
||||||
|
|
||||||
|
private final Runnable mOnInterceptTouch;
|
||||||
|
private final MotionEventListener mMotionEventListener;
|
||||||
|
private final int mTouchSlop;
|
||||||
|
private final BubblePositioner mPositioner;
|
||||||
|
private final PointF mTouchDown = new PointF();
|
||||||
|
private boolean mTrackingTouches;
|
||||||
|
private boolean mInterceptingTouches;
|
||||||
|
@Nullable
|
||||||
|
private VelocityTracker mVelocityTracker;
|
||||||
|
|
||||||
|
BubblesNavBarMotionEventHandler(Context context, BubblePositioner positioner,
|
||||||
|
Runnable onInterceptTouch, MotionEventListener motionEventListener) {
|
||||||
|
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||||
|
mPositioner = positioner;
|
||||||
|
mOnInterceptTouch = onInterceptTouch;
|
||||||
|
mMotionEventListener = motionEventListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle {@link MotionEvent} and forward it to {@code motionEventListener} defined in
|
||||||
|
* constructor
|
||||||
|
*
|
||||||
|
* @return {@code true} if this {@link MotionEvent} is handled (it started in the gesture area)
|
||||||
|
*/
|
||||||
|
public boolean onMotionEvent(MotionEvent motionEvent) {
|
||||||
|
float dx = motionEvent.getX() - mTouchDown.x;
|
||||||
|
float dy = motionEvent.getY() - mTouchDown.y;
|
||||||
|
|
||||||
|
switch (motionEvent.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
if (isInGestureRegion(motionEvent)) {
|
||||||
|
mTouchDown.set(motionEvent.getX(), motionEvent.getY());
|
||||||
|
mMotionEventListener.onDown(motionEvent.getX(), motionEvent.getY());
|
||||||
|
mTrackingTouches = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
if (mTrackingTouches) {
|
||||||
|
if (!mInterceptingTouches && Math.hypot(dx, dy) > mTouchSlop) {
|
||||||
|
mInterceptingTouches = true;
|
||||||
|
mOnInterceptTouch.run();
|
||||||
|
}
|
||||||
|
if (mInterceptingTouches) {
|
||||||
|
getVelocityTracker().addMovement(motionEvent);
|
||||||
|
mMotionEventListener.onMove(dx, dy);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
if (mTrackingTouches) {
|
||||||
|
mMotionEventListener.onCancel();
|
||||||
|
finishTracking();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
if (mTrackingTouches) {
|
||||||
|
if (mInterceptingTouches) {
|
||||||
|
getVelocityTracker().computeCurrentVelocity(VELOCITY_UNITS);
|
||||||
|
mMotionEventListener.onUp(getVelocityTracker().getXVelocity(),
|
||||||
|
getVelocityTracker().getYVelocity());
|
||||||
|
}
|
||||||
|
finishTracking();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInGestureRegion(MotionEvent ev) {
|
||||||
|
// Only handles touch events beginning in navigation bar system gesture zone
|
||||||
|
if (mPositioner.getNavBarGestureZone().contains((int) ev.getX(), (int) ev.getY())) {
|
||||||
|
if (DEBUG_BUBBLE_GESTURE) {
|
||||||
|
Log.d(TAG, "handling touch y=" + ev.getY()
|
||||||
|
+ " navBarGestureZone=" + mPositioner.getNavBarGestureZone());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VelocityTracker getVelocityTracker() {
|
||||||
|
if (mVelocityTracker == null) {
|
||||||
|
mVelocityTracker = VelocityTracker.obtain();
|
||||||
|
}
|
||||||
|
return mVelocityTracker;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishTracking() {
|
||||||
|
mTouchDown.set(0, 0);
|
||||||
|
mTrackingTouches = false;
|
||||||
|
mInterceptingTouches = false;
|
||||||
|
if (mVelocityTracker != null) {
|
||||||
|
mVelocityTracker.recycle();
|
||||||
|
mVelocityTracker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for receiving {@link MotionEvent} updates
|
||||||
|
*/
|
||||||
|
interface MotionEventListener {
|
||||||
|
/**
|
||||||
|
* Touch down action.
|
||||||
|
*
|
||||||
|
* @param x x coordinate
|
||||||
|
* @param y y coordinate
|
||||||
|
*/
|
||||||
|
void onDown(float x, float y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move action.
|
||||||
|
* Reports distance from point reported in {@link #onDown(float, float)}
|
||||||
|
*
|
||||||
|
* @param dx distance moved on x-axis from starting point, in pixels
|
||||||
|
* @param dy distance moved on y-axis from starting point, in pixels
|
||||||
|
*/
|
||||||
|
void onMove(float dx, float dy);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Touch up action.
|
||||||
|
*
|
||||||
|
* @param velX velocity of the move action on x axis
|
||||||
|
* @param velY velocity of the move actin on y axis
|
||||||
|
*/
|
||||||
|
void onUp(float velX, float velY);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Motion action was cancelled.
|
||||||
|
*/
|
||||||
|
void onCancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,8 +17,6 @@
|
|||||||
package com.android.wm.shell.bubbles
|
package com.android.wm.shell.bubbles
|
||||||
|
|
||||||
import android.graphics.PointF
|
import android.graphics.PointF
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.VelocityTracker
|
import android.view.VelocityTracker
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -146,6 +144,12 @@ abstract class RelativeTouchListener : View.OnTouchListener {
|
|||||||
velocityTracker.clear()
|
velocityTracker.clear()
|
||||||
movedEnough = false
|
movedEnough = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_CANCEL -> {
|
||||||
|
v.handler.removeCallbacksAndMessages(null)
|
||||||
|
velocityTracker.clear()
|
||||||
|
movedEnough = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -17,11 +17,13 @@
|
|||||||
package com.android.wm.shell.bubbles.animation;
|
package com.android.wm.shell.bubbles.animation;
|
||||||
|
|
||||||
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
|
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
|
||||||
|
import static com.android.wm.shell.bubbles.BubbleStackView.HOME_GESTURE_ENABLED;
|
||||||
|
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.animation.Interpolator;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -61,7 +63,10 @@ public class ExpandedAnimationController
|
|||||||
private static final float DAMPING_RATIO_MEDIUM_LOW_BOUNCY = 0.65f;
|
private static final float DAMPING_RATIO_MEDIUM_LOW_BOUNCY = 0.65f;
|
||||||
|
|
||||||
/** Stiffness for the expand/collapse path-following animation. */
|
/** Stiffness for the expand/collapse path-following animation. */
|
||||||
private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
|
private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 400;
|
||||||
|
|
||||||
|
/** Stiffness for the expand/collapse animation when home gesture handling is off */
|
||||||
|
private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS_WITHOUT_HOME_GESTURE = 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Velocity required to dismiss an individual bubble without dragging it into the dismiss
|
* Velocity required to dismiss an individual bubble without dragging it into the dismiss
|
||||||
@@ -73,6 +78,11 @@ public class ExpandedAnimationController
|
|||||||
new PhysicsAnimator.SpringConfig(
|
new PhysicsAnimator.SpringConfig(
|
||||||
EXPAND_COLLAPSE_ANIM_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
|
EXPAND_COLLAPSE_ANIM_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
|
||||||
|
|
||||||
|
private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfigWithoutHomeGesture =
|
||||||
|
new PhysicsAnimator.SpringConfig(
|
||||||
|
EXPAND_COLLAPSE_ANIM_STIFFNESS_WITHOUT_HOME_GESTURE,
|
||||||
|
SpringForce.DAMPING_RATIO_NO_BOUNCY);
|
||||||
|
|
||||||
/** Horizontal offset between bubbles, which we need to know to re-stack them. */
|
/** Horizontal offset between bubbles, which we need to know to re-stack them. */
|
||||||
private float mStackOffsetPx;
|
private float mStackOffsetPx;
|
||||||
/** Size of each bubble. */
|
/** Size of each bubble. */
|
||||||
@@ -278,11 +288,20 @@ public class ExpandedAnimationController
|
|||||||
(firstBubbleLeads && index == 0)
|
(firstBubbleLeads && index == 0)
|
||||||
|| (!firstBubbleLeads && index == mLayout.getChildCount() - 1);
|
|| (!firstBubbleLeads && index == mLayout.getChildCount() - 1);
|
||||||
|
|
||||||
|
Interpolator interpolator;
|
||||||
|
if (HOME_GESTURE_ENABLED) {
|
||||||
|
// When home gesture is enabled, we use a different animation timing for collapse
|
||||||
|
interpolator = expanding
|
||||||
|
? Interpolators.EMPHASIZED_ACCELERATE : Interpolators.EMPHASIZED_DECELERATE;
|
||||||
|
} else {
|
||||||
|
interpolator = Interpolators.LINEAR;
|
||||||
|
}
|
||||||
|
|
||||||
animation
|
animation
|
||||||
.followAnimatedTargetAlongPath(
|
.followAnimatedTargetAlongPath(
|
||||||
path,
|
path,
|
||||||
EXPAND_COLLAPSE_TARGET_ANIM_DURATION /* targetAnimDuration */,
|
EXPAND_COLLAPSE_TARGET_ANIM_DURATION /* targetAnimDuration */,
|
||||||
Interpolators.LINEAR /* targetAnimInterpolator */,
|
interpolator /* targetAnimInterpolator */,
|
||||||
isLeadBubble ? mLeadBubbleEndAction : null /* endAction */,
|
isLeadBubble ? mLeadBubbleEndAction : null /* endAction */,
|
||||||
() -> mLeadBubbleEndAction = null /* endAction */)
|
() -> mLeadBubbleEndAction = null /* endAction */)
|
||||||
.withStartDelay(startDelay)
|
.withStartDelay(startDelay)
|
||||||
@@ -525,10 +544,16 @@ public class ExpandedAnimationController
|
|||||||
finishRemoval.run();
|
finishRemoval.run();
|
||||||
mOnBubbleAnimatedOutAction.run();
|
mOnBubbleAnimatedOutAction.run();
|
||||||
} else {
|
} else {
|
||||||
|
PhysicsAnimator.SpringConfig springConfig;
|
||||||
|
if (HOME_GESTURE_ENABLED) {
|
||||||
|
springConfig = mAnimateOutSpringConfig;
|
||||||
|
} else {
|
||||||
|
springConfig = mAnimateOutSpringConfigWithoutHomeGesture;
|
||||||
|
}
|
||||||
PhysicsAnimator.getInstance(child)
|
PhysicsAnimator.getInstance(child)
|
||||||
.spring(DynamicAnimation.ALPHA, 0f)
|
.spring(DynamicAnimation.ALPHA, 0f)
|
||||||
.spring(DynamicAnimation.SCALE_X, 0f, mAnimateOutSpringConfig)
|
.spring(DynamicAnimation.SCALE_X, 0f, springConfig)
|
||||||
.spring(DynamicAnimation.SCALE_Y, 0f, mAnimateOutSpringConfig)
|
.spring(DynamicAnimation.SCALE_Y, 0f, springConfig)
|
||||||
.withEndActions(finishRemoval, mOnBubbleAnimatedOutAction)
|
.withEndActions(finishRemoval, mOnBubbleAnimatedOutAction)
|
||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.bubbles.animation;
|
||||||
|
|
||||||
|
import com.android.wm.shell.bubbles.BubbleExpandedView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animation controller for bubble expanded view collapsing
|
||||||
|
*/
|
||||||
|
public interface ExpandedViewAnimationController {
|
||||||
|
/**
|
||||||
|
* Set expanded view that this controller is working with.
|
||||||
|
*/
|
||||||
|
void setExpandedView(BubbleExpandedView expandedView);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set current collapse value, in pixels.
|
||||||
|
*
|
||||||
|
* @param distance pixels that user dragged the view by
|
||||||
|
*/
|
||||||
|
void updateDrag(float distance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set current swipe velocity.
|
||||||
|
* Velocity is directional:
|
||||||
|
* <ul>
|
||||||
|
* <li>velocity < 0 means swipe direction is up</li>
|
||||||
|
* <li>velocity > 0 means swipe direction is down</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
void setSwipeVelocity(float velocity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if view is dragged past collapse threshold or swipe up velocity exceeds min velocity
|
||||||
|
* required to collapse the view
|
||||||
|
*/
|
||||||
|
boolean shouldCollapse();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animate view to collapsed state
|
||||||
|
*
|
||||||
|
* @param startStackCollapse runnable that is triggered when bubbles can start moving back to
|
||||||
|
* their collapsed location
|
||||||
|
* @param after runnable to run after animation is complete
|
||||||
|
*/
|
||||||
|
void animateCollapse(Runnable startStackCollapse, Runnable after);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animate the view back to fully expanded state.
|
||||||
|
*/
|
||||||
|
void animateBackToExpanded();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animate view for IME visibility change
|
||||||
|
*/
|
||||||
|
void animateForImeVisibilityChange(boolean visible);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the view to fully expanded state
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
}
|
||||||
@@ -0,0 +1,432 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.bubbles.animation;
|
||||||
|
|
||||||
|
import static android.view.View.ALPHA;
|
||||||
|
|
||||||
|
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_COLLAPSE_ANIMATOR;
|
||||||
|
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_EXPANDED_VIEW_DRAGGING;
|
||||||
|
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
|
||||||
|
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
|
||||||
|
import static com.android.wm.shell.bubbles.BubbleExpandedView.BOTTOM_CLIP_PROPERTY;
|
||||||
|
import static com.android.wm.shell.bubbles.BubbleExpandedView.CONTENT_ALPHA;
|
||||||
|
import static com.android.wm.shell.bubbles.BubbleExpandedView.MANAGE_BUTTON_ALPHA;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.HapticFeedbackConstants;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||||
|
import androidx.dynamicanimation.animation.FloatPropertyCompat;
|
||||||
|
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||||
|
import androidx.dynamicanimation.animation.SpringForce;
|
||||||
|
|
||||||
|
import com.android.wm.shell.animation.FlingAnimationUtils;
|
||||||
|
import com.android.wm.shell.animation.Interpolators;
|
||||||
|
import com.android.wm.shell.bubbles.BubbleExpandedView;
|
||||||
|
import com.android.wm.shell.bubbles.BubblePositioner;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link ExpandedViewAnimationController} that uses a collapse animation to
|
||||||
|
* hide the {@link BubbleExpandedView}
|
||||||
|
*/
|
||||||
|
public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimationController {
|
||||||
|
|
||||||
|
private static final String TAG = TAG_WITH_CLASS_NAME ? "ExpandedViewAnimCtrl" : TAG_BUBBLES;
|
||||||
|
|
||||||
|
private static final float COLLAPSE_THRESHOLD = 0.02f;
|
||||||
|
|
||||||
|
private static final int COLLAPSE_DURATION_MS = 250;
|
||||||
|
|
||||||
|
private static final int MANAGE_BUTTON_ANIM_DURATION_MS = 78;
|
||||||
|
|
||||||
|
private static final int CONTENT_OPACITY_ANIM_DELAY_MS = 93;
|
||||||
|
private static final int CONTENT_OPACITY_ANIM_DURATION_MS = 78;
|
||||||
|
|
||||||
|
private static final int BACKGROUND_OPACITY_ANIM_DELAY_MS = 172;
|
||||||
|
private static final int BACKGROUND_OPACITY_ANIM_DURATION_MS = 78;
|
||||||
|
|
||||||
|
/** Animation fraction threshold for content alpha animation when stack collapse should begin */
|
||||||
|
private static final float STACK_COLLAPSE_THRESHOLD = 0.5f;
|
||||||
|
|
||||||
|
private static final FloatPropertyCompat<ExpandedViewAnimationControllerImpl>
|
||||||
|
COLLAPSE_HEIGHT_PROPERTY =
|
||||||
|
new FloatPropertyCompat<ExpandedViewAnimationControllerImpl>("CollapseSpring") {
|
||||||
|
@Override
|
||||||
|
public float getValue(ExpandedViewAnimationControllerImpl controller) {
|
||||||
|
return controller.getCollapsedAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(ExpandedViewAnimationControllerImpl controller,
|
||||||
|
float value) {
|
||||||
|
controller.setCollapsedAmount(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final int mMinFlingVelocity;
|
||||||
|
private float mSwipeUpVelocity;
|
||||||
|
private float mSwipeDownVelocity;
|
||||||
|
private final BubblePositioner mPositioner;
|
||||||
|
private final FlingAnimationUtils mFlingAnimationUtils;
|
||||||
|
private int mDraggedAmount;
|
||||||
|
private float mCollapsedAmount;
|
||||||
|
@Nullable
|
||||||
|
private BubbleExpandedView mExpandedView;
|
||||||
|
@Nullable
|
||||||
|
private AnimatorSet mCollapseAnimation;
|
||||||
|
private boolean mNotifiedAboutThreshold;
|
||||||
|
private SpringAnimation mBackToExpandedAnimation;
|
||||||
|
@Nullable
|
||||||
|
private ObjectAnimator mBottomClipAnim;
|
||||||
|
|
||||||
|
public ExpandedViewAnimationControllerImpl(Context context, BubblePositioner positioner) {
|
||||||
|
mFlingAnimationUtils = new FlingAnimationUtils(context.getResources().getDisplayMetrics(),
|
||||||
|
COLLAPSE_DURATION_MS / 1000f);
|
||||||
|
mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
|
||||||
|
mPositioner = positioner;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void adjustAnimatorSetDuration(AnimatorSet animatorSet,
|
||||||
|
float durationAdjustment) {
|
||||||
|
for (Animator animator : animatorSet.getChildAnimations()) {
|
||||||
|
animator.setStartDelay((long) (animator.getStartDelay() * durationAdjustment));
|
||||||
|
animator.setDuration((long) (animator.getDuration() * durationAdjustment));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setExpandedView(BubbleExpandedView expandedView) {
|
||||||
|
if (mExpandedView != null) {
|
||||||
|
if (DEBUG_COLLAPSE_ANIMATOR) {
|
||||||
|
Log.d(TAG, "updating expandedView, resetting previous");
|
||||||
|
}
|
||||||
|
if (mCollapseAnimation != null) {
|
||||||
|
mCollapseAnimation.cancel();
|
||||||
|
}
|
||||||
|
if (mBackToExpandedAnimation != null) {
|
||||||
|
mBackToExpandedAnimation.cancel();
|
||||||
|
}
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
mExpandedView = expandedView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDrag(float distance) {
|
||||||
|
if (mExpandedView != null) {
|
||||||
|
mDraggedAmount = OverScroll.dampedScroll(distance, mExpandedView.getContentHeight());
|
||||||
|
|
||||||
|
if (DEBUG_COLLAPSE_ANIMATOR && DEBUG_EXPANDED_VIEW_DRAGGING) {
|
||||||
|
Log.d(TAG, "updateDrag: distance=" + distance + " dragged=" + mDraggedAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCollapsedAmount(mDraggedAmount);
|
||||||
|
|
||||||
|
if (!mNotifiedAboutThreshold && isPastCollapseThreshold()) {
|
||||||
|
mNotifiedAboutThreshold = true;
|
||||||
|
if (DEBUG_COLLAPSE_ANIMATOR) {
|
||||||
|
Log.d(TAG, "notifying over collapse threshold");
|
||||||
|
}
|
||||||
|
vibrateIfEnabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSwipeVelocity(float velocity) {
|
||||||
|
if (velocity < 0) {
|
||||||
|
mSwipeUpVelocity = Math.abs(velocity);
|
||||||
|
mSwipeDownVelocity = 0;
|
||||||
|
} else {
|
||||||
|
mSwipeUpVelocity = 0;
|
||||||
|
mSwipeDownVelocity = velocity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldCollapse() {
|
||||||
|
if (mSwipeDownVelocity > mMinFlingVelocity) {
|
||||||
|
// Swipe velocity is positive and over fling velocity.
|
||||||
|
// This is a swipe down, always reset to expanded state, regardless of dragged amount.
|
||||||
|
if (DEBUG_COLLAPSE_ANIMATOR) {
|
||||||
|
Log.d(TAG,
|
||||||
|
"not collapsing expanded view, swipe down velocity: " + mSwipeDownVelocity
|
||||||
|
+ " minV: " + mMinFlingVelocity);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mSwipeUpVelocity > mMinFlingVelocity) {
|
||||||
|
// Swiping up and over fling velocity, collapse the view.
|
||||||
|
if (DEBUG_COLLAPSE_ANIMATOR) {
|
||||||
|
Log.d(TAG,
|
||||||
|
"collapse expanded view, swipe up velocity: " + mSwipeUpVelocity + " minV: "
|
||||||
|
+ mMinFlingVelocity);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPastCollapseThreshold()) {
|
||||||
|
if (DEBUG_COLLAPSE_ANIMATOR) {
|
||||||
|
Log.d(TAG, "collapse expanded view, past threshold, dragged: " + mDraggedAmount);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_COLLAPSE_ANIMATOR) {
|
||||||
|
Log.d(TAG, "not collapsing expanded view");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void animateCollapse(Runnable startStackCollapse, Runnable after) {
|
||||||
|
if (DEBUG_COLLAPSE_ANIMATOR) {
|
||||||
|
Log.d(TAG,
|
||||||
|
"expandedView animate collapse swipeVel=" + mSwipeUpVelocity + " minFlingVel="
|
||||||
|
+ mMinFlingVelocity);
|
||||||
|
}
|
||||||
|
if (mExpandedView != null) {
|
||||||
|
// Mark it as animating immediately to avoid updates to the view before animation starts
|
||||||
|
mExpandedView.setAnimating(true);
|
||||||
|
|
||||||
|
if (mCollapseAnimation != null) {
|
||||||
|
mCollapseAnimation.cancel();
|
||||||
|
}
|
||||||
|
mCollapseAnimation = createCollapseAnimation(mExpandedView, startStackCollapse, after);
|
||||||
|
|
||||||
|
if (mSwipeUpVelocity >= mMinFlingVelocity) {
|
||||||
|
int contentHeight = mExpandedView.getContentHeight();
|
||||||
|
|
||||||
|
// Use a temp animator to get adjusted duration value for swipe.
|
||||||
|
// This new value will be used to adjust animation times proportionally in the
|
||||||
|
// animator set. If we adjust animator set duration directly, all child animations
|
||||||
|
// will get the same animation time.
|
||||||
|
ValueAnimator tempAnimator = new ValueAnimator();
|
||||||
|
mFlingAnimationUtils.applyDismissing(tempAnimator, mCollapsedAmount, contentHeight,
|
||||||
|
mSwipeUpVelocity, (contentHeight - mCollapsedAmount));
|
||||||
|
|
||||||
|
float durationAdjustment =
|
||||||
|
(float) tempAnimator.getDuration() / COLLAPSE_DURATION_MS;
|
||||||
|
|
||||||
|
adjustAnimatorSetDuration(mCollapseAnimation, durationAdjustment);
|
||||||
|
mCollapseAnimation.setInterpolator(tempAnimator.getInterpolator());
|
||||||
|
}
|
||||||
|
mCollapseAnimation.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void animateBackToExpanded() {
|
||||||
|
if (DEBUG_COLLAPSE_ANIMATOR) {
|
||||||
|
Log.d(TAG, "expandedView animate back to expanded");
|
||||||
|
}
|
||||||
|
BubbleExpandedView expandedView = mExpandedView;
|
||||||
|
if (expandedView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
expandedView.setAnimating(true);
|
||||||
|
|
||||||
|
mBackToExpandedAnimation = new SpringAnimation(this, COLLAPSE_HEIGHT_PROPERTY);
|
||||||
|
mBackToExpandedAnimation.setSpring(new SpringForce()
|
||||||
|
.setStiffness(SpringForce.STIFFNESS_LOW)
|
||||||
|
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
|
||||||
|
);
|
||||||
|
mBackToExpandedAnimation.addEndListener(new OneTimeEndListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
|
||||||
|
float velocity) {
|
||||||
|
super.onAnimationEnd(animation, canceled, value, velocity);
|
||||||
|
mNotifiedAboutThreshold = false;
|
||||||
|
mBackToExpandedAnimation = null;
|
||||||
|
expandedView.setAnimating(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mBackToExpandedAnimation.setStartValue(mCollapsedAmount);
|
||||||
|
mBackToExpandedAnimation.animateToFinalPosition(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void animateForImeVisibilityChange(boolean visible) {
|
||||||
|
if (mExpandedView != null) {
|
||||||
|
if (mBottomClipAnim != null) {
|
||||||
|
mBottomClipAnim.cancel();
|
||||||
|
}
|
||||||
|
int clip = 0;
|
||||||
|
if (visible) {
|
||||||
|
// Clip the expanded view at the top of the IME view
|
||||||
|
clip = mExpandedView.getContentBottomOnScreen() - mPositioner.getImeTop();
|
||||||
|
// Don't allow negative clip value
|
||||||
|
clip = Math.max(clip, 0);
|
||||||
|
}
|
||||||
|
mBottomClipAnim = ObjectAnimator.ofInt(mExpandedView, BOTTOM_CLIP_PROPERTY, clip);
|
||||||
|
mBottomClipAnim.addListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
mBottomClipAnim = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mBottomClipAnim.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
if (DEBUG_COLLAPSE_ANIMATOR) {
|
||||||
|
Log.d(TAG, "reset expandedView collapsed state");
|
||||||
|
}
|
||||||
|
if (mExpandedView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mExpandedView.setAnimating(false);
|
||||||
|
|
||||||
|
if (mCollapseAnimation != null) {
|
||||||
|
mCollapseAnimation.cancel();
|
||||||
|
}
|
||||||
|
if (mBackToExpandedAnimation != null) {
|
||||||
|
mBackToExpandedAnimation.cancel();
|
||||||
|
}
|
||||||
|
mExpandedView.setContentAlpha(1);
|
||||||
|
mExpandedView.setAlpha(1);
|
||||||
|
mExpandedView.setManageButtonAlpha(1);
|
||||||
|
setCollapsedAmount(0);
|
||||||
|
mExpandedView.setBottomClip(0);
|
||||||
|
mExpandedView.movePointerBy(0, 0);
|
||||||
|
mCollapsedAmount = 0;
|
||||||
|
mDraggedAmount = 0;
|
||||||
|
mSwipeUpVelocity = 0;
|
||||||
|
mSwipeDownVelocity = 0;
|
||||||
|
mNotifiedAboutThreshold = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getCollapsedAmount() {
|
||||||
|
return mCollapsedAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCollapsedAmount(float collapsed) {
|
||||||
|
if (mCollapsedAmount != collapsed) {
|
||||||
|
float previous = mCollapsedAmount;
|
||||||
|
mCollapsedAmount = collapsed;
|
||||||
|
|
||||||
|
if (mExpandedView != null) {
|
||||||
|
if (previous == 0) {
|
||||||
|
// View was not collapsed before. Apply z order change
|
||||||
|
mExpandedView.setSurfaceZOrderedOnTop(true);
|
||||||
|
mExpandedView.setAnimating(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
mExpandedView.setTopClip((int) mCollapsedAmount);
|
||||||
|
// Move up with translationY. Use negative collapsed value
|
||||||
|
mExpandedView.setContentTranslationY(-mCollapsedAmount);
|
||||||
|
mExpandedView.setManageButtonTranslationY(-mCollapsedAmount);
|
||||||
|
|
||||||
|
if (mCollapsedAmount == 0) {
|
||||||
|
// View is no longer collapsed. Revert z order change
|
||||||
|
mExpandedView.setSurfaceZOrderedOnTop(false);
|
||||||
|
mExpandedView.setAnimating(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPastCollapseThreshold() {
|
||||||
|
if (mExpandedView != null) {
|
||||||
|
return mDraggedAmount > mExpandedView.getContentHeight() * COLLAPSE_THRESHOLD;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AnimatorSet createCollapseAnimation(BubbleExpandedView expandedView,
|
||||||
|
Runnable startStackCollapse, Runnable after) {
|
||||||
|
List<Animator> animatorList = new ArrayList<>();
|
||||||
|
animatorList.add(createHeightAnimation(expandedView));
|
||||||
|
animatorList.add(createManageButtonAnimation());
|
||||||
|
ObjectAnimator contentAlphaAnimation = createContentAlphaAnimation();
|
||||||
|
final boolean[] notified = {false};
|
||||||
|
contentAlphaAnimation.addUpdateListener(animation -> {
|
||||||
|
if (!notified[0] && animation.getAnimatedFraction() > STACK_COLLAPSE_THRESHOLD) {
|
||||||
|
notified[0] = true;
|
||||||
|
// Notify bubbles that they can start moving back to the collapsed position
|
||||||
|
startStackCollapse.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
animatorList.add(contentAlphaAnimation);
|
||||||
|
animatorList.add(createBackgroundAlphaAnimation());
|
||||||
|
|
||||||
|
AnimatorSet animatorSet = new AnimatorSet();
|
||||||
|
animatorSet.addListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
after.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
animatorSet.playTogether(animatorList);
|
||||||
|
return animatorSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueAnimator createHeightAnimation(BubbleExpandedView expandedView) {
|
||||||
|
ValueAnimator animator = ValueAnimator.ofInt((int) mCollapsedAmount,
|
||||||
|
expandedView.getContentHeight());
|
||||||
|
animator.setInterpolator(Interpolators.EMPHASIZED_ACCELERATE);
|
||||||
|
animator.setDuration(COLLAPSE_DURATION_MS);
|
||||||
|
animator.addUpdateListener(anim -> setCollapsedAmount((int) anim.getAnimatedValue()));
|
||||||
|
return animator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObjectAnimator createManageButtonAnimation() {
|
||||||
|
ObjectAnimator animator = ObjectAnimator.ofFloat(mExpandedView, MANAGE_BUTTON_ALPHA, 0f);
|
||||||
|
animator.setDuration(MANAGE_BUTTON_ANIM_DURATION_MS);
|
||||||
|
animator.setInterpolator(Interpolators.LINEAR);
|
||||||
|
return animator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObjectAnimator createContentAlphaAnimation() {
|
||||||
|
ObjectAnimator animator = ObjectAnimator.ofFloat(mExpandedView, CONTENT_ALPHA, 0f);
|
||||||
|
animator.setDuration(CONTENT_OPACITY_ANIM_DURATION_MS);
|
||||||
|
animator.setInterpolator(Interpolators.LINEAR);
|
||||||
|
animator.setStartDelay(CONTENT_OPACITY_ANIM_DELAY_MS);
|
||||||
|
return animator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObjectAnimator createBackgroundAlphaAnimation() {
|
||||||
|
ObjectAnimator animator = ObjectAnimator.ofFloat(mExpandedView, ALPHA, 0f);
|
||||||
|
animator.setDuration(BACKGROUND_OPACITY_ANIM_DURATION_MS);
|
||||||
|
animator.setInterpolator(Interpolators.LINEAR);
|
||||||
|
animator.setStartDelay(BACKGROUND_OPACITY_ANIM_DELAY_MS);
|
||||||
|
return animator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
private void vibrateIfEnabled() {
|
||||||
|
if (mExpandedView != null) {
|
||||||
|
mExpandedView.performHapticFeedback(HapticFeedbackConstants.DRAG_CROSSING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.bubbles.animation;
|
||||||
|
|
||||||
|
import com.android.wm.shell.bubbles.BubbleExpandedView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stub implementation {@link ExpandedViewAnimationController} that does not animate the
|
||||||
|
* {@link BubbleExpandedView}
|
||||||
|
*/
|
||||||
|
public class ExpandedViewAnimationControllerStub implements ExpandedViewAnimationController {
|
||||||
|
@Override
|
||||||
|
public void setExpandedView(BubbleExpandedView expandedView) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDrag(float distance) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSwipeVelocity(float velocity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldCollapse() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void animateCollapse(Runnable startStackCollapse, Runnable after) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void animateBackToExpanded() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void animateForImeVisibilityChange(boolean visible) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.bubbles.animation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods for overscroll damping and related effect.
|
||||||
|
*
|
||||||
|
* Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/OverScroll.java
|
||||||
|
*/
|
||||||
|
public class OverScroll {
|
||||||
|
|
||||||
|
private static final float OVERSCROLL_DAMP_FACTOR = 0.07f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This curve determines how the effect of scrolling over the limits of the page diminishes
|
||||||
|
* as the user pulls further and further from the bounds
|
||||||
|
*
|
||||||
|
* @param f The percentage of how much the user has overscrolled.
|
||||||
|
* @return A transformed percentage based on the influence curve.
|
||||||
|
*/
|
||||||
|
private static float overScrollInfluenceCurve(float f) {
|
||||||
|
f -= 1.0f;
|
||||||
|
return f * f * f + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param amount The original amount overscrolled.
|
||||||
|
* @param max The maximum amount that the View can overscroll.
|
||||||
|
* @return The dampened overscroll amount.
|
||||||
|
*/
|
||||||
|
public static int dampedScroll(float amount, int max) {
|
||||||
|
if (Float.compare(amount, 0) == 0) return 0;
|
||||||
|
|
||||||
|
float f = amount / max;
|
||||||
|
f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
|
||||||
|
|
||||||
|
// Clamp this factor, f, to -1 < f < 1
|
||||||
|
if (Math.abs(f) >= 1) {
|
||||||
|
f /= Math.abs(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round(OVERSCROLL_DAMP_FACTOR * f * max);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.bubbles;
|
||||||
|
|
||||||
|
import static android.view.MotionEvent.ACTION_CANCEL;
|
||||||
|
import static android.view.MotionEvent.ACTION_DOWN;
|
||||||
|
import static android.view.MotionEvent.ACTION_MOVE;
|
||||||
|
import static android.view.MotionEvent.ACTION_UP;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.ArgumentMatchers.floatThat;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
|
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.testing.AndroidTestingRunner;
|
||||||
|
import android.testing.TestableLooper;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
|
import com.android.wm.shell.ShellTestCase;
|
||||||
|
import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test {@link MotionEvent} handling in {@link BubblesNavBarMotionEventHandler}.
|
||||||
|
* Verifies that swipe events
|
||||||
|
*/
|
||||||
|
@SmallTest
|
||||||
|
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||||
|
@RunWith(AndroidTestingRunner.class)
|
||||||
|
public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
|
||||||
|
|
||||||
|
private BubblesNavBarMotionEventHandler mMotionEventHandler;
|
||||||
|
@Mock
|
||||||
|
private WindowManager mWindowManager;
|
||||||
|
@Mock
|
||||||
|
private Runnable mInterceptTouchRunnable;
|
||||||
|
@Mock
|
||||||
|
private MotionEventListener mMotionEventListener;
|
||||||
|
private long mMotionEventTime;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
TestableBubblePositioner positioner = new TestableBubblePositioner(getContext(),
|
||||||
|
mWindowManager);
|
||||||
|
mMotionEventHandler = new BubblesNavBarMotionEventHandler(getContext(), positioner,
|
||||||
|
mInterceptTouchRunnable, mMotionEventListener);
|
||||||
|
mMotionEventTime = SystemClock.uptimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMotionEvent_swipeUpInGestureZone_handled() {
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990));
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 690));
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 490));
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 390));
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 390));
|
||||||
|
|
||||||
|
verify(mMotionEventListener).onDown(0, 990);
|
||||||
|
verify(mMotionEventListener).onMove(0, -300);
|
||||||
|
verify(mMotionEventListener).onMove(0, -500);
|
||||||
|
verify(mMotionEventListener).onMove(0, -600);
|
||||||
|
// Check that velocity up is about 5000
|
||||||
|
verify(mMotionEventListener).onUp(eq(0f), floatThat(f -> Math.round(f) == -5000));
|
||||||
|
verifyZeroInteractions(mMotionEventListener);
|
||||||
|
verify(mInterceptTouchRunnable).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMotionEvent_swipeUpOutsideGestureZone_ignored() {
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 500));
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 100));
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 100));
|
||||||
|
|
||||||
|
verifyZeroInteractions(mMotionEventListener);
|
||||||
|
verifyZeroInteractions(mInterceptTouchRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMotionEvent_horizontalMoveMoreThanTouchSlop_handled() {
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990));
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 100, 990));
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 100, 990));
|
||||||
|
|
||||||
|
verify(mMotionEventListener).onDown(0, 990);
|
||||||
|
verify(mMotionEventListener).onMove(100, 0);
|
||||||
|
verify(mMotionEventListener).onUp(0, 0);
|
||||||
|
verifyZeroInteractions(mMotionEventListener);
|
||||||
|
verify(mInterceptTouchRunnable).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMotionEvent_moveLessThanTouchSlop_ignored() {
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990));
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 989));
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 989));
|
||||||
|
|
||||||
|
verify(mMotionEventListener).onDown(0, 990);
|
||||||
|
verifyNoMoreInteractions(mMotionEventListener);
|
||||||
|
verifyZeroInteractions(mInterceptTouchRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMotionEvent_actionCancel_listenerNotified() {
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990));
|
||||||
|
mMotionEventHandler.onMotionEvent(newEvent(ACTION_CANCEL, 0, 990));
|
||||||
|
verify(mMotionEventListener).onDown(0, 990);
|
||||||
|
verify(mMotionEventListener).onCancel();
|
||||||
|
verifyNoMoreInteractions(mMotionEventListener);
|
||||||
|
verifyZeroInteractions(mInterceptTouchRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MotionEvent newEvent(int actionDown, float x, float y) {
|
||||||
|
MotionEvent event = MotionEvent.obtain(0L, mMotionEventTime, actionDown, x, y, 0);
|
||||||
|
mMotionEventTime += 10;
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.bubbles.animation;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.testing.AndroidTestingRunner;
|
||||||
|
import android.testing.TestableLooper;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
|
import com.android.wm.shell.ShellTestCase;
|
||||||
|
import com.android.wm.shell.bubbles.BubbleExpandedView;
|
||||||
|
import com.android.wm.shell.bubbles.TestableBubblePositioner;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidTestingRunner.class)
|
||||||
|
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||||
|
public class ExpandedViewAnimationControllerTest extends ShellTestCase {
|
||||||
|
|
||||||
|
private ExpandedViewAnimationController mController;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private WindowManager mWindowManager;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private BubbleExpandedView mMockExpandedView;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
|
TestableBubblePositioner positioner = new TestableBubblePositioner(getContext(),
|
||||||
|
mWindowManager);
|
||||||
|
mController = new ExpandedViewAnimationControllerImpl(getContext(), positioner);
|
||||||
|
|
||||||
|
mController.setExpandedView(mMockExpandedView);
|
||||||
|
when(mMockExpandedView.getContentHeight()).thenReturn(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateDrag_expandedViewMovesUpAndClipped() {
|
||||||
|
// Drag by 50 pixels which corresponds to 10 pixels with overscroll
|
||||||
|
int dragDistance = 50;
|
||||||
|
int dampenedDistance = 10;
|
||||||
|
|
||||||
|
mController.updateDrag(dragDistance);
|
||||||
|
|
||||||
|
verify(mMockExpandedView).setTopClip(dampenedDistance);
|
||||||
|
verify(mMockExpandedView).setContentTranslationY(-dampenedDistance);
|
||||||
|
verify(mMockExpandedView).setManageButtonTranslationY(-dampenedDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateDrag_zOrderUpdates() {
|
||||||
|
mController.updateDrag(10);
|
||||||
|
mController.updateDrag(20);
|
||||||
|
|
||||||
|
verify(mMockExpandedView, times(1)).setSurfaceZOrderedOnTop(true);
|
||||||
|
verify(mMockExpandedView, times(1)).setAnimating(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateDrag_moveBackToZero_zOrderRestored() {
|
||||||
|
mController.updateDrag(50);
|
||||||
|
reset(mMockExpandedView);
|
||||||
|
mController.updateDrag(0);
|
||||||
|
mController.updateDrag(0);
|
||||||
|
|
||||||
|
verify(mMockExpandedView, times(1)).setSurfaceZOrderedOnTop(false);
|
||||||
|
verify(mMockExpandedView, times(1)).setAnimating(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateDrag_hapticFeedbackOnlyOnce() {
|
||||||
|
// Drag by 10 which is below the collapse threshold - no feedback
|
||||||
|
mController.updateDrag(10);
|
||||||
|
verify(mMockExpandedView, times(0)).performHapticFeedback(anyInt());
|
||||||
|
// 150 takes it over the threshold - perform feedback
|
||||||
|
mController.updateDrag(150);
|
||||||
|
verify(mMockExpandedView, times(1)).performHapticFeedback(anyInt());
|
||||||
|
// Continue dragging, no more feedback
|
||||||
|
mController.updateDrag(200);
|
||||||
|
verify(mMockExpandedView, times(1)).performHapticFeedback(anyInt());
|
||||||
|
// Drag below threshold and over again - no more feedback
|
||||||
|
mController.updateDrag(10);
|
||||||
|
mController.updateDrag(150);
|
||||||
|
verify(mMockExpandedView, times(1)).performHapticFeedback(anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldCollapse_doNotCollapseIfNotDragged() {
|
||||||
|
assertThat(mController.shouldCollapse()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldCollapse_doNotCollapseIfVelocityDown() {
|
||||||
|
assumeTrue("Min fling velocity should be > 1 for this test", getMinFlingVelocity() > 1);
|
||||||
|
mController.setSwipeVelocity(getVelocityAboveMinFling());
|
||||||
|
assertThat(mController.shouldCollapse()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tesShouldCollapse_doNotCollapseIfVelocityUpIsSmall() {
|
||||||
|
assumeTrue("Min fling velocity should be > 1 for this test", getMinFlingVelocity() > 1);
|
||||||
|
mController.setSwipeVelocity(-getVelocityBelowMinFling());
|
||||||
|
assertThat(mController.shouldCollapse()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldCollapse_collapseIfVelocityUpIsLarge() {
|
||||||
|
assumeTrue("Min fling velocity should be > 1 for this test", getMinFlingVelocity() > 1);
|
||||||
|
mController.setSwipeVelocity(-getVelocityAboveMinFling());
|
||||||
|
assertThat(mController.shouldCollapse()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldCollapse_collapseIfPastThreshold() {
|
||||||
|
mController.updateDrag(500);
|
||||||
|
assertThat(mController.shouldCollapse()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReset() {
|
||||||
|
mController.updateDrag(100);
|
||||||
|
reset(mMockExpandedView);
|
||||||
|
mController.reset();
|
||||||
|
verify(mMockExpandedView, atLeastOnce()).setAnimating(false);
|
||||||
|
verify(mMockExpandedView).setContentAlpha(1);
|
||||||
|
verify(mMockExpandedView).setAlpha(1);
|
||||||
|
verify(mMockExpandedView).setManageButtonAlpha(1);
|
||||||
|
verify(mMockExpandedView).setManageButtonAlpha(1);
|
||||||
|
verify(mMockExpandedView).setTopClip(0);
|
||||||
|
verify(mMockExpandedView).setContentTranslationY(-0f);
|
||||||
|
verify(mMockExpandedView).setManageButtonTranslationY(-0f);
|
||||||
|
verify(mMockExpandedView).setBottomClip(0);
|
||||||
|
verify(mMockExpandedView).movePointerBy(0, 0);
|
||||||
|
assertThat(mController.shouldCollapse()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getVelocityBelowMinFling() {
|
||||||
|
return getMinFlingVelocity() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getVelocityAboveMinFling() {
|
||||||
|
return getMinFlingVelocity() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getMinFlingVelocity() {
|
||||||
|
return ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -166,6 +166,13 @@ public class Flags {
|
|||||||
public static final SysPropBooleanFlag WM_ENABLE_SHELL_TRANSITIONS =
|
public static final SysPropBooleanFlag WM_ENABLE_SHELL_TRANSITIONS =
|
||||||
new SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false);
|
new SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* b/170163464: animate bubbles expanded view collapse with home gesture
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
public static final SysPropBooleanFlag BUBBLES_HOME_GESTURE =
|
||||||
|
new SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", false);
|
||||||
|
|
||||||
// 1200 - predictive back
|
// 1200 - predictive back
|
||||||
@Keep
|
@Keep
|
||||||
public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
|
public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
|
||||||
|
|||||||
Reference in New Issue
Block a user