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

Commit 436101dc authored by Bryce Lee's avatar Bryce Lee
Browse files

Show bouncer over dream.

This changelist allows the keyguard bouncer to be dragged up from the bottom of the screen over dreams. This interaction is accomplished by controlling the expansion of the keyguard view directly, allowing it to appear independently of the rest of the NotificationViewPanel.

Test: atest BouncerSwipeTouchHandlerTest
Bug: 211506329
Change-Id: I513d64e26b634b4e15bf1ba00af5e36e6df70eee
parent d7bd0f13
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -1371,4 +1371,9 @@
    <item name="dream_overlay_complication_guide_top_percent" format="float" type="dimen">
        0.10
    </item>

    <!-- The percentage of the screen from which a swipe can start to reveal the bouncer. -->
    <item name="dream_overlay_bouncer_start_region_screen_percentage" format="float" type="dimen">
        .2
    </item>
</resources>
+0 −10
Original line number Diff line number Diff line
@@ -34,7 +34,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayContainerView;
import com.android.systemui.dreams.DreamOverlayStatusBarView;
import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
@@ -44,7 +43,6 @@ import javax.inject.Named;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;

/** Dagger module for {@link DreamOverlayComponent}. */
@Module
@@ -149,12 +147,4 @@ public abstract class DreamOverlayModule {
    static Lifecycle providesLifecycle(LifecycleOwner lifecycleOwner) {
        return lifecycleOwner.getLifecycle();
    }

    // TODO: This stub should be removed once there is a {@link DreamTouchHandler}
    // implementation present.
    @Provides
    @IntoSet
    static DreamTouchHandler provideDreamTouchHandler() {
        return session -> { };
    }
}
+273 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.dreams.touch;

import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING;
import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING;
import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION;

import android.animation.ValueAnimator;
import android.util.Log;
import android.view.GestureDetector;
import android.view.InputEvent;
import android.view.MotionEvent;
import android.view.VelocityTracker;

import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.wm.shell.animation.FlingAnimationUtils;

import javax.inject.Inject;
import javax.inject.Named;

/**
 * Monitor for tracking touches on the DreamOverlay to bring up the bouncer.
 */
