diff options
7 files changed, 416 insertions, 459 deletions
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml index c58c00114262..27823009459c 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -26,7 +26,7 @@ android:layout_width="match_parent" android:layout_gravity="bottom" android:src="@drawable/overlay_actions_background_protection"/> - <com.android.systemui.clipboardoverlay.DraggableConstraintLayout + <com.android.systemui.screenshot.DraggableConstraintLayout android:id="@+id/clipboard_ui" android:theme="@style/FloatingOverlay" android:layout_width="match_parent" @@ -146,5 +146,5 @@ android:layout_margin="@dimen/overlay_dismiss_button_margin" android:src="@drawable/overlay_cancel"/> </FrameLayout> - </com.android.systemui.clipboardoverlay.DraggableConstraintLayout> + </com.android.systemui.screenshot.DraggableConstraintLayout> </FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml index 813bb6018801..8de80844d784 100644 --- a/packages/SystemUI/res/layout/screenshot_static.xml +++ b/packages/SystemUI/res/layout/screenshot_static.xml @@ -14,25 +14,25 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<androidx.constraintlayout.widget.ConstraintLayout +<com.android.systemui.screenshot.DraggableConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView - android:id="@+id/screenshot_actions_container_background" + android:id="@+id/actions_container_background" android:visibility="gone" android:layout_height="0dp" android:layout_width="0dp" android:elevation="1dp" android:background="@drawable/action_chip_container_background" android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" - app:layout_constraintBottom_toBottomOf="@+id/screenshot_actions_container" + app:layout_constraintBottom_toBottomOf="@+id/actions_container" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="@+id/screenshot_actions_container" - app:layout_constraintEnd_toEndOf="@+id/screenshot_actions_container"/> + app:layout_constraintTop_toTopOf="@+id/actions_container" + app:layout_constraintEnd_toEndOf="@+id/actions_container"/> <HorizontalScrollView - android:id="@+id/screenshot_actions_container" + android:id="@+id/actions_container" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal" @@ -130,4 +130,4 @@ app:layout_constraintStart_toStartOf="@id/screenshot_preview" app:layout_constraintTop_toTopOf="@id/screenshot_preview" android:elevation="@dimen/overlay_preview_elevation"/> -</androidx.constraintlayout.widget.ConstraintLayout> +</com.android.systemui.screenshot.DraggableConstraintLayout> diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index 56e4d7ec1882..0f937d163f5c 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -73,6 +73,7 @@ import android.widget.TextView; import com.android.internal.policy.PhoneWindow; import com.android.systemui.R; +import com.android.systemui.screenshot.DraggableConstraintLayout; import com.android.systemui.screenshot.FloatingWindowUtil; import com.android.systemui.screenshot.OverlayActionChip; import com.android.systemui.screenshot.TimeoutHandler; @@ -166,8 +167,28 @@ public class ClipboardOverlayController { mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip)); mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button)); - mView.setOnDismissEndCallback(this::hideImmediate); - mView.setOnInteractionCallback(mTimeoutHandler::resetTimeout); + mView.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() { + @Override + public void onInteraction() { + mTimeoutHandler.resetTimeout(); + } + + @Override + public void onSwipeDismissInitiated(Animator animator) { + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + mContainer.animate().alpha(0).start(); + } + }); + } + + @Override + public void onDismissComplete() { + hideImmediate(); + } + }); mDismissButton.setOnClickListener(view -> animateOut()); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java deleted file mode 100644 index 57dc1620a410..000000000000 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/DraggableConstraintLayout.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.clipboardoverlay; - -import android.animation.Animator; -import android.content.Context; -import android.graphics.Rect; -import android.graphics.Region; -import android.util.AttributeSet; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewTreeObserver; - -import androidx.constraintlayout.widget.ConstraintLayout; - -import com.android.systemui.R; -import com.android.systemui.screenshot.SwipeDismissHandler; - -import java.util.function.Consumer; - -/** - * ConstraintLayout that is draggable when touched in a specific region - */ -public class DraggableConstraintLayout extends ConstraintLayout - implements ViewTreeObserver.OnComputeInternalInsetsListener { - private final SwipeDismissHandler mSwipeDismissHandler; - private final GestureDetector mSwipeDetector; - private Consumer<Animator> mOnDismissInitiated; - private Runnable mOnDismissComplete; - private Runnable mOnInteraction; - - public DraggableConstraintLayout(Context context) { - this(context, null); - } - - public DraggableConstraintLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public DraggableConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - - mSwipeDismissHandler = new SwipeDismissHandler(mContext, this, - new SwipeDismissHandler.SwipeDismissCallbacks() { - @Override - public void onInteraction() { - if (mOnInteraction != null) { - mOnInteraction.run(); - } - } - - @Override - public void onSwipeDismissInitiated(Animator animator) { - if (mOnDismissInitiated != null) { - mOnDismissInitiated.accept(animator); - } - } - - @Override - public void onDismissComplete() { - if (mOnDismissComplete != null) { - mOnDismissComplete.run(); - } - } - }); - setOnTouchListener(mSwipeDismissHandler); - - mSwipeDetector = new GestureDetector(mContext, - new GestureDetector.SimpleOnGestureListener() { - final Rect mActionsRect = new Rect(); - - @Override - public boolean onScroll( - MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) { - View actionsContainer = findViewById(R.id.actions_container); - actionsContainer.getBoundsOnScreen(mActionsRect); - // return true if we aren't in the actions bar, or if we are but it isn't - // scrollable in the direction of movement - return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY()) - || !actionsContainer.canScrollHorizontally((int) distanceX); - } - }); - mSwipeDetector.setIsLongpressEnabled(false); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { - mSwipeDismissHandler.onTouch(this, ev); - } - - return mSwipeDetector.onTouchEvent(ev); - } - - /** - * Dismiss the view, with animation controlled by SwipeDismissHandler - */ - public void dismiss() { - mSwipeDismissHandler.dismiss(); - } - - /** - * Set the callback to be run after view is dismissed (before animation; receives animator as - * input) - */ - public void setOnDismissStartCallback(Consumer<Animator> callback) { - mOnDismissInitiated = callback; - } - - /** - * Set the callback to be run after view is dismissed - */ - public void setOnDismissEndCallback(Runnable callback) { - mOnDismissComplete = callback; - } - - /** - * Set the callback to be run when the view is interacted with (e.g. tapped) - */ - public void setOnInteractionCallback(Runnable callback) { - mOnInteraction = callback; - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - getViewTreeObserver().addOnComputeInternalInsetsListener(this); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - getViewTreeObserver().removeOnComputeInternalInsetsListener(this); - } - - @Override - public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { - // Only child views are touchable. - Region r = new Region(); - Rect rect = new Rect(); - for (int i = 0; i < getChildCount(); i++) { - getChildAt(i).getGlobalVisibleRect(rect); - r.op(rect, Region.Op.UNION); - } - inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - inoutInfo.touchableRegion.set(r); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java new file mode 100644 index 000000000000..0b987677eac9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM; +import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Rect; +import android.graphics.Region; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.MathUtils; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewTreeObserver; + +import androidx.constraintlayout.widget.ConstraintLayout; + +import com.android.systemui.R; + +/** + * ConstraintLayout that is draggable when touched in a specific region + */ +public class DraggableConstraintLayout extends ConstraintLayout + implements ViewTreeObserver.OnComputeInternalInsetsListener { + + private final SwipeDismissHandler mSwipeDismissHandler; + private final GestureDetector mSwipeDetector; + private View mActionsContainer; + private SwipeDismissCallbacks mCallbacks; + + /** + * Stores the callbacks when the view is interacted with or dismissed. + */ + public interface SwipeDismissCallbacks { + /** + * Run when the view is interacted with (touched) + */ + default void onInteraction() { + + } + + /** + * Run when the view is dismissed (the distance threshold is met), pre-dismissal animation + */ + default void onSwipeDismissInitiated(Animator animator) { + + } + + /** + * Run when the view is dismissed (the distance threshold is met), post-dismissal animation + */ + default void onDismissComplete() { + + } + } + + public DraggableConstraintLayout(Context context) { + this(context, null); + } + + public DraggableConstraintLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DraggableConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + mSwipeDismissHandler = new SwipeDismissHandler(mContext, this); + setOnTouchListener(mSwipeDismissHandler); + + mSwipeDetector = new GestureDetector(mContext, + new GestureDetector.SimpleOnGestureListener() { + final Rect mActionsRect = new Rect(); + + @Override + public boolean onScroll( + MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) { + mActionsContainer.getBoundsOnScreen(mActionsRect); + // return true if we aren't in the actions bar, or if we are but it isn't + // scrollable in the direction of movement + return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY()) + || !mActionsContainer.canScrollHorizontally((int) distanceX); + } + }); + mSwipeDetector.setIsLongpressEnabled(false); + } + + public void setCallbacks(SwipeDismissCallbacks callbacks) { + mCallbacks = callbacks; + } + + @Override // View + protected void onFinishInflate() { + mActionsContainer = findViewById(R.id.actions_container_background); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { + mSwipeDismissHandler.onTouch(this, ev); + } + return mSwipeDetector.onTouchEvent(ev); + } + + public int getVisibleRight() { + return mActionsContainer.getRight(); + } + + /** + * Cancel current dismissal animation, if any + */ + public void cancelDismissal() { + mSwipeDismissHandler.cancel(); + } + + /** + * Return whether the view is currently dismissing + */ + public boolean isDismissing() { + return mSwipeDismissHandler.isDismissing(); + } + + /** + * Dismiss the view, with animation controlled by SwipeDismissHandler + */ + public void dismiss() { + mSwipeDismissHandler.dismiss(); + } + + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + getViewTreeObserver().addOnComputeInternalInsetsListener(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + } + + @Override + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { + // Only child views are touchable. + Region r = new Region(); + Rect rect = new Rect(); + for (int i = 0; i < getChildCount(); i++) { + getChildAt(i).getGlobalVisibleRect(rect); + r.op(rect, Region.Op.UNION); + } + inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + inoutInfo.touchableRegion.set(r); + } + + /** + * Allows a view to be swipe-dismissed, or returned to its location if distance threshold is not + * met + */ + private class SwipeDismissHandler implements OnTouchListener { + private static final String TAG = "SwipeDismissHandler"; + + // distance needed to register a dismissal + private static final float DISMISS_DISTANCE_THRESHOLD_DP = 20; + + private final DraggableConstraintLayout mView; + private final GestureDetector mGestureDetector; + private final DisplayMetrics mDisplayMetrics; + private ValueAnimator mDismissAnimation; + + private float mStartX; + // Keeps track of the most recent direction (between the last two move events). + // -1 for left; +1 for right. + private int mDirectionX; + private float mPreviousX; + + SwipeDismissHandler(Context context, DraggableConstraintLayout view) { + mView = view; + GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener(); + mGestureDetector = new GestureDetector(context, gestureListener); + mDisplayMetrics = new DisplayMetrics(); + context.getDisplay().getRealMetrics(mDisplayMetrics); + mCallbacks = new SwipeDismissCallbacks() { + }; // default to unimplemented callbacks + } + + @Override + public boolean onTouch(View view, MotionEvent event) { + boolean gestureResult = mGestureDetector.onTouchEvent(event); + mCallbacks.onInteraction(); + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mStartX = event.getRawX(); + mPreviousX = mStartX; + return true; + } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { + if (mDismissAnimation != null && mDismissAnimation.isRunning()) { + return true; + } + if (isPastDismissThreshold()) { + dismiss(); + } else { + // if we've moved, but not past the threshold, start the return animation + if (DEBUG_DISMISS) { + Log.d(TAG, "swipe gesture abandoned"); + } + createSwipeReturnAnimation().start(); + } + return true; + } + return gestureResult; + } + + class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener { + @Override + public boolean onScroll( + MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) { + mView.setTranslationX(ev2.getRawX() - mStartX); + mDirectionX = (ev2.getRawX() < mPreviousX) ? -1 : 1; + mPreviousX = ev2.getRawX(); + return true; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + if (mView.getTranslationX() * velocityX > 0 + && (mDismissAnimation == null || !mDismissAnimation.isRunning())) { + ValueAnimator dismissAnimator = + createSwipeDismissAnimation(velocityX / (float) 1000); + mCallbacks.onSwipeDismissInitiated(dismissAnimator); + dismiss(dismissAnimator); + return true; + } + return false; + } + } + + private boolean isPastDismissThreshold() { + float translationX = mView.getTranslationX(); + // Determines whether the absolute translation from the start is in the same direction + // as the current movement. For example, if the user moves most of the way to the right, + // but then starts dragging back left, we do not dismiss even though the absolute + // distance is greater than the threshold. + if (translationX * mDirectionX > 0) { + return Math.abs(translationX) >= FloatingWindowUtil.dpToPx(mDisplayMetrics, + DISMISS_DISTANCE_THRESHOLD_DP); + } + return false; + } + + boolean isDismissing() { + return (mDismissAnimation != null && mDismissAnimation.isRunning()); + } + + void cancel() { + if (isDismissing()) { + if (DEBUG_ANIM) { + Log.d(TAG, "cancelling dismiss animation"); + } + mDismissAnimation.cancel(); + } + } + + void dismiss() { + ValueAnimator anim = createSwipeDismissAnimation(3); + mCallbacks.onSwipeDismissInitiated(anim); + dismiss(anim); + } + + private void dismiss(ValueAnimator animator) { + mDismissAnimation = animator; + mDismissAnimation.addListener(new AnimatorListenerAdapter() { + private boolean mCancelled; + + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + mCancelled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (!mCancelled) { + mCallbacks.onDismissComplete(); + } + } + }); + mDismissAnimation.start(); + } + + private ValueAnimator createSwipeDismissAnimation(float velocity) { + // velocity is measured in pixels per millisecond + velocity = Math.min(3, Math.max(1, velocity)); + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + float startX = mView.getTranslationX(); + // make sure the UI gets all the way off the screen in the direction of movement + // (the actions container background is guaranteed to be both the leftmost and + // rightmost UI element in LTR and RTL) + float finalX; + int layoutDir = + mView.getContext().getResources().getConfiguration().getLayoutDirection(); + if (startX > 0 || (startX == 0 && layoutDir == LAYOUT_DIRECTION_RTL)) { + finalX = mDisplayMetrics.widthPixels; + } else { + finalX = -1 * mActionsContainer.getRight(); + } + float distance = Math.abs(finalX - startX); + + anim.addUpdateListener(animation -> { + float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction()); + mView.setTranslationX(translation); + mView.setAlpha(1 - animation.getAnimatedFraction()); + }); + anim.setDuration((long) (distance / Math.abs(velocity))); + return anim; + } + + private ValueAnimator createSwipeReturnAnimation() { + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + float startX = mView.getTranslationX(); + float finalX = 0; + + anim.addUpdateListener(animation -> { + float translation = MathUtils.lerp( + startX, finalX, animation.getAnimatedFraction()); + mView.setTranslationX(translation); + }); + + return anim; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index 6d729b92e7b8..6af6e36a75f7 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -141,7 +141,7 @@ public class ScreenshotView extends FrameLayout implements private ScreenshotSelectorView mScreenshotSelectorView; private ImageView mScrollingScrim; - private View mScreenshotStatic; + private DraggableConstraintLayout mScreenshotStatic; private ImageView mScreenshotPreview; private View mScreenshotPreviewBorder; private ImageView mScrollablePreview; @@ -159,7 +159,6 @@ public class ScreenshotView extends FrameLayout implements private UiEventLogger mUiEventLogger; private ScreenshotViewCallback mCallbacks; private boolean mPendingSharedTransition; - private SwipeDismissHandler mSwipeDismissHandler; private InputMonitorCompat mInputMonitor; private InputChannelCompat.InputEventReceiver mInputEventReceiver; private boolean mShowScrollablePreview; @@ -332,19 +331,6 @@ public class ScreenshotView extends FrameLayout implements } } - @Override // ViewGroup - public boolean onInterceptTouchEvent(MotionEvent ev) { - // scrolling scrim should not be swipeable; return early if we're on the scrim - if (!getSwipeRegion().contains((int) ev.getRawX(), (int) ev.getRawY())) { - return false; - } - // always pass through the down event so the swipe handler knows the initial state - if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { - mSwipeDismissHandler.onTouch(this, ev); - } - return mSwipeDetector.onTouchEvent(ev); - } - @Override // View protected void onFinishInflate() { mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim)); @@ -356,8 +342,8 @@ public class ScreenshotView extends FrameLayout implements mScreenshotPreview.setClipToOutline(true); mActionsContainerBackground = requireNonNull(findViewById( - R.id.screenshot_actions_container_background)); - mActionsContainer = requireNonNull(findViewById(R.id.screenshot_actions_container)); + R.id.actions_container_background)); + mActionsContainer = requireNonNull(findViewById(R.id.actions_container)); mActionsView = requireNonNull(findViewById(R.id.screenshot_actions)); mBackgroundProtection = requireNonNull( findViewById(R.id.screenshot_actions_background)); @@ -395,35 +381,34 @@ public class ScreenshotView extends FrameLayout implements setFocusableInTouchMode(true); requestFocus(); - mSwipeDismissHandler = new SwipeDismissHandler(mContext, mScreenshotStatic, - new SwipeDismissHandler.SwipeDismissCallbacks() { - @Override - public void onInteraction() { - mCallbacks.onUserInteraction(); - } - - @Override - public void onSwipeDismissInitiated(Animator anim) { - if (DEBUG_DISMISS) { - Log.d(ScreenshotView.TAG, "dismiss triggered via swipe gesture"); - } - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0, - mPackageName); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - super.onAnimationStart(animation); - mBackgroundProtection.animate() - .alpha(0).setDuration(anim.getDuration()).start(); - } - }); - } + mScreenshotStatic.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() { + @Override + public void onInteraction() { + mCallbacks.onUserInteraction(); + } + @Override + public void onSwipeDismissInitiated(Animator animator) { + if (DEBUG_DISMISS) { + Log.d(ScreenshotView.TAG, "dismiss triggered via swipe gesture"); + } + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0, + mPackageName); + animator.addListener(new AnimatorListenerAdapter() { @Override - public void onDismissComplete() { - mCallbacks.onDismiss(); + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + mBackgroundProtection.animate() + .alpha(0).setDuration(animation.getDuration()).start(); } }); + } + + @Override + public void onDismissComplete() { + mCallbacks.onDismiss(); + } + }); } View getScreenshotPreview() { @@ -648,8 +633,6 @@ public class ScreenshotView extends FrameLayout implements requestLayout(); createScreenshotActionsShadeAnimation().start(); - - setOnTouchListener(mSwipeDismissHandler); } }); @@ -958,7 +941,7 @@ public class ScreenshotView extends FrameLayout implements } boolean isDismissing() { - return mSwipeDismissHandler.isDismissing(); + return mScreenshotStatic.isDismissing(); } boolean isPendingSharedTransition() { @@ -966,15 +949,14 @@ public class ScreenshotView extends FrameLayout implements } void animateDismissal() { - mSwipeDismissHandler.dismiss(); + mScreenshotStatic.dismiss(); } void reset() { if (DEBUG_UI) { Log.d(TAG, "reset screenshot view"); } - - mSwipeDismissHandler.cancel(); + mScreenshotStatic.cancelDismissal(); if (DEBUG_WINDOW) { Log.d(TAG, "removing OnComputeInternalInsetsListener"); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java deleted file mode 100644 index 24b1249c3f98..000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SwipeDismissHandler.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.screenshot; - -import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM; -import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.content.Context; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.MathUtils; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.View; - -/** - * Allows a view to be swipe-dismissed, or returned to its location if distance threshold is not met - */ -public class SwipeDismissHandler implements View.OnTouchListener { - private static final String TAG = "SwipeDismissHandler"; - - // distance needed to register a dismissal - private static final float DISMISS_DISTANCE_THRESHOLD_DP = 20; - - /** - * Stores the callbacks when the view is interacted with or dismissed. - */ - public interface SwipeDismissCallbacks { - /** - * Run when the view is interacted with (touched) - */ - void onInteraction(); - - /** - * Run when the view is dismissed (the distance threshold is met), pre-dismissal animation - */ - void onSwipeDismissInitiated(Animator animator); - - /** - * Run when the view is dismissed (the distance threshold is met), post-dismissal animation - */ - void onDismissComplete(); - } - - private final View mView; - private final SwipeDismissCallbacks mCallbacks; - private final GestureDetector mGestureDetector; - private DisplayMetrics mDisplayMetrics; - private ValueAnimator mDismissAnimation; - - - private float mStartX; - // Keeps track of the most recent direction (between the last two move events). - // -1 for left; +1 for right. - private int mDirectionX; - private float mPreviousX; - - public SwipeDismissHandler(Context context, View view, SwipeDismissCallbacks callbacks) { - mView = view; - mCallbacks = callbacks; - GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener(); - mGestureDetector = new GestureDetector(context, gestureListener); - mDisplayMetrics = new DisplayMetrics(); - context.getDisplay().getRealMetrics(mDisplayMetrics); - } - - @Override - public boolean onTouch(View view, MotionEvent event) { - boolean gestureResult = mGestureDetector.onTouchEvent(event); - mCallbacks.onInteraction(); - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mStartX = event.getRawX(); - mPreviousX = mStartX; - return true; - } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { - if (mDismissAnimation != null && mDismissAnimation.isRunning()) { - return true; - } - if (isPastDismissThreshold()) { - ValueAnimator dismissAnimator = createSwipeDismissAnimation(1); - mCallbacks.onSwipeDismissInitiated(dismissAnimator); - dismiss(dismissAnimator); - } else { - // if we've moved, but not past the threshold, start the return animation - if (DEBUG_DISMISS) { - Log.d(TAG, "swipe gesture abandoned"); - } - createSwipeReturnAnimation().start(); - } - return true; - } - return gestureResult; - } - - class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener { - @Override - public boolean onScroll( - MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) { - mView.setTranslationX(ev2.getRawX() - mStartX); - mDirectionX = (ev2.getRawX() < mPreviousX) ? -1 : 1; - mPreviousX = ev2.getRawX(); - return true; - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, - float velocityY) { - if (mView.getTranslationX() * velocityX > 0 - && (mDismissAnimation == null || !mDismissAnimation.isRunning())) { - ValueAnimator dismissAnimator = - createSwipeDismissAnimation(velocityX / (float) 1000); - mCallbacks.onSwipeDismissInitiated(dismissAnimator); - dismiss(dismissAnimator); - return true; - } - return false; - } - } - - private boolean isPastDismissThreshold() { - float translationX = mView.getTranslationX(); - // Determines whether the absolute translation from the start is in the same direction - // as the current movement. For example, if the user moves most of the way to the right, - // but then starts dragging back left, we do not dismiss even though the absolute - // distance is greater than the threshold. - if (translationX * mDirectionX > 0) { - return Math.abs(translationX) >= FloatingWindowUtil.dpToPx(mDisplayMetrics, - DISMISS_DISTANCE_THRESHOLD_DP); - } - return false; - } - - /** - * Return whether the view is currently being dismissed - */ - public boolean isDismissing() { - return (mDismissAnimation != null && mDismissAnimation.isRunning()); - } - - /** - * Cancel the currently-running dismissal animation, if any. - */ - public void cancel() { - if (isDismissing()) { - if (DEBUG_ANIM) { - Log.d(TAG, "cancelling dismiss animation"); - } - mDismissAnimation.cancel(); - } - } - - /** - * Start dismissal animation (will run onDismiss callback when animation complete) - */ - public void dismiss() { - dismiss(createSwipeDismissAnimation(1)); - } - - private void dismiss(ValueAnimator animator) { - mDismissAnimation = animator; - mDismissAnimation.addListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - - @Override - public void onAnimationCancel(Animator animation) { - super.onAnimationCancel(animation); - mCancelled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - if (!mCancelled) { - mCallbacks.onDismissComplete(); - } - } - }); - mDismissAnimation.start(); - } - - private ValueAnimator createSwipeDismissAnimation(float velocity) { - // velocity is measured in pixels per millisecond - velocity = Math.min(3, Math.max(1, velocity)); - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - float startX = mView.getTranslationX(); - // make sure the UI gets all the way off the screen in the direction of movement - // (the actions container background is guaranteed to be both the leftmost and - // rightmost UI element in LTR and RTL) - float finalX; - int layoutDir = mView.getContext().getResources().getConfiguration().getLayoutDirection(); - if (startX > 0 || (startX == 0 && layoutDir == View.LAYOUT_DIRECTION_RTL)) { - finalX = mDisplayMetrics.widthPixels; - } else { - finalX = -1 * mView.getRight(); - } - float distance = Math.abs(finalX - startX); - - anim.addUpdateListener(animation -> { - float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction()); - mView.setTranslationX(translation); - mView.setAlpha(1 - animation.getAnimatedFraction()); - }); - anim.setDuration((long) (distance / Math.abs(velocity))); - return anim; - } - - private ValueAnimator createSwipeReturnAnimation() { - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - float startX = mView.getTranslationX(); - float finalX = 0; - - anim.addUpdateListener(animation -> { - float translation = MathUtils.lerp( - startX, finalX, animation.getAnimatedFraction()); - mView.setTranslationX(translation); - }); - - return anim; - } -} |