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

Commit 8d8d6776 authored by Zak Cohen's avatar Zak Cohen
Browse files

Add visual indicator for back navigation gesture.

Test: manual

Change-Id: I549c8fd8ab40175cd160ce2d111a2fb083cb9611
parent 7d2d23ad
Loading
Loading
Loading
Loading
+221 −2
Original line number Diff line number Diff line
@@ -16,9 +16,17 @@

package com.android.systemui.statusbar.phone;

import android.animation.ObjectAnimator;
import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.util.FloatProperty;
import android.util.MathUtils;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;

@@ -27,6 +35,71 @@ import com.android.systemui.R;
public class NavigationBarEdgePanel extends View {
    private static final String TAG = "NavigationBarEdgePanel";

    // TODO: read from resources once drawing is finalized.
    private static final boolean SHOW_PROTECTION_STROKE = true;
    private static final int PROTECTION_COLOR = 0xffc0c0c0;
    private static final int STROKE_COLOR = 0xffe5e5e5;
    private static final int PROTECTION_WIDTH_PX = 4;
    private static final int BASE_EXTENT = 32;
    private static final int ARROW_HEIGHT_DP = 32;
    private static final int POINT_EXTENT_DP = 8;
    private static final int ARROW_THICKNESS_DP = 4;
    private static final float TRACK_LENGTH_MULTIPLIER = 1.5f;
    private static final float START_POINTING_RATIO = 0.3f;
    private static final float POINTEDNESS_BEFORE_SNAP_RATIO = 0.4f;
    private static final int ANIM_DURATION_MS = 150;

    private final Paint mPaint = new Paint();
    private final Paint mProtectionPaint = new Paint();

    private final ObjectAnimator mEndAnimator;
    private final ObjectAnimator mLegAnimator;

    private final float mDensity;
    private final float mBaseExtent;
    private final float mPointExtent;
    private final float mHeight;
    private final float mStrokeThickness;
    private final boolean mIsLeftPanel;

    private float mStartY;
    private float mStartX;

    private boolean mGestureDetected;
    private boolean mArrowsPointLeft;
    private float mGestureLength;
    private float mLegProgress;
    private float mDragProgress;

    // How much the "legs" of the back arrow have proceeded from being a line to an arrow.
    private static final FloatProperty<NavigationBarEdgePanel> LEG_PROGRESS =
            new FloatProperty<NavigationBarEdgePanel>("legProgress") {
        @Override
        public void setValue(NavigationBarEdgePanel object, float value) {
            object.setLegProgress(value);
        }

        @Override
        public Float get(NavigationBarEdgePanel object) {
            return object.getLegProgress();
        }
    };

    // How far across the view the arrow should be drawn.
    private static final FloatProperty<NavigationBarEdgePanel> DRAG_PROGRESS =
            new FloatProperty<NavigationBarEdgePanel>("dragProgress") {

                @Override
                public void setValue(NavigationBarEdgePanel object, float value) {
                    object.setDragProgress(value);
                }

                @Override
                public Float get(NavigationBarEdgePanel object) {
                    return object.getDragProgress();
                }
            };

