diff options
4 files changed, 456 insertions, 527 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java deleted file mode 100644 index 636bc5b912e5..000000000000 --- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright (C) 2024 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.ambient.touch; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.graphics.Rect; -import android.graphics.Region; -import android.util.Log; -import android.view.GestureDetector; -import android.view.InputEvent; -import android.view.MotionEvent; -import android.view.VelocityTracker; - -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; - -import com.android.internal.logging.UiEvent; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.widget.LockPatternUtils; -import com.android.systemui.Flags; -import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule; -import com.android.systemui.ambient.touch.scrim.ScrimController; -import com.android.systemui.ambient.touch.scrim.ScrimManager; -import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.settings.UserTracker; -import com.android.systemui.shade.ShadeExpansionChangeEvent; -import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.phone.CentralSurfaces; -import com.android.wm.shell.animation.FlingAnimationUtils; - -import java.util.Optional; - -import javax.inject.Inject; -import javax.inject.Named; - -/** - * Monitor for tracking touches on the DreamOverlay to bring up the bouncer. - */ -public class BouncerSwipeTouchHandler implements TouchHandler { - /** - * An interface for creating ValueAnimators. - */ - public interface ValueAnimatorCreator { - /** - * Creates {@link ValueAnimator}. - */ - ValueAnimator create(float start, float finish); - } - - /** - * An interface for obtaining VelocityTrackers. - */ - public interface VelocityTrackerFactory { - /** - * Obtains {@link VelocityTracker}. - */ - VelocityTracker obtain(); - } - - public static final float FLING_PERCENTAGE_THRESHOLD = 0.5f; - - private static final String TAG = "BouncerSwipeTouchHandler"; - private final NotificationShadeWindowController mNotificationShadeWindowController; - private final LockPatternUtils mLockPatternUtils; - private final UserTracker mUserTracker; - private final float mBouncerZoneScreenPercentage; - private final float mMinBouncerZoneScreenPercentage; - - private final ScrimManager mScrimManager; - private ScrimController mCurrentScrimController; - private float mCurrentExpansion; - private final Optional<CentralSurfaces> mCentralSurfaces; - - private VelocityTracker mVelocityTracker; - - private final FlingAnimationUtils mFlingAnimationUtils; - private final FlingAnimationUtils mFlingAnimationUtilsClosing; - - private Boolean mCapture; - private Boolean mExpanded; - - private TouchSession mTouchSession; - - private final ValueAnimatorCreator mValueAnimatorCreator; - - private final VelocityTrackerFactory mVelocityTrackerFactory; - - private final UiEventLogger mUiEventLogger; - - private final ActivityStarter mActivityStarter; - - private final ScrimManager.Callback mScrimManagerCallback = new ScrimManager.Callback() { - @Override - public void onScrimControllerChanged(ScrimController controller) { - if (mCurrentScrimController != null) { - mCurrentScrimController.reset(); - } - - mCurrentScrimController = controller; - } - }; - - private final GestureDetector.OnGestureListener mOnGestureListener = - new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX, - float distanceY) { - if (mCapture == null) { - if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) { - mCapture = Math.abs(distanceY) > Math.abs(distanceX) - && distanceY > 0; - } else { - // If the user scrolling favors a vertical direction, begin capturing - // scrolls. - mCapture = Math.abs(distanceY) > Math.abs(distanceX); - } - if (mCapture) { - // reset expanding - mExpanded = false; - // Since the user is dragging the bouncer up, set scrimmed to false. - mCurrentScrimController.show(); - } - } - - if (!mCapture) { - return false; - } - - // Don't set expansion for downward scroll. - if (e1.getY() < e2.getY()) { - return true; - } - - if (!mCentralSurfaces.isPresent()) { - return true; - } - - // If scrolling up and keyguard is not locked, dismiss both keyguard and the - // dream since there's no bouncer to show. - if (e1.getY() > e2.getY() - && !mLockPatternUtils.isSecure(mUserTracker.getUserId())) { - mActivityStarter.executeRunnableDismissingKeyguard( - () -> mCentralSurfaces.get().awakenDreams(), - /* cancelAction= */ null, - /* dismissShade= */ true, - /* afterKeyguardGone= */ true, - /* deferred= */ false); - return true; - } - - // For consistency, we adopt the expansion definition found in the - // PanelViewController. In this case, expansion refers to the view above the - // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer - // is fully hidden at full expansion (1) and fully visible when fully collapsed - // (0). - final float screenTravelPercentage = Math.abs(e1.getY() - e2.getY()) - / mTouchSession.getBounds().height(); - setPanelExpansion(1 - screenTravelPercentage); - return true; - } - }; - - private void setPanelExpansion(float expansion) { - mCurrentExpansion = expansion; - ShadeExpansionChangeEvent event = - new ShadeExpansionChangeEvent( - /* fraction= */ mCurrentExpansion, - /* expanded= */ mExpanded, - /* tracking= */ true); - mCurrentScrimController.expand(event); - } - - - @VisibleForTesting - public enum DreamEvent implements UiEventLogger.UiEventEnum { - @UiEvent(doc = "The screensaver has been swiped up.") - DREAM_SWIPED(988), - - @UiEvent(doc = "The bouncer has become fully visible over dream.") - DREAM_BOUNCER_FULLY_VISIBLE(1056); - - private final int mId; - - DreamEvent(int id) { - mId = id; - } - - @Override - public int getId() { - return mId; - } - } - - @Inject - public BouncerSwipeTouchHandler( - ScrimManager scrimManager, - Optional<CentralSurfaces> centralSurfaces, - NotificationShadeWindowController notificationShadeWindowController, - ValueAnimatorCreator valueAnimatorCreator, - VelocityTrackerFactory velocityTrackerFactory, - LockPatternUtils lockPatternUtils, - UserTracker userTracker, - @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING) - FlingAnimationUtils flingAnimationUtils, - @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING) - FlingAnimationUtils flingAnimationUtilsClosing, - @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage, - @Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage, - UiEventLogger uiEventLogger, - ActivityStarter activityStarter) { - mCentralSurfaces = centralSurfaces; - mScrimManager = scrimManager; - mNotificationShadeWindowController = notificationShadeWindowController; - mLockPatternUtils = lockPatternUtils; - mUserTracker = userTracker; - mBouncerZoneScreenPercentage = swipeRegionPercentage; - mMinBouncerZoneScreenPercentage = minRegionPercentage; - mFlingAnimationUtils = flingAnimationUtils; - mFlingAnimationUtilsClosing = flingAnimationUtilsClosing; - mValueAnimatorCreator = valueAnimatorCreator; - mVelocityTrackerFactory = velocityTrackerFactory; - mUiEventLogger = uiEventLogger; - mActivityStarter = activityStarter; - } - - @Override - public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) { - final int width = bounds.width(); - final int height = bounds.height(); - final int minAllowableBottom = Math.round(height * (1 - mMinBouncerZoneScreenPercentage)); - - final Rect normalRegion = new Rect(0, - Math.round(height * (1 - mBouncerZoneScreenPercentage)), - width, height); - - if (exclusionRect != null) { - int lowestBottom = Math.min(Math.max(0, exclusionRect.bottom), minAllowableBottom); - normalRegion.top = Math.max(normalRegion.top, lowestBottom); - } - region.union(normalRegion); - } - - - @Override - public void onSessionStart(TouchSession session) { - mVelocityTracker = mVelocityTrackerFactory.obtain(); - mTouchSession = session; - mVelocityTracker.clear(); - - if (!Flags.communalBouncerDoNotModifyPluginOpen()) { - mNotificationShadeWindowController.setForcePluginOpen(true, this); - } - - mScrimManager.addCallback(mScrimManagerCallback); - mCurrentScrimController = mScrimManager.getCurrentController(); - - session.registerCallback(() -> { - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - mScrimManager.removeCallback(mScrimManagerCallback); - mCapture = null; - mTouchSession = null; - - if (!Flags.communalBouncerDoNotModifyPluginOpen()) { - mNotificationShadeWindowController.setForcePluginOpen(false, this); - } - }); - - session.registerGestureListener(mOnGestureListener); - session.registerInputListener(ev -> onMotionEvent(ev)); - - } - - private void onMotionEvent(InputEvent event) { - if (!(event instanceof MotionEvent)) { - Log.e(TAG, "non MotionEvent received:" + event); - return; - } - - final MotionEvent motionEvent = (MotionEvent) event; - - switch (motionEvent.getAction()) { - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - mTouchSession.pop(); - // If we are not capturing any input, there is no need to consider animating to - // finish transition. - if (mCapture == null || !mCapture) { - break; - } - - // We must capture the resulting velocities as resetMonitor() will clear these - // values. - mVelocityTracker.computeCurrentVelocity(1000); - final float verticalVelocity = mVelocityTracker.getYVelocity(); - final float horizontalVelocity = mVelocityTracker.getXVelocity(); - - final float velocityVector = - (float) Math.hypot(horizontalVelocity, verticalVelocity); - - mExpanded = !flingRevealsOverlay(verticalVelocity, velocityVector); - final float expansion = mExpanded - ? KeyguardBouncerConstants.EXPANSION_VISIBLE - : KeyguardBouncerConstants.EXPANSION_HIDDEN; - - // Log the swiping up to show Bouncer event. - if (expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) { - mUiEventLogger.log(DreamEvent.DREAM_SWIPED); - } - - flingToExpansion(verticalVelocity, expansion); - break; - default: - mVelocityTracker.addMovement(motionEvent); - break; - } - } - - private ValueAnimator createExpansionAnimator(float targetExpansion) { - final ValueAnimator animator = - mValueAnimatorCreator.create(mCurrentExpansion, targetExpansion); - animator.addUpdateListener( - animation -> { - float expansionFraction = (float) animation.getAnimatedValue(); - setPanelExpansion(expansionFraction); - }); - if (targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) { - animator.addListener( - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mUiEventLogger.log(DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE); - } - }); - } - return animator; - } - - protected boolean flingRevealsOverlay(float velocity, float velocityVector) { - // Fully expand the space above the bouncer, if the user has expanded the bouncer less - // than halfway or final velocity was positive, indicating a downward direction. - if (Math.abs(velocityVector) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { - return mCurrentExpansion > FLING_PERCENTAGE_THRESHOLD; - } else { - return velocity > 0; - } - } - - protected void flingToExpansion(float velocity, float expansion) { - if (!mCentralSurfaces.isPresent()) { - return; - } - - // Don't set expansion if the user doesn't have a pin/password set. - if (!mLockPatternUtils.isSecure(mUserTracker.getUserId())) { - return; - } - - // The animation utils deal in pixel units, rather than expansion height. - final float viewHeight = mTouchSession.getBounds().height(); - final float currentHeight = viewHeight * mCurrentExpansion; - final float targetHeight = viewHeight * expansion; - final ValueAnimator animator = createExpansionAnimator(expansion); - if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) { - // Hides the bouncer, i.e., fully expands the space above the bouncer. - mFlingAnimationUtilsClosing.apply(animator, currentHeight, targetHeight, velocity, - viewHeight); - } else { - // Shows the bouncer, i.e., fully collapses the space above the bouncer. - mFlingAnimationUtils.apply( - animator, currentHeight, targetHeight, velocity, viewHeight); - } - - animator.start(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt new file mode 100644 index 000000000000..00ca0082aacc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2024 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.ambient.touch + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.graphics.Rect +import android.graphics.Region +import android.util.Log +import android.view.GestureDetector +import android.view.GestureDetector.SimpleOnGestureListener +import android.view.InputEvent +import android.view.MotionEvent +import android.view.VelocityTracker +import androidx.annotation.VisibleForTesting +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger +import com.android.internal.widget.LockPatternUtils +import com.android.systemui.Flags +import com.android.systemui.ambient.touch.TouchHandler.TouchSession +import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule +import com.android.systemui.ambient.touch.scrim.ScrimController +import com.android.systemui.ambient.touch.scrim.ScrimManager +import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.ShadeExpansionChangeEvent +import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.wm.shell.animation.FlingAnimationUtils +import java.util.Optional +import javax.inject.Inject +import javax.inject.Named +import kotlin.math.abs +import kotlin.math.hypot +import kotlin.math.max +import kotlin.math.min + +/** Monitor for tracking touches on the DreamOverlay to bring up the bouncer. */ +class BouncerSwipeTouchHandler +@Inject +constructor( + private val scrimManager: ScrimManager, + private val centralSurfaces: Optional<CentralSurfaces>, + private val notificationShadeWindowController: NotificationShadeWindowController, + private val valueAnimatorCreator: ValueAnimatorCreator, + private val velocityTrackerFactory: VelocityTrackerFactory, + private val lockPatternUtils: LockPatternUtils, + private val userTracker: UserTracker, + @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING) + private val flingAnimationUtils: FlingAnimationUtils, + @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING) + private val flingAnimationUtilsClosing: FlingAnimationUtils, + @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION) + private val bouncerZoneScreenPercentage: Float, + @param:Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) + private val minBouncerZoneScreenPercentage: Float, + private val uiEventLogger: UiEventLogger, + private val activityStarter: ActivityStarter +) : TouchHandler { + /** An interface for creating ValueAnimators. */ + interface ValueAnimatorCreator { + /** Creates [ValueAnimator]. */ + fun create(start: Float, finish: Float): ValueAnimator + } + + /** An interface for obtaining VelocityTrackers. */ + interface VelocityTrackerFactory { + /** Obtains [VelocityTracker]. */ + fun obtain(): VelocityTracker? + } + + private var currentScrimController: ScrimController? = null + private var currentExpansion = 0f + private var velocityTracker: VelocityTracker? = null + private var capture: Boolean? = null + private var expanded: Boolean = false + private var touchSession: TouchSession? = null + private val scrimManagerCallback = + ScrimManager.Callback { controller -> + currentScrimController?.reset() + + currentScrimController = controller + } + private val onGestureListener: GestureDetector.OnGestureListener = + object : SimpleOnGestureListener() { + override fun onScroll( + e1: MotionEvent?, + e2: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + if (capture == null) { + capture = + if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) { + (abs(distanceY.toDouble()) > abs(distanceX.toDouble()) && distanceY > 0) + } else { + // If the user scrolling favors a vertical direction, begin capturing + // scrolls. + abs(distanceY.toDouble()) > abs(distanceX.toDouble()) + } + if (capture == true) { + // reset expanding + expanded = false + // Since the user is dragging the bouncer up, set scrimmed to false. + currentScrimController?.show() + } + } + if (capture != true) { + return false + } + + if (!centralSurfaces.isPresent) { + return true + } + + e1?.apply outer@{ + // Don't set expansion for downward scroll. + if (y < e2.y) { + return true + } + + // If scrolling up and keyguard is not locked, dismiss both keyguard and the + // dream since there's no bouncer to show. + if (y > e2.y && !lockPatternUtils.isSecure(userTracker.userId)) { + activityStarter.executeRunnableDismissingKeyguard( + { centralSurfaces.get().awakenDreams() }, + /* cancelAction= */ null, + /* dismissShade= */ true, + /* afterKeyguardGone= */ true, + /* deferred= */ false + ) + return true + } + + // For consistency, we adopt the expansion definition found in the + // PanelViewController. In this case, expansion refers to the view above the + // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer + // is fully hidden at full expansion (1) and fully visible when fully collapsed + // (0). + touchSession?.apply { + val screenTravelPercentage = + (abs((this@outer.y - e2.y).toDouble()) / getBounds().height()).toFloat() + setPanelExpansion(1 - screenTravelPercentage) + } + } + + return true + } + } + + private fun setPanelExpansion(expansion: Float) { + currentExpansion = expansion + val event = + ShadeExpansionChangeEvent( + /* fraction= */ currentExpansion, + /* expanded= */ expanded, + /* tracking= */ true + ) + currentScrimController?.expand(event) + } + + @VisibleForTesting + enum class DreamEvent(private val mId: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "The screensaver has been swiped up.") DREAM_SWIPED(988), + @UiEvent(doc = "The bouncer has become fully visible over dream.") + DREAM_BOUNCER_FULLY_VISIBLE(1056); + + override fun getId(): Int { + return mId + } + } + + override fun getTouchInitiationRegion(bounds: Rect, region: Region, exclusionRect: Rect?) { + val width = bounds.width() + val height = bounds.height() + val minAllowableBottom = Math.round(height * (1 - minBouncerZoneScreenPercentage)) + val normalRegion = + Rect(0, Math.round(height * (1 - bouncerZoneScreenPercentage)), width, height) + if (exclusionRect != null) { + val lowestBottom = + min(max(0.0, exclusionRect.bottom.toDouble()), minAllowableBottom.toDouble()) + .toInt() + normalRegion.top = max(normalRegion.top.toDouble(), lowestBottom.toDouble()).toInt() + } + region.union(normalRegion) + } + + override fun onSessionStart(session: TouchSession) { + velocityTracker = velocityTrackerFactory.obtain() + touchSession = session + velocityTracker?.apply { clear() } + if (!Flags.communalBouncerDoNotModifyPluginOpen()) { + notificationShadeWindowController.setForcePluginOpen(true, this) + } + scrimManager.addCallback(scrimManagerCallback) + currentScrimController = scrimManager.currentController + session.registerCallback { + velocityTracker?.apply { recycle() } + velocityTracker = null + + scrimManager.removeCallback(scrimManagerCallback) + capture = null + touchSession = null + if (!Flags.communalBouncerDoNotModifyPluginOpen()) { + notificationShadeWindowController.setForcePluginOpen(false, this) + } + } + session.registerGestureListener(onGestureListener) + session.registerInputListener { ev: InputEvent -> onMotionEvent(ev) } + } + + private fun onMotionEvent(event: InputEvent) { + if (event !is MotionEvent) { + Log.e(TAG, "non MotionEvent received:$event") + return + } + val motionEvent = event + when (motionEvent.action) { + MotionEvent.ACTION_CANCEL, + MotionEvent.ACTION_UP -> { + touchSession?.apply { pop() } + // If we are not capturing any input, there is no need to consider animating to + // finish transition. + if (capture == null || !capture!!) { + return + } + + // We must capture the resulting velocities as resetMonitor() will clear these + // values. + velocityTracker!!.computeCurrentVelocity(1000) + val verticalVelocity = velocityTracker!!.yVelocity + val horizontalVelocity = velocityTracker!!.xVelocity + val velocityVector = + hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat() + expanded = !flingRevealsOverlay(verticalVelocity, velocityVector) + val expansion = + if (expanded!!) KeyguardBouncerConstants.EXPANSION_VISIBLE + else KeyguardBouncerConstants.EXPANSION_HIDDEN + + // Log the swiping up to show Bouncer event. + if (expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) { + uiEventLogger.log(DreamEvent.DREAM_SWIPED) + } + flingToExpansion(verticalVelocity, expansion) + } + else -> velocityTracker!!.addMovement(motionEvent) + } + } + + private fun createExpansionAnimator(targetExpansion: Float): ValueAnimator { + val animator = valueAnimatorCreator.create(currentExpansion, targetExpansion) + animator.addUpdateListener { animation: ValueAnimator -> + val expansionFraction = animation.animatedValue as Float + setPanelExpansion(expansionFraction) + } + if (targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) { + animator.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + uiEventLogger.log(DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE) + } + } + ) + } + return animator + } + + protected fun flingRevealsOverlay(velocity: Float, velocityVector: Float): Boolean { + // Fully expand the space above the bouncer, if the user has expanded the bouncer less + // than halfway or final velocity was positive, indicating a downward direction. + return if (abs(velocityVector.toDouble()) < flingAnimationUtils.minVelocityPxPerSecond) { + currentExpansion > FLING_PERCENTAGE_THRESHOLD + } else { + velocity > 0 + } + } + + protected fun flingToExpansion(velocity: Float, expansion: Float) { + if (!centralSurfaces.isPresent) { + return + } + + // Don't set expansion if the user doesn't have a pin/password set. + if (!lockPatternUtils.isSecure(userTracker.userId)) { + return + } + + touchSession?.apply { + // The animation utils deal in pixel units, rather than expansion height. + val viewHeight = getBounds().height().toFloat() + val currentHeight = viewHeight * currentExpansion + val targetHeight = viewHeight * expansion + val animator = createExpansionAnimator(expansion) + if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) { + // Hides the bouncer, i.e., fully expands the space above the bouncer. + flingAnimationUtilsClosing.apply( + animator, + currentHeight, + targetHeight, + velocity, + viewHeight + ) + } else { + // Shows the bouncer, i.e., fully collapses the space above the bouncer. + flingAnimationUtils.apply( + animator, + currentHeight, + targetHeight, + velocity, + viewHeight + ) + } + animator.start() + } + } + + companion object { + const val FLING_PERCENTAGE_THRESHOLD = 0.5f + private const val TAG = "BouncerSwipeTouchHandler" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java deleted file mode 100644 index baca9594dd2f..000000000000 --- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2024 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.ambient.touch; - -import static com.android.systemui.ambient.touch.dagger.ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT; - -import android.app.DreamManager; -import android.graphics.Rect; -import android.graphics.Region; -import android.view.GestureDetector; -import android.view.MotionEvent; - -import androidx.annotation.NonNull; - -import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor; -import com.android.systemui.shade.ShadeViewController; -import com.android.systemui.statusbar.phone.CentralSurfaces; - -import java.util.Optional; - -import javax.inject.Inject; -import javax.inject.Named; - -/** - * {@link ShadeTouchHandler} is responsible for handling swipe down gestures over dream - * to bring down the shade. - */ -public class ShadeTouchHandler implements TouchHandler { - private final Optional<CentralSurfaces> mSurfaces; - private final ShadeViewController mShadeViewController; - private final DreamManager mDreamManager; - private final int mInitiationHeight; - private final CommunalSettingsInteractor - mCommunalSettingsInteractor; - - /** - * Tracks whether or not we are capturing a given touch. Will be null before and after a touch. - */ - private Boolean mCapture; - - @Inject - ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces, - ShadeViewController shadeViewController, - DreamManager dreamManager, - CommunalSettingsInteractor communalSettingsInteractor, - @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) { - mSurfaces = centralSurfaces; - mShadeViewController = shadeViewController; - mDreamManager = dreamManager; - mCommunalSettingsInteractor = communalSettingsInteractor; - mInitiationHeight = initiationHeight; - } - - @Override - public void onSessionStart(TouchSession session) { - if (mSurfaces.isEmpty()) { - session.pop(); - return; - } - - session.registerCallback(() -> mCapture = null); - - session.registerInputListener(ev -> { - if (ev instanceof MotionEvent) { - if (mCapture != null && mCapture) { - sendTouchEvent((MotionEvent) ev); - } - if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP - || ((MotionEvent) ev).getAction() == MotionEvent.ACTION_CANCEL) { - session.pop(); - } - } - }); - - session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX, - float distanceY) { - if (mCapture == null) { - // Only capture swipes that are going downwards. - mCapture = Math.abs(distanceY) > Math.abs(distanceX) && distanceY < 0; - if (mCapture) { - // Send the initial touches over, as the input listener has already - // processed these touches. - sendTouchEvent(e1); - sendTouchEvent(e2); - } - } - return mCapture; - } - - @Override - public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX, - float velocityY) { - return mCapture; - } - }); - } - - private void sendTouchEvent(MotionEvent event) { - if (mCommunalSettingsInteractor.isCommunalFlagEnabled() && !mDreamManager.isDreaming()) { - // Send touches to central surfaces only when on the glanceable hub while not dreaming. - // While sending touches where while dreaming will open the shade, the shade - // while closing if opened then closed in the same gesture. - mSurfaces.get().handleExternalShadeWindowTouch(event); - } else { - // Send touches to the shade view when dreaming. - mShadeViewController.handleExternalTouch(event); - } - } - - @Override - public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) { - final Rect outBounds = new Rect(bounds); - outBounds.inset(0, 0, 0, outBounds.height() - mInitiationHeight); - region.op(outBounds, Region.Op.UNION); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt new file mode 100644 index 000000000000..87f02aacdb82 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2024 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.ambient.touch + +import android.app.DreamManager +import android.graphics.Rect +import android.graphics.Region +import android.view.GestureDetector.SimpleOnGestureListener +import android.view.InputEvent +import android.view.MotionEvent +import com.android.systemui.ambient.touch.TouchHandler.TouchSession +import com.android.systemui.ambient.touch.dagger.ShadeModule +import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor +import com.android.systemui.shade.ShadeViewController +import com.android.systemui.statusbar.phone.CentralSurfaces +import java.util.Optional +import javax.inject.Inject +import javax.inject.Named +import kotlin.math.abs + +/** + * [ShadeTouchHandler] is responsible for handling swipe down gestures over dream to bring down the + * shade. + */ +class ShadeTouchHandler +@Inject +constructor( + private val surfaces: Optional<CentralSurfaces>, + private val shadeViewController: ShadeViewController, + private val dreamManager: DreamManager, + private val communalSettingsInteractor: CommunalSettingsInteractor, + @param:Named(ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) + private val initiationHeight: Int +) : TouchHandler { + /** + * Tracks whether or not we are capturing a given touch. Will be null before and after a touch. + */ + private var capture: Boolean? = null + + override fun onSessionStart(session: TouchSession) { + if (surfaces.isEmpty) { + session.pop() + return + } + session.registerCallback { capture = null } + session.registerInputListener { ev: InputEvent? -> + if (ev is MotionEvent) { + if (capture == true) { + sendTouchEvent(ev) + } + if (ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL) { + session.pop() + } + } + } + session.registerGestureListener( + object : SimpleOnGestureListener() { + override fun onScroll( + e1: MotionEvent?, + e2: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + if (capture == null) { + // Only capture swipes that are going downwards. + capture = + abs(distanceY.toDouble()) > abs(distanceX.toDouble()) && distanceY < 0 + if (capture == true) { + // Send the initial touches over, as the input listener has already + // processed these touches. + e1?.apply { sendTouchEvent(this) } + sendTouchEvent(e2) + } + } + return capture == true + } + + override fun onFling( + e1: MotionEvent?, + e2: MotionEvent, + velocityX: Float, + velocityY: Float + ): Boolean { + return capture == true + } + } + ) + } + + private fun sendTouchEvent(event: MotionEvent) { + if (communalSettingsInteractor.isCommunalFlagEnabled() && !dreamManager.isDreaming) { + // Send touches to central surfaces only when on the glanceable hub while not dreaming. + // While sending touches where while dreaming will open the shade, the shade + // while closing if opened then closed in the same gesture. + surfaces.get().handleExternalShadeWindowTouch(event) + } else { + // Send touches to the shade view when dreaming. + shadeViewController.handleExternalTouch(event) + } + } + + override fun getTouchInitiationRegion(bounds: Rect, region: Region, exclusionRect: Rect) { + val outBounds = Rect(bounds) + outBounds.inset(0, 0, 0, outBounds.height() - initiationHeight) + region.op(outBounds, Region.Op.UNION) + } +} |