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

Commit 86a436ef authored by Matthew Ng's avatar Matthew Ng
Browse files

Refactor QuickStepController into Gestures

The refactor allows adding new gestures easier and provides a way to
reassign triggers to the gestures.

go/navbar-prototypes-doc

Test: atest QuickStepControllerTest
Bug: 112934365
Change-Id: I5334947e2ff3f39af890e1f026089b933ff48a3c
parent fe0efe6b
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -36,7 +36,7 @@ public interface NavGesture extends Plugin {

        public boolean onInterceptTouchEvent(MotionEvent event);

        public void setBarState(boolean vertical, boolean isRtl);
        public void setBarState(boolean isRtl, int navBarPosition);

        public void onDraw(Canvas canvas);

+15 −0
Original line number Diff line number Diff line
@@ -263,6 +263,16 @@ public class ButtonDispatcher {
        }
    }

    public void setTranslation(int x, int y, int z) {
        final int N = mViews.size();
        for (int i = 0; i < N; i++) {
            final View view = mViews.get(i);
            view.setTranslationX(x);
            view.setTranslationY(y);
            view.setTranslationZ(z);
        }
    }

    public ArrayList<View> getViews() {
        return mViews;
    }
@@ -276,6 +286,11 @@ public class ButtonDispatcher {
        if (mImageDrawable != null) {
            mImageDrawable.setCallback(mCurrentView);
        }
        if (mCurrentView != null) {
            mCurrentView.setTranslationX(0);
            mCurrentView.setTranslationY(0);
            mCurrentView.setTranslationZ(0);
        }
    }

    public void setVertical(boolean vertical) {
+131 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.statusbar.phone;

import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME;

import android.annotation.NonNull;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.SystemClock;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;

import com.android.systemui.recents.OverviewProxyService;

/**
 * A back action when triggered will execute a back command
 */
public class NavigationBackAction extends NavigationGestureAction {

    private static final String PULL_HOME_GO_BACK_PROP = "quickstepcontroller_homegoesback";
    private static final String BACK_AFTER_END_PROP =
            "quickstepcontroller_homegoesbackwhenend";
    private static final String NAVBAR_EXPERIMENTS_DISABLED = "navbarexperiments_disabled";
    private static final long BACK_BUTTON_FADE_OUT_ALPHA = 60;
    private static final long BACK_GESTURE_POLL_TIMEOUT = 1000;

    private final Handler mHandler = new Handler();

    private final Runnable mExecuteBackRunnable = new Runnable() {
        @Override
        public void run() {
            if (isEnabled() && canPerformAction()) {
                performBack();
                mHandler.postDelayed(this, BACK_GESTURE_POLL_TIMEOUT);
            }
        }
    };

    public NavigationBackAction(@NonNull NavigationBarView navigationBarView,
            @NonNull OverviewProxyService service) {
        super(navigationBarView, service);
    }

    @Override
    public int requiresTouchDownHitTarget() {
        return HIT_TARGET_HOME;
    }

    @Override
    public boolean requiresDragWithHitTarget() {
        return true;
    }

    @Override
    public boolean canPerformAction() {
        return mProxySender.getBackButtonAlpha() > 0;
    }

    @Override
    public boolean isEnabled() {
        return swipeHomeGoBackGestureEnabled();
    }

    @Override
    protected void onGestureStart(MotionEvent event) {
        if (!QuickStepController.shouldhideBackButton(getContext())) {
            mNavigationBarView.getBackButton().setAlpha(0 /* alpha */, true /* animate */,
                    BACK_BUTTON_FADE_OUT_ALPHA);
        }
        mHandler.removeCallbacks(mExecuteBackRunnable);
        if (!shouldExecuteBackOnUp()) {
            performBack();
            mHandler.postDelayed(mExecuteBackRunnable, BACK_GESTURE_POLL_TIMEOUT);
        }
    }

    @Override
    protected void onGestureEnd() {
        mHandler.removeCallbacks(mExecuteBackRunnable);
        if (!QuickStepController.shouldhideBackButton(getContext())) {
            mNavigationBarView.getBackButton().setAlpha(
                    mProxySender.getBackButtonAlpha(), true /* animate */);
        }
        if (shouldExecuteBackOnUp()) {
            performBack();
        }
    }

    private void performBack() {
        sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
        sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
        mNavigationBarView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
    }

    private boolean swipeHomeGoBackGestureEnabled() {
        return !getGlobalBoolean(NAVBAR_EXPERIMENTS_DISABLED)
                && getGlobalBoolean(PULL_HOME_GO_BACK_PROP);
    }

    private boolean shouldExecuteBackOnUp() {
        return !getGlobalBoolean(NAVBAR_EXPERIMENTS_DISABLED)
                && getGlobalBoolean(BACK_AFTER_END_PROP);
    }

    private void sendEvent(int action, int code) {
        long when = SystemClock.uptimeMillis();
        final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
                0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
                KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
                InputDevice.SOURCE_KEYBOARD);
        InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
    }
}
+28 −19
Original line number Diff line number Diff line
@@ -38,9 +38,11 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.MotionEvent;
@@ -49,6 +51,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.inputmethod.InputMethodManager;
@@ -143,6 +146,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
    private RecentsOnboarding mRecentsOnboarding;
    private NotificationPanelView mPanelView;

    private QuickScrubAction mQuickScrubAction;
    private QuickStepAction mQuickStepAction;
    private NavigationBackAction mBackAction;

    /**
     * Helper that is responsible for showing the right toast when a disallowed activity operation
     * occurred. In pinned mode, we show instructions on how to break out of this mode, whilst in
@@ -299,6 +306,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
        mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
        mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
        mDeadZone = new DeadZone(this);

        mQuickScrubAction = new QuickScrubAction(this, mOverviewProxyService);
        mQuickStepAction = new QuickStepAction(this, mOverviewProxyService);
        mBackAction = new NavigationBackAction(this, mOverviewProxyService);
    }

    public BarTransitions getBarTransitions() {
@@ -313,6 +324,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
        mPanelView = panel;
        if (mGestureHelper instanceof QuickStepController) {
            ((QuickStepController) mGestureHelper).setComponents(this);
            ((QuickStepController) mGestureHelper).setGestureActions(mQuickStepAction,
                    null /* swipeDownAction*/, mBackAction, mQuickScrubAction);
        }
    }