    public static NavigationBarEdgePanel create(@NonNull Context context, int width, int height,
            int gravity) {
        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
@@ -40,13 +113,43 @@ public class NavigationBarEdgePanel extends View {
        lp.setTitle(TAG + context.getDisplayId());
        lp.accessibilityTitle = context.getString(R.string.nav_bar_edge_panel);
        lp.windowAnimations = 0;
        NavigationBarEdgePanel panel = new NavigationBarEdgePanel(context);
        NavigationBarEdgePanel panel = new NavigationBarEdgePanel(
                context, (gravity & Gravity.LEFT) != 0);
        panel.setLayoutParams(lp);
        return panel;
    }

    private NavigationBarEdgePanel(Context context) {
    private NavigationBarEdgePanel(Context context, boolean isLeftPanel) {
        super(context);

        mEndAnimator = ObjectAnimator.ofFloat(this, DRAG_PROGRESS, 1f);
        mEndAnimator.setAutoCancel(true);
        mEndAnimator.setDuration(ANIM_DURATION_MS);

        mLegAnimator = ObjectAnimator.ofFloat(this, LEG_PROGRESS, 1f);
        mLegAnimator.setAutoCancel(true);
        mLegAnimator.setDuration(ANIM_DURATION_MS);

        mDensity = context.getResources().getDisplayMetrics().density;

        mBaseExtent = dp(BASE_EXTENT);
        mHeight = dp(ARROW_HEIGHT_DP);
        mPointExtent = dp(POINT_EXTENT_DP);
        mStrokeThickness = dp(ARROW_THICKNESS_DP);

        mPaint.setStrokeWidth(mStrokeThickness);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setColor(STROKE_COLOR);
        mPaint.setAntiAlias(true);

        mProtectionPaint.setStrokeWidth(mStrokeThickness + PROTECTION_WIDTH_PX);
        mProtectionPaint.setStrokeCap(Paint.Cap.ROUND);
        mProtectionPaint.setColor(PROTECTION_COLOR);
        mProtectionPaint.setAntiAlias(true);

        // Both panels arrow point the same way
        mArrowsPointLeft = getLayoutDirection() == LAYOUT_DIRECTION_LTR;
        mIsLeftPanel = isLeftPanel;
    }

    public void setWindowFlag(int flags, boolean enable) {
@@ -62,6 +165,58 @@ public class NavigationBarEdgePanel extends View {
        updateLayout(lp);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN : {
                show(event.getX(), event.getY());
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                handleNewSwipePoint(event.getX());
                break;
            }
            // Fall through
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL: {
                hide();
                break;
            }
        }

        return false;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        float edgeOffset = mBaseExtent * mDragProgress - mStrokeThickness;
        float animatedOffset = mPointExtent * mLegProgress;
        canvas.save();
        canvas.translate(
                mIsLeftPanel ? edgeOffset : getWidth() - edgeOffset,
                mStartY - mHeight * 0.5f);

        float outsideX = mArrowsPointLeft ? animatedOffset : 0;
        float middleX = mArrowsPointLeft ? 0 : animatedOffset;

        if (SHOW_PROTECTION_STROKE) {
            canvas.drawLine(outsideX, 0, middleX, mHeight * 0.5f, mProtectionPaint);
            canvas.drawLine(middleX, mHeight * 0.5f, outsideX, mHeight, mProtectionPaint);
        }

        canvas.drawLine(outsideX, 0, middleX, mHeight * 0.5f, mPaint);
        canvas.drawLine(middleX, mHeight * 0.5f, outsideX, mHeight, mPaint);
        canvas.restore();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        // TODO: read the gesture length from the nav controller.
        mGestureLength = getWidth();
    }

    public void setDimensions(int width, int height) {
        final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams();
        if (lp.width != width || lp.height != height) {
@@ -71,8 +226,72 @@ public class NavigationBarEdgePanel extends View {
        }
    }

    private void setLegProgress(float progress) {
        mLegProgress = progress;
        invalidate();
    }

    private float getLegProgress() {
        return mLegProgress;
    }

    private void setDragProgress(float dragProgress) {
        mDragProgress = dragProgress;
        invalidate();
    }

    private float getDragProgress() {
        return mDragProgress;
    }

    private void hide() {
        animate().alpha(0f).setDuration(ANIM_DURATION_MS);
    }

    private void show(float x, float y) {
        mEndAnimator.cancel();
        mLegAnimator.cancel();
        setLegProgress(0f);
        setDragProgress(0f);
        setAlpha(1f);

        float halfHeight = mHeight * 0.5f;
        mStartY = MathUtils.constrain(y, halfHeight, getHeight() - halfHeight);
        mStartX = x;
    }

    private void handleNewSwipePoint(float x) {
        float dist = MathUtils.abs(x - mStartX);

        setDragProgress(MathUtils.constrainedMap(
                0, 1.0f,
                0, mGestureLength * TRACK_LENGTH_MULTIPLIER,
                dist));

        if (dist < mGestureLength) {
            setLegProgress(MathUtils.constrainedMap(
                    0f, POINTEDNESS_BEFORE_SNAP_RATIO,
                    mGestureLength * START_POINTING_RATIO, mGestureLength,
                    dist));

            mGestureDetected = false;
        } else {
            if (!mGestureDetected) {
                performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
                mGestureDetected = true;

                mLegAnimator.setFloatValues(1f);
                mLegAnimator.start();
            }
        }
    }

    private void updateLayout(WindowManager.LayoutParams lp) {
        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        wm.updateViewLayout(this, lp);
    }

    private float dp(float dp) {
        return mDensity * dp;
    }
}
+3 −2
Original line number Diff line number Diff line
@@ -1227,10 +1227,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
                    .getSystemService(Context.WINDOW_SERVICE);
            int width = mPrototypeController.getEdgeSensitivityWidth();
            int height = mPrototypeController.getEdgeSensitivityHeight();
            // Explicitly left and right, not start and end as this is device relative.
            mLeftEdgePanel = NavigationBarEdgePanel.create(getContext(), width, height,
                    Gravity.START | Gravity.BOTTOM);
                    Gravity.LEFT | Gravity.BOTTOM);
            mRightEdgePanel = NavigationBarEdgePanel.create(getContext(), width, height,
                    Gravity.END | Gravity.BOTTOM);
                    Gravity.RIGHT | Gravity.BOTTOM);
            mLeftEdgePanel.setOnTouchListener(mEdgePanelTouchListener);
            mRightEdgePanel.setOnTouchListener(mEdgePanelTouchListener);
            wm.addView(mLeftEdgePanel, mLeftEdgePanel.getLayoutParams());