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

Commit bf09e036 authored by Ats Jenk's avatar Ats Jenk Committed by Android (Google) Code Review
Browse files

Merge "Have bubble bar unstash gesture track finger" into main

parents e1b7ca56 26d74b9e
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -108,6 +108,7 @@ import com.android.launcher3.taskbar.TaskbarTranslationController.TransitionCall
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
import com.android.launcher3.taskbar.bubbles.BubbleBarPinController;
import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController;
import com.android.launcher3.taskbar.bubbles.BubbleBarView;
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
@@ -279,9 +280,11 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
        BubbleBarController.onTaskbarRecreated();
        if (BubbleBarController.isBubbleBarEnabled() && bubbleBarView != null) {
            Optional<BubbleStashedHandleViewController> bubbleHandleController = Optional.empty();
            Optional<BubbleBarSwipeController> bubbleBarSwipeController = Optional.empty();
            if (isTransientTaskbar) {
                bubbleHandleController = Optional.of(
                        new BubbleStashedHandleViewController(this, bubbleHandleView));
                bubbleBarSwipeController = Optional.of(new BubbleBarSwipeController(this));
            }
            TaskbarHotseatDimensionsProvider dimensionsProvider =
                    new DeviceProfileDimensionsProviderAdapter(this);
@@ -299,6 +302,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
                            () -> DisplayController.INSTANCE.get(this).getInfo().currentSize),
                    new BubblePinController(this, mDragLayer,
                            () -> DisplayController.INSTANCE.get(this).getInfo().currentSize),
                    bubbleBarSwipeController,
                    new BubbleCreator(this)
            ));
        }
+203 −0
Original line number Diff line number Diff line
/*
 * 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.launcher3.taskbar.bubbles

import android.animation.ValueAnimator
import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.core.animation.doOnEnd
import androidx.dynamicanimation.animation.SpringForce
import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.anim.SpringAnimationBuilder
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.TaskbarThresholdUtils
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
import com.android.launcher3.touch.OverScroll

/** Handle swipe events on the bubble bar and handle */
class BubbleBarSwipeController {

    private val context: Context

    private var bubbleStashedHandleViewController: BubbleStashedHandleViewController? = null
    private var bubbleBarViewController: BubbleBarViewController? = null
    private var bubbleStashController: BubbleStashController? = null

    private var springAnimation: ValueAnimator? = null
    private val animatedSwipeTranslation = AnimatedFloat(this::onSwipeUpdate)

    private val unstashThreshold: Int
    private val expandThreshold: Int
    private val maxOverscroll: Int

    private var swipeState: SwipeState = SwipeState()

    constructor(tac: TaskbarActivityContext) : this(tac, DefaultDimensionProvider(tac))

    @VisibleForTesting
    constructor(context: Context, dimensionProvider: DimensionProvider) {
        this.context = context
        unstashThreshold = dimensionProvider.unstashThreshold
        expandThreshold = dimensionProvider.expandThreshold
        maxOverscroll = dimensionProvider.maxOverscroll
    }

    fun init(bubbleControllers: BubbleControllers) {
        bubbleStashedHandleViewController =
            bubbleControllers.bubbleStashedHandleViewController.orElse(null)
        bubbleBarViewController = bubbleControllers.bubbleBarViewController
        bubbleStashController = bubbleControllers.bubbleStashController
    }

    /** Start tracking a new swipe gesture */
    fun start() {
        if (springAnimation != null) reset()
        val stashed = bubbleStashController?.isStashed ?: false
        val barVisible = bubbleStashController?.isBubbleBarVisible() ?: false
        val expanded = bubbleBarViewController?.isExpanded ?: false

        swipeState =
            SwipeState(
                stashedOnStart = stashed,
                collapsedOnStart = !stashed && barVisible && !expanded,
                expandedOnStart = expanded,
            )
    }

    /** Update swipe distance to [dy] */
    fun swipeTo(dy: Float) {
        // Only handle swipe up and stashed or collapsed bar
        if (dy > 0 || swipeState.expandedOnStart) return

        animatedSwipeTranslation.updateValue(dy)

        val prevState = swipeState
        // We can pass unstash threshold once per gesture, keep it true if it happened once
        val passedUnstashThreshold = isUnstash(dy) || prevState.passedUnstashThreshold
        // Expand happens at the end of the gesture, always keep the current value
        val passedExpandThreshold = isExpand(dy)

        if (
            passedUnstashThreshold != prevState.passedUnstashThreshold ||
                passedExpandThreshold != prevState.passedExpandThreshold
        ) {
            swipeState =
                swipeState.copy(
                    passedUnstashThreshold = passedUnstashThreshold,
                    passedExpandThreshold = passedExpandThreshold,
                )
        }

        if (
            swipeState.stashedOnStart &&
                swipeState.passedUnstashThreshold &&
                !prevState.passedUnstashThreshold
        ) {
            bubbleStashController?.showBubbleBar(expandBubbles = false)
        }
    }