public class BouncerSwipeTouchHandler implements DreamTouchHandler {
    /**
     * 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 float mBouncerZoneScreenPercentage;

    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
    private float mCurrentExpansion;
    private final StatusBar mStatusBar;

    private VelocityTracker mVelocityTracker;

    private final FlingAnimationUtils mFlingAnimationUtils;
    private final FlingAnimationUtils mFlingAnimationUtilsClosing;

    private Boolean mCapture;

    private TouchSession mTouchSession;

    private ValueAnimatorCreator mValueAnimatorCreator;

    private VelocityTrackerFactory mVelocityTrackerFactory;

    private final GestureDetector.OnGestureListener mOnGestureListener =
            new  GestureDetector.SimpleOnGestureListener() {
                boolean mTrack;
                boolean mBouncerPresent;

                @Override
                public boolean onDown(MotionEvent e) {
                    // We only consider gestures that originate from the lower portion of the
                    // screen.
                    final float displayHeight = mStatusBar.getDisplayHeight();

                    mBouncerPresent = mStatusBar.isBouncerShowing();

                    // The target zone is either at the top or bottom of the screen, dependent on
                    // whether the bouncer is present.
                    final float zonePercentage =
                            Math.abs(e.getY() - (mBouncerPresent ? 0 : displayHeight))
                                    / displayHeight;

                    mTrack =  zonePercentage < mBouncerZoneScreenPercentage;

                    // Never capture onDown. While this might lead to some false positive touches
                    // being sent to other windows/layers, this is necessary to make sure the
                    // proper touch event sequence is received by others in the event we do not
                    // consume the sequence here.
                    return false;
                }

                @Override
                public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
                        float distanceY) {
                    // Do not handle scroll gestures if not tracking touch events.
                    if (!mTrack) {
                        return false;
                    }

                    if (mCapture == null) {
                        // If the user scrolling favors a vertical direction, begin capturing
                        // scrolls.
                        mCapture = Math.abs(distanceY) > Math.abs(distanceX);

                        if (mCapture) {
                            // Since the user is dragging the bouncer up, set scrimmed to false.
                            mStatusBarKeyguardViewManager.showBouncer(false);
                        }
                    }

                    if (!mCapture) {
                        return false;
                    }

                    // 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()) / mStatusBar.getDisplayHeight());
                    setPanelExpansion(
                            mBouncerPresent ? screenTravelPercentage : 1 - screenTravelPercentage);

                    return true;
                }
            };

    private void setPanelExpansion(float expansion) {
        mCurrentExpansion = expansion;
        mStatusBarKeyguardViewManager.onPanelExpansionChanged(mCurrentExpansion, false, true);
    }

    @Inject
    public BouncerSwipeTouchHandler(
            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
            StatusBar statusBar,
            NotificationShadeWindowController notificationShadeWindowController,
            ValueAnimatorCreator valueAnimatorCreator,
            VelocityTrackerFactory velocityTrackerFactory,
            @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
                    FlingAnimationUtils flingAnimationUtils,
            @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
                    FlingAnimationUtils flingAnimationUtilsClosing,
            @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage) {
        mStatusBar = statusBar;
        mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
        mNotificationShadeWindowController = notificationShadeWindowController;
        mBouncerZoneScreenPercentage = swipeRegionPercentage;
        mFlingAnimationUtils = flingAnimationUtils;
        mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
        mValueAnimatorCreator = valueAnimatorCreator;
        mVelocityTrackerFactory = velocityTrackerFactory;
    }

    @Override
    public void onSessionStart(TouchSession session) {
        mVelocityTracker = mVelocityTrackerFactory.obtain();
        mTouchSession = session;
        mVelocityTracker.clear();
        mNotificationShadeWindowController.setForcePluginOpen(true, this);
        session.registerGestureListener(mOnGestureListener);
        session.registerInputListener(ev -> onMotionEvent(ev));

    }

    @Override
    public void onSessionEnd(TouchSession session) {
        mVelocityTracker.recycle();
        mCapture = null;
        mNotificationShadeWindowController.setForcePluginOpen(false, this);
    }

    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_UP:
                // 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);


                final float expansion = flingRevealsOverlay(verticalVelocity, velocityVector)
                            ? KeyguardBouncer.EXPANSION_HIDDEN : KeyguardBouncer.EXPANSION_VISIBLE;
                flingToExpansion(verticalVelocity, expansion);

                if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) {
                    mStatusBarKeyguardViewManager.reset(false);
                }
                mTouchSession.pop();
                break;
            default:
                mVelocityTracker.addMovement(motionEvent);
                break;
        }
    }

    private ValueAnimator createExpansionAnimator(float targetExpansion) {
        final ValueAnimator animator =
                mValueAnimatorCreator.create(mCurrentExpansion, targetExpansion);
        animator.addUpdateListener(
                animation -> {
                    setPanelExpansion((float) animation.getAnimatedValue());
                });
        return animator;
    }

    protected boolean flingRevealsOverlay(float velocity, float velocityVector) {
        // Fully expand if the user has expanded the bouncer less than halfway or final velocity was
        // positive, indicating an downward direction.
        if (Math.abs(velocityVector) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
            return mCurrentExpansion > FLING_PERCENTAGE_THRESHOLD;
        } else {
            return velocity > 0;
        }
    }

    protected void flingToExpansion(float velocity, float expansion) {
        final float viewHeight = mStatusBar.getDisplayHeight();
        final float currentHeight = viewHeight * mCurrentExpansion;
        final float targetHeight = viewHeight * expansion;

        final ValueAnimator animator = createExpansionAnimator(expansion);
        if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) {
            // The animation utils deal in pixel units, rather than expansion height.
            mFlingAnimationUtils.apply(animator, currentHeight, targetHeight, velocity, viewHeight);
        } else {
            mFlingAnimationUtilsClosing.apply(
                    animator, mCurrentExpansion, currentHeight, targetHeight, viewHeight);
        }

        animator.start();
    }
}
+128 −0
Original line number Diff line number Diff line
/*
 * 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.systemui.dreams.touch.dagger;

import android.animation.ValueAnimator;
import android.content.res.Resources;
import android.util.TypedValue;
import android.view.VelocityTracker;

import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler;
import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.statusbar.phone.PanelViewController;
import com.android.wm.shell.animation.FlingAnimationUtils;

import javax.inject.Named;
import javax.inject.Provider;

import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;

/**
 * This module captures the components associated with {@link BouncerSwipeTouchHandler}.
 */
@Module
public class BouncerSwipeModule {
    /**
     * The region, defined as the percentage of the screen, from which a touch gesture to start
     * swiping up to the bouncer can occur.
     */
    public static final String SWIPE_TO_BOUNCER_START_REGION = "swipe_to_bouncer_start_region";