@@ -756,24 +769,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
        mRecentsOnboarding.hide(true);
    }

    /**
     * @return the button at the given {@param x} and {@param y}.
     */
    ButtonDispatcher getButtonAtPosition(int x, int y) {
        for (int i = 0; i < mButtonDispatchers.size(); i++) {
            ButtonDispatcher button = mButtonDispatchers.valueAt(i);
            View buttonView = button.getCurrentView();
            if (buttonView != null) {
                buttonView.getHitRect(mTmpRect);
                offsetDescendantRectToMyCoords(buttonView, mTmpRect);
                if (mTmpRect.contains(x, y)) {
                    return button;
                }
            }
        }
        return null;
    }

    @Override
    public void onFinishInflate() {
        mNavigationInflaterView = findViewById(R.id.navigation_inflater);
@@ -908,7 +903,13 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
    private void updateTaskSwitchHelper() {
        if (mGestureHelper == null) return;
        boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
        mGestureHelper.setBarState(mVertical, isRtl);
        int navBarPos = 0;
        try {
            navBarPos = WindowManagerGlobal.getWindowManagerService().getNavBarPosition();
        } catch (RemoteException e) {
            Slog.e(TAG, "Failed to get nav bar position.", e);
        }
        mGestureHelper.setBarState(isRtl, navBarPos);
    }

    @Override
@@ -1112,6 +1113,14 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav

        mContextualButtonGroup.dump(pw);
        if (mGestureHelper != null) {
            pw.println("Navigation Gesture Actions {");
            pw.print("    "); pw.println("QuickScrub Enabled=" + mQuickScrubAction.isEnabled());
            pw.print("    "); pw.println("QuickScrub Active=" + mQuickScrubAction.isActive());
            pw.print("    "); pw.println("QuickStep Enabled=" + mQuickStepAction.isEnabled());
            pw.print("    "); pw.println("QuickStep Active=" + mQuickStepAction.isActive());
            pw.print("    "); pw.println("Back Gesture Enabled=" + mBackAction.isEnabled());
            pw.print("    "); pw.println("Back Gesture Active=" + mBackAction.isActive());
            pw.println("}");
            mGestureHelper.dump(pw);
        }
        mRecentsOnboarding.dump(pw);
+181 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.statusbar.phone;

import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;

import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;

import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Canvas;
import android.view.MotionEvent;

import android.view.WindowManagerPolicyConstants;
import com.android.systemui.recents.OverviewProxyService;

/**
 * A gesture action that would be triggered and reassigned by {@link QuickStepController}
 */
public abstract class NavigationGestureAction {

    protected final NavigationBarView mNavigationBarView;
    protected final OverviewProxyService mProxySender;

    protected int mNavigationBarPosition;
    protected boolean mDragHorizontalPositive;
    protected boolean mDragVerticalPositive;
    private boolean mIsActive;

    public NavigationGestureAction(@NonNull NavigationBarView navigationBarView,
            @NonNull OverviewProxyService service) {
        mNavigationBarView = navigationBarView;
        mProxySender = service;
    }

    /**
     * Pass event that the state of the bar (such as rotation) has changed
     * @param changed if rotation or drag positive direction (such as ltr) has changed
     * @param navBarPos position of navigation bar
     * @param dragHorPositive direction of positive horizontal drag, could change with ltr changes
     * @param dragVerPositive direction of positive vertical drag, could change with ltr changes
     */
    public void setBarState(boolean changed, int navBarPos, boolean dragHorPositive,
            boolean dragVerPositive) {
        mNavigationBarPosition = navBarPos;
        mDragHorizontalPositive = dragHorPositive;
        mDragVerticalPositive = dragVerPositive;
    }

    /**
     * Resets the state of the action. Called when touch down occurs over the Navigation Bar.
     */
    public void reset() {
        mIsActive = false;
    }

    /**
     * Start the gesture and the action will be active
     * @param event the event that caused the gesture
     */
    public void startGesture(MotionEvent event) {
        mIsActive = true;
        onGestureStart(event);
    }

    /**
     * Gesture has ended with action cancel or up and this action will not be active
     */
    public void endGesture() {
        mIsActive = false;
        onGestureEnd();
    }

    /**
     * If the action is currently active based on the gesture that triggered it. Only one action
     * can occur at a time
     * @return whether or not if this action has been triggered
     */
    public boolean isActive() {
        return mIsActive;
    }

    /**
     * @return whether or not this action can run if notification shade is shown
     */
    public boolean canRunWhenNotificationsShowing() {
        return true;
    }

    /**
     * @return whether or not this action triggers when starting a gesture from a certain hit target
     * If {@link HIT_TARGET_NONE} is specified then action does not need to be triggered by button
     */
    public int requiresTouchDownHitTarget() {
        return HIT_TARGET_NONE;
    }

    /**
     * @return whether or not to move the button that started gesture over with user input drag
     */
    public boolean requiresDragWithHitTarget() {
        return false;
    }

    /**
     * Tell if the action is able to execute. Note that {@link #isEnabled()} must be true for this
     * to be checked. The difference between this and {@link #isEnabled()} is that this dependent
     * on the state of the navigation bar
     * @return true if action can execute after gesture activates based on current states
     */
    public boolean canPerformAction() {
        return true;
    }

    /**
     * Tell if action is enabled. Compared to {@link #canPerformAction()} this is based on settings
     * if the action is disabled for a particular gesture. For example a back action can be enabled
     * however if there is nothing to back to then {@link #canPerformAction()} should return false.
     * In this way if the action requires {@link #requiresDragWithHitTarget()} then if enabled, the
     * button can be dragged with a large dampening factor during the gesture but will not activate
     * the action.
     * @return true if this action is enabled and can run
     */
    public abstract boolean isEnabled();

    protected void onDarkIntensityChange(float intensity) {
    }

    protected void onDraw(Canvas canvas) {
    }

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

    /**
     * When gesture starts, this will run to execute the action
     * @param event the event that triggered the gesture
     */
    protected abstract void onGestureStart(MotionEvent event);

    /**
     * Channels motion move events to the action to track the user inputs
     * @param x the x position
     * @param y the y position
     */
    public void onGestureMove(int x, int y) {
    }

    /**
     * When gesture ends, this will run from action up or cancel
     */
    protected void onGestureEnd() {
    }

    protected Context getContext() {
        return mNavigationBarView.getContext();
    }

    protected boolean isNavBarVertical() {
        return mNavigationBarPosition == NAV_BAR_LEFT || mNavigationBarPosition == NAV_BAR_RIGHT;
    }

    protected boolean getGlobalBoolean(@NonNull String key) {
        return QuickStepController.getBoolGlobalSetting(getContext(), key);
    }
}
Loading