    /** Finish tracking swipe gesture. Animate views back to resting state */
    fun finish() {
        if (swipeState.passedExpandThreshold) {
            bubbleStashController?.showBubbleBar(expandBubbles = true)
        }
        springToRest()
    }

    /** Returns `true` if we are tracking a swipe gesture */
    fun isSwipeGesture(): Boolean {
        return swipeState.passedUnstashThreshold || swipeState.passedExpandThreshold
    }

    private fun isUnstash(dy: Float): Boolean {
        return dy < -unstashThreshold
    }

    private fun isExpand(dy: Float): Boolean {
        return dy < -expandThreshold
    }

    private fun reset() {
        springAnimation?.let {
            if (it.isRunning) {
                it.removeAllListeners()
                it.cancel()
                animatedSwipeTranslation.updateValue(0f)
            }
        }
        springAnimation = null
        swipeState = SwipeState()
    }

    private fun onSwipeUpdate(value: Float) {
        val dampedSwipe = -OverScroll.dampedScroll(-value, maxOverscroll).toFloat()
        bubbleStashedHandleViewController?.setTranslationYForSwipe(dampedSwipe)
        bubbleBarViewController?.setTranslationYForSwipe(dampedSwipe)
    }

    private fun springToRest() {
        springAnimation =
            SpringAnimationBuilder(context)
                .setStartValue(animatedSwipeTranslation.value)
                .setEndValue(0f)
                .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
                .setStiffness(SpringForce.STIFFNESS_LOW)
                .build(animatedSwipeTranslation, AnimatedFloat.VALUE)
                .also { it.doOnEnd { reset() } }
        springAnimation?.start()
    }

    internal data class SwipeState(
        val stashedOnStart: Boolean = false,
        val collapsedOnStart: Boolean = false,
        val expandedOnStart: Boolean = false,
        val passedUnstashThreshold: Boolean = false,
        val passedExpandThreshold: Boolean = false,
    )

    /** Allows overriding the dimension provider for testing */
    @VisibleForTesting
    interface DimensionProvider {
        val unstashThreshold: Int
        val expandThreshold: Int
        val maxOverscroll: Int
    }

    private class DefaultDimensionProvider(taskbarActivityContext: TaskbarActivityContext) :
        DimensionProvider {
        override val unstashThreshold: Int
        override val expandThreshold: Int
        override val maxOverscroll: Int

        init {
            val resources = taskbarActivityContext.resources
            unstashThreshold =
                TaskbarThresholdUtils.getFromNavThreshold(
                    resources,
                    taskbarActivityContext.deviceProfile,
                )
            // TODO(325673340): review threshold with ux
            expandThreshold =
                TaskbarThresholdUtils.getAppWindowThreshold(
                    resources,
                    taskbarActivityContext.deviceProfile,
                )
            maxOverscroll = taskbarActivityContext.deviceProfile.heightPx - unstashThreshold
        }
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ public class BubbleControllers {
    public final BubbleDismissController bubbleDismissController;
    public final BubbleBarPinController bubbleBarPinController;
    public final BubblePinController bubblePinController;
    public final Optional<BubbleBarSwipeController> bubbleBarSwipeController;
    public final BubbleCreator bubbleCreator;

    private final RunnableList mPostInitRunnables = new RunnableList();
@@ -58,6 +59,7 @@ public class BubbleControllers {
            BubbleDismissController bubbleDismissController,
            BubbleBarPinController bubbleBarPinController,
            BubblePinController bubblePinController,
            Optional<BubbleBarSwipeController> bubbleBarSwipeController,
            BubbleCreator bubbleCreator) {
        this.bubbleBarController = bubbleBarController;
        this.bubbleBarViewController = bubbleBarViewController;
@@ -67,6 +69,7 @@ public class BubbleControllers {
        this.bubbleDismissController = bubbleDismissController;
        this.bubbleBarPinController = bubbleBarPinController;
        this.bubblePinController = bubblePinController;
        this.bubbleBarSwipeController = bubbleBarSwipeController;
        this.bubbleCreator = bubbleCreator;
    }

@@ -111,6 +114,7 @@ public class BubbleControllers {
        bubbleDismissController.init(/* bubbleControllers = */ this);
        bubbleBarPinController.init(this, bubbleBarLocationListeners);
        bubblePinController.init(this);
        bubbleBarSwipeController.ifPresent(c -> c.init(this));

        mPostInitRunnables.executeAllAndDestroy();
    }
+1 −4
Original line number Diff line number Diff line
@@ -44,8 +44,6 @@ import android.util.PathParser;
import android.view.LayoutInflater;
import android.view.ViewGroup;

import androidx.appcompat.content.res.AppCompatResources;

import com.android.internal.graphics.ColorUtils;
import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapInfo;
@@ -196,8 +194,7 @@ public class BubbleCreator {
    }

    private Bitmap createOverflowBitmap() {
        Drawable iconDrawable = AppCompatResources.getDrawable(mContext,
                R.drawable.bubble_ic_overflow_button);
        Drawable iconDrawable = mContext.getDrawable(R.drawable.bubble_ic_overflow_button);

        final TypedArray ta = mContext.obtainStyledAttributes(
                new int[]{
+22 −11
Original line number Diff line number Diff line
@@ -23,10 +23,12 @@ import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.ViewConfiguration;

import androidx.annotation.Nullable;

import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.bubbles.BubbleBarSwipeController;
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
import com.android.launcher3.taskbar.bubbles.BubbleDragController;
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -40,10 +42,11 @@ public class BubbleBarInputConsumer implements InputConsumer {

    private final BubbleStashController mBubbleStashController;
    private final BubbleBarViewController mBubbleBarViewController;
    private final BubbleDragController mBubbleDragController;
    @Nullable
    private final BubbleBarSwipeController mBubbleBarSwipeController;
    private final InputMonitorCompat mInputMonitorCompat;

    private boolean mSwipeUpOnBubbleHandle;
    private boolean mPilfered;
    private boolean mPassedTouchSlop;
    private boolean mStashedOrCollapsedOnDown;

@@ -57,7 +60,8 @@ public class BubbleBarInputConsumer implements InputConsumer {
            InputMonitorCompat inputMonitorCompat) {
        mBubbleStashController = bubbleControllers.bubbleStashController;
        mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
        mBubbleDragController = bubbleControllers.bubbleDragController;
        mBubbleBarSwipeController = bubbleControllers.bubbleBarSwipeController.orElse(null);

        mInputMonitorCompat = inputMonitorCompat;
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mTimeForTap = ViewConfiguration.getTapTimeout();
@@ -77,6 +81,9 @@ public class BubbleBarInputConsumer implements InputConsumer {
                mDownPos.set(ev.getX(), ev.getY());
                mLastPos.set(mDownPos);
                mStashedOrCollapsedOnDown = mBubbleStashController.isStashed() || isCollapsed();
                if (mBubbleBarSwipeController != null) {
                    mBubbleBarSwipeController.start();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int pointerIndex = ev.findPointerIndex(mActivePointerId);
@@ -90,11 +97,10 @@ public class BubbleBarInputConsumer implements InputConsumer {
                if (!mPassedTouchSlop) {
                    mPassedTouchSlop = Math.abs(dY) > mTouchSlop || Math.abs(dX) > mTouchSlop;
                }
                if (mStashedOrCollapsedOnDown && !mSwipeUpOnBubbleHandle && mPassedTouchSlop) {
                    boolean verticalGesture = Math.abs(dY) > Math.abs(dX);
                    if (verticalGesture && !mBubbleDragController.isDragging()) {
                        mSwipeUpOnBubbleHandle = true;
                        mBubbleStashController.showBubbleBar(/* expandBubbles= */ true);
                if (mBubbleBarSwipeController != null) {
                    mBubbleBarSwipeController.swipeTo(dY);
                    if (!mPilfered && mBubbleBarSwipeController.isSwipeGesture()) {
                        mPilfered = true;
                        // Bubbles is handling the swipe so make sure no one else gets it.
                        TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
                        mInputMonitorCompat.pilferPointers();
@@ -102,8 +108,10 @@ public class BubbleBarInputConsumer implements InputConsumer {
                }
                break;
            case MotionEvent.ACTION_UP:
                boolean swipeUpOnBubbleHandle = mBubbleBarSwipeController != null
                        && mBubbleBarSwipeController.isSwipeGesture();
                boolean isWithinTapTime = ev.getEventTime() - ev.getDownTime() <= mTimeForTap;
                if (isWithinTapTime && !mSwipeUpOnBubbleHandle && !mPassedTouchSlop
                if (isWithinTapTime && !swipeUpOnBubbleHandle && !mPassedTouchSlop
                        && mStashedOrCollapsedOnDown) {
                    // Taps on the handle / collapsed state should open the bar
                    mBubbleStashController.showBubbleBar(/* expandBubbles= */ true);
@@ -116,8 +124,11 @@ public class BubbleBarInputConsumer implements InputConsumer {
    }

    private void cleanupAfterMotionEvent() {
        if (mBubbleBarSwipeController != null) {
            mBubbleBarSwipeController.finish();
        }
        mPassedTouchSlop = false;
        mSwipeUpOnBubbleHandle = false;
        mPilfered = false;
    }

    private boolean isCollapsed() {
Loading