    /**
     * The {@link android.view.animation.AnimationUtils} for animating the bouncer closing.
     */
    public static final String SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING =
            "swipe_to_bouncer_fling_animation_utils_closing";

    /**
     * The {@link android.view.animation.AnimationUtils} for animating the bouncer opening.
     */
    public static final String SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING =
                    "swipe_to_bouncer_fling_animation_utils_opening";

    /**
     * Provides {@link BouncerSwipeTouchHandler} for inclusion in touch handling over the dream.
     */
    @Provides
    @IntoSet
    public static DreamTouchHandler providesBouncerSwipeTouchHandler(
            BouncerSwipeTouchHandler touchHandler) {
        return touchHandler;
    }

    /**
     * Provides {@link android.view.animation.AnimationUtils} for closing.
     */
    @Provides
    @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
    public static FlingAnimationUtils providesSwipeToBouncerFlingAnimationUtilsClosing(
            Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider) {
        return flingAnimationUtilsBuilderProvider.get()
                .reset()
                .setMaxLengthSeconds(PanelViewController.FLING_CLOSING_MAX_LENGTH_SECONDS)
                .setSpeedUpFactor(PanelViewController.FLING_SPEED_UP_FACTOR)
                .build();
    }

    /**
     * Provides {@link android.view.animation.AnimationUtils} for opening.
     */
    @Provides
    @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
    public static FlingAnimationUtils providesSwipeToBouncerFlingAnimationUtilsOpening(
            Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider) {
        return flingAnimationUtilsBuilderProvider.get()
                .reset()
                .setMaxLengthSeconds(PanelViewController.FLING_MAX_LENGTH_SECONDS)
                .setSpeedUpFactor(PanelViewController.FLING_SPEED_UP_FACTOR)
                .build();
    }

    /**
     * Provides the region to start swipe gestures from.
     */
    @Provides
    @Named(SWIPE_TO_BOUNCER_START_REGION)
    public static float providesSwipeToBouncerStartRegion(@Main Resources resources) {
        TypedValue typedValue = new TypedValue();
        resources.getValue(R.dimen.dream_overlay_bouncer_start_region_screen_percentage,
                typedValue, true);
        return typedValue.getFloat();
    }

    /**
     * Provides the default {@link BouncerSwipeTouchHandler.ValueAnimatorCreator}, which is simply
     * a wrapper around {@link ValueAnimator}.
     */
    @Provides
    public static BouncerSwipeTouchHandler.ValueAnimatorCreator providesValueAnimatorCreator() {
        return (start, finish) -> ValueAnimator.ofFloat(start, finish);
    }

    /**
     * Provides the default {@link BouncerSwipeTouchHandler.VelocityTrackerFactory}. which is a
     * passthrough to {@link android.view.VelocityTracker}.
     */
    @Provides
    public static BouncerSwipeTouchHandler.VelocityTrackerFactory providesVelocityTrackerFactory() {
        return () -> VelocityTracker.obtain();
    }
}
+4 −2
Original line number Diff line number Diff line
@@ -21,7 +21,9 @@ import dagger.Module;
/**
 * {@link DreamTouchModule} encapsulates dream touch-related components.
 */
@Module(subcomponents = {
@Module(includes = {
            BouncerSwipeModule.class,
        }, subcomponents = {
            InputSessionComponent.class,
})
public interface DreamTouchModule {
Loading