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

Commit 7a834a4e authored by Jason Chang's avatar Jason Chang Committed by Automerger Merge Worker
Browse files

Merge "(1/n) Disabling OHM gesture in NavigationBar 3 Button mode" into sc-dev am: e8b62e4d

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14185029

Change-Id: If523e14d512a1c0169bfb5b8ca6562bbce328706
parents 29e8d492 e8b62e4d
Loading
Loading
Loading
Loading
+0 −12
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.wm.shell.onehanded;
import android.content.res.Configuration;

import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEventCallback;

/**
 * Interface to engage one handed feature.
@@ -59,11 +58,6 @@ public interface OneHanded {
     */
    void stopOneHanded(int uiEvent);

    /**
     * Sets navigation 3 button mode enabled or disabled by users.
     */
    void setThreeButtonModeEnabled(boolean enabled);

    /**
     * Sets one handed feature temporary locked in enabled or disabled state, this won't change
     * settings configuration.
@@ -79,12 +73,6 @@ public interface OneHanded {
     */
    void registerTransitionCallback(OneHandedTransitionCallback callback);

    /**
     * Registers callback for one handed gesture, this gesture callback will be activated on
     * 3 button navigation mode only
     */
    void registerGestureCallback(OneHandedGestureEventCallback callback);

    /**
     * Receive onConfigurationChanged() events
     */
+2 −42
Original line number Diff line number Diff line
@@ -41,7 +41,6 @@ import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Slog;
import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;

@@ -59,7 +58,6 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEventCallback;

import java.io.PrintWriter;

@@ -101,7 +99,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
    private final OneHandedImpl mImpl = new OneHandedImpl();

    private OneHandedDisplayAreaOrganizer mDisplayAreaOrganizer;
    private OneHandedGestureHandler mGestureHandler;
    private OneHandedBackgroundPanelOrganizer mBackgroundPanelOrganizer;

    /**
@@ -113,7 +110,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
                    return;
                }
                mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation, wct);
                mGestureHandler.onRotateDisplay(mDisplayAreaOrganizer.getDisplayLayout());
            };

    private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
@@ -196,7 +192,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>

    private boolean isInitialized() {
        if (mDisplayAreaOrganizer == null || mDisplayController == null
                || mGestureHandler == null || mOneHandedSettingsUtil == null) {
                || mOneHandedSettingsUtil == null) {
            Slog.w(TAG, "Components may not initialized yet!");
            return false;
        }
@@ -226,8 +222,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
                new OneHandedAnimationController(context);
        OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler,
                mainExecutor);
        OneHandedGestureHandler gestureHandler = new OneHandedGestureHandler(
                context, displayLayout, ViewConfiguration.get(context), mainExecutor);
        OneHandedBackgroundPanelOrganizer oneHandedBackgroundPanelOrganizer =
                new OneHandedBackgroundPanelOrganizer(context, displayLayout, mainExecutor);
        OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer(
@@ -238,7 +232,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
                ServiceManager.getService(Context.OVERLAY_SERVICE));
        return new OneHandedController(context, displayController,
                oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
                gestureHandler, settingsUtil, accessibilityUtil, timeoutHandler, transitionState,
                settingsUtil, accessibilityUtil, timeoutHandler, transitionState,
                oneHandedUiEventsLogger, overlayManager, taskStackListener, mainExecutor,
                mainHandler);
    }
@@ -250,7 +244,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
            OneHandedDisplayAreaOrganizer displayAreaOrganizer,
            OneHandedTouchHandler touchHandler,
            OneHandedTutorialHandler tutorialHandler,
            OneHandedGestureHandler gestureHandler,
            OneHandedSettingsUtil settingsUtil,
            OneHandedAccessibilityUtil oneHandedAccessibilityUtil,
            OneHandedTimeoutHandler timeoutHandler,
@@ -269,7 +262,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
        mTouchHandler = touchHandler;
        mState = state;
        mTutorialHandler = tutorialHandler;
        mGestureHandler = gestureHandler;
        mOverlayManager = overlayManager;
        mMainExecutor = mainExecutor;
        mMainHandler = mainHandler;
@@ -399,24 +391,15 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
        mOneHandedUiEventLogger.writeEvent(uiEvent);
    }

    private void setThreeButtonModeEnabled(boolean enabled) {
        mGestureHandler.onThreeButtonModeEnabled(enabled);
    }

    @VisibleForTesting
    void registerTransitionCallback(OneHandedTransitionCallback callback) {
        mDisplayAreaOrganizer.registerTransitionCallback(callback);
    }

    private void registerGestureCallback(OneHandedGestureEventCallback callback) {
        mGestureHandler.setGestureEventListener(callback);
    }

    private void setupCallback() {
        mTouchHandler.registerTouchEventListener(() ->
                stopOneHanded(OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_OVERSPACE_OUT));
        mDisplayAreaOrganizer.registerTransitionCallback(mTouchHandler);
        mDisplayAreaOrganizer.registerTransitionCallback(mGestureHandler);
        mDisplayAreaOrganizer.registerTransitionCallback(mTutorialHandler);
        mDisplayAreaOrganizer.registerTransitionCallback(mBackgroundPanelOrganizer);
        mDisplayAreaOrganizer.registerTransitionCallback(mTransitionCallBack);
@@ -465,7 +448,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
    private void updateDisplayLayout(int displayId) {
        final DisplayLayout newDisplayLayout = mDisplayController.getDisplayLayout(displayId);
        mDisplayAreaOrganizer.setDisplayLayout(newDisplayLayout);
        mGestureHandler.onDisplayChanged(newDisplayLayout);
        mTutorialHandler.onDisplayChanged(newDisplayLayout);
    }

@@ -588,7 +570,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
        }

        mTouchHandler.onOneHandedEnabled(mIsOneHandedEnabled);
        mGestureHandler.onGestureEnabled(mIsOneHandedEnabled || mIsSwipeToNotificationEnabled);

        if (!mIsOneHandedEnabled) {
            mDisplayAreaOrganizer.unregisterOrganizer();
@@ -643,9 +624,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
            return;
        }
        mLockedDisabled = locked && !enabled;

        // Disabled gesture when keyguard ON
        mGestureHandler.onGestureEnabled(!mLockedDisabled && isFeatureEnabled);
    }

    private void onConfigChanged(Configuration newConfig) {
@@ -685,10 +663,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
            mDisplayAreaOrganizer.dump(pw);
        }

        if (mGestureHandler != null) {
            mGestureHandler.dump(pw);
        }

        if (mTouchHandler != null) {
            mTouchHandler.dump(pw);
        }
@@ -774,13 +748,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
            });
        }

        @Override
        public void setThreeButtonModeEnabled(boolean enabled) {
            mMainExecutor.execute(() -> {
                OneHandedController.this.setThreeButtonModeEnabled(enabled);
            });
        }

        @Override
        public void setLockedDisabled(boolean locked, boolean enabled) {
            mMainExecutor.execute(() -> {
@@ -795,13 +762,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
            });
        }

        @Override
        public void registerGestureCallback(OneHandedGestureEventCallback callback) {
            mMainExecutor.execute(() -> {
                OneHandedController.this.registerGestureCallback(callback);
            });
        }

        @Override
        public void onConfigChanged(Configuration newConfig) {
            mMainExecutor.execute(() -> {
+0 −308
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.wm.shell.onehanded;

import static android.view.Display.DEFAULT_DISPLAY;

import android.annotation.Nullable;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Looper;
import android.view.Display;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.ViewConfiguration;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;

import java.io.PrintWriter;

/**
 * The class manage swipe up and down gesture for 3-Button mode navigation, others(e.g, 2-button,
 * full gesture mode) are handled by Launcher quick steps. TODO(b/160934654) Migrate to Launcher
 * quick steps
 */
public class OneHandedGestureHandler implements OneHandedTransitionCallback {
    private static final String TAG = "OneHandedGestureHandler";

    private static final int ANGLE_MAX = 150;
    private static final int ANGLE_MIN = 30;
    private final float mDragDistThreshold;
    private final float mSquaredSlop;
    private final PointF mDownPos = new PointF();
    private final PointF mLastPos = new PointF();
    private final PointF mStartDragPos = new PointF();

    private boolean mPassedSlop;
    private boolean mAllowGesture;
    private boolean mIsEnabled;
    private int mNavGestureHeight;
    private boolean mIsThreeButtonModeEnabled;
    private int mRotation = Surface.ROTATION_0;

    @VisibleForTesting
    InputMonitor mInputMonitor;
    @VisibleForTesting
    InputEventReceiver mInputEventReceiver;
    private final ShellExecutor mMainExecutor;
    @VisibleForTesting
    @Nullable
    OneHandedGestureEventCallback mGestureEventCallback;
    private Rect mGestureRegion = new Rect();
    private boolean mIsStopGesture;

    /**
     * Constructor of OneHandedGestureHandler, we only handle the gesture of {@link
     * Display#DEFAULT_DISPLAY}
     *
     * @param context       Any context
     * @param displayLayout Current {@link DisplayLayout} from controller
     * @param viewConfig    {@link ViewConfiguration} to obtain touch slop
     * @param mainExecutor  The wm-shell main executor
     */
    public OneHandedGestureHandler(Context context,
            DisplayLayout displayLayout,
            ViewConfiguration viewConfig,
            ShellExecutor mainExecutor) {
        mMainExecutor = mainExecutor;
        mDragDistThreshold = context.getResources().getDimensionPixelSize(
                R.dimen.gestures_onehanded_drag_threshold);

        final float slop = viewConfig.getScaledTouchSlop();
        mSquaredSlop = slop * slop;
        onDisplayChanged(displayLayout);
        updateIsEnabled();
    }

    /**
     * Notifies by {@link OneHandedController}, when swipe down gesture is enabled on 3 button
     * navigation bar mode.
     *
     * @param isEnabled Either one handed mode or swipe for notification function enabled or not
     */
    public void onGestureEnabled(boolean isEnabled) {
        mIsEnabled = isEnabled;
        updateIsEnabled();
    }

    void onThreeButtonModeEnabled(boolean isEnabled) {
        mIsThreeButtonModeEnabled = isEnabled;
        updateIsEnabled();
    }

    /**
     * Registers {@link OneHandedGestureEventCallback} to receive onStart(), onStop() callback
     */
    public void setGestureEventListener(OneHandedGestureEventCallback callback) {
        mGestureEventCallback = callback;
    }

    /**
     * Called when onDisplayAdded() or onDisplayRemoved() callback
     * @param displayLayout The latest {@link DisplayLayout} representing current displayId
     */
    public void onDisplayChanged(DisplayLayout displayLayout) {
        mNavGestureHeight = getNavBarSize(displayLayout);
        mGestureRegion.set(0, displayLayout.height() - mNavGestureHeight, displayLayout.width(),
                displayLayout.height());
        mRotation = displayLayout.rotation();
    }

    private void onMotionEvent(MotionEvent ev) {
        int action = ev.getActionMasked();
        if (action == MotionEvent.ACTION_DOWN) {
            mAllowGesture = isWithinTouchRegion(ev.getX(), ev.getY()) && isGestureAvailable();
            if (mAllowGesture) {
                mDownPos.set(ev.getX(), ev.getY());
                mLastPos.set(mDownPos);
            }
        } else if (mAllowGesture) {
            switch (action) {
                case MotionEvent.ACTION_MOVE:
                    mLastPos.set(ev.getX(), ev.getY());
                    if (!mPassedSlop) {
                        if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y)
                                > mSquaredSlop) {
                            mStartDragPos.set(mLastPos.x, mLastPos.y);
                            if (isValidStartAngle(
                                    mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y)
                                    || isValidExitAngle(
                                    mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y)) {
                                mPassedSlop = true;
                                mInputMonitor.pilferPointers();
                            }
                        }
                    } else {
                        float distance = (float) Math.hypot(mLastPos.x - mDownPos.x,
                                mLastPos.y - mDownPos.y);
                        if (distance > mDragDistThreshold) {
                            mIsStopGesture = true;
                        }
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    if (mLastPos.y >= mDownPos.y && mPassedSlop) {
                        mGestureEventCallback.onStart();
                    } else if (mIsStopGesture) {
                        mGestureEventCallback.onStop();
                    }
                    clearState();
                    break;
                case MotionEvent.ACTION_CANCEL:
                    clearState();
                    break;
                default:
                    break;
            }
        }
    }

    private void clearState() {
        mPassedSlop = false;
        mIsStopGesture = false;
    }

    private void disposeInputChannel() {
        if (mInputEventReceiver != null) {
            mInputEventReceiver.dispose();
            mInputEventReceiver = null;
        }

        if (mInputMonitor != null) {
            mInputMonitor.dispose();
            mInputMonitor = null;
        }
    }

    private boolean isWithinTouchRegion(float x, float y) {
        return mGestureRegion.contains(Math.round(x), Math.round(y));
    }

    private int getNavBarSize(@NonNull DisplayLayout displayLayout) {
        return isGestureAvailable() ? displayLayout.navBarFrameHeight() : 0 /* In landscape */;
    }

    private void updateIsEnabled() {
        disposeInputChannel();

        if (mIsEnabled && mIsThreeButtonModeEnabled && isGestureAvailable()) {
            mInputMonitor = InputManager.getInstance().monitorGestureInput(
                    "onehanded-gesture-offset", DEFAULT_DISPLAY);
            try {
                mMainExecutor.executeBlocking(() -> {
                    mInputEventReceiver = new EventReceiver(
                            mInputMonitor.getInputChannel(), Looper.myLooper());
                });
            } catch (InterruptedException e) {
                throw new RuntimeException("Failed to create input event receiver", e);
            }
        }
    }

    private void onInputEvent(InputEvent ev) {
        if (ev instanceof MotionEvent) {
            onMotionEvent((MotionEvent) ev);
        }
    }

    /**
     * Handler for display rotation changes by {@link DisplayLayout}
     *
     * @param displayLayout The rotated displayLayout
     */
    public void onRotateDisplay(DisplayLayout displayLayout) {
        mRotation = displayLayout.rotation();
        mNavGestureHeight = getNavBarSize(displayLayout);
        mGestureRegion.set(0, displayLayout.height() - mNavGestureHeight, displayLayout.width(),
                displayLayout.height());
        updateIsEnabled();
    }

    // TODO: Use BatchedInputEventReceiver
    private class EventReceiver extends InputEventReceiver {
        EventReceiver(InputChannel channel, Looper looper) {
            super(channel, looper);
        }

        public void onInputEvent(InputEvent event) {
            OneHandedGestureHandler.this.onInputEvent(event);
            finishInputEvent(event, true);
        }
    }

    private boolean isGestureAvailable() {
        // Either OHM or swipe notification shade can activate in portrait mode only
        return mRotation == Surface.ROTATION_0 || mRotation == Surface.ROTATION_180;
    }

    private boolean isValidStartAngle(float deltaX, float deltaY) {
        final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
        return angle > -(ANGLE_MAX) && angle < -(ANGLE_MIN);
    }

    private boolean isValidExitAngle(float deltaX, float deltaY) {
        final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
        return angle > ANGLE_MIN && angle < ANGLE_MAX;
    }

    private float squaredHypot(float x, float y) {
        return x * x + y * y;
    }

    void dump(@NonNull PrintWriter pw) {
        final String innerPrefix = "  ";
        pw.println(TAG);
        pw.print(innerPrefix + "mAllowGesture=");
        pw.println(mAllowGesture);
        pw.print(innerPrefix + "mIsEnabled=");
        pw.println(mIsEnabled);
        pw.print(innerPrefix + "mGestureRegion=");
        pw.println(mGestureRegion);
        pw.print(innerPrefix + "mNavGestureHeight=");
        pw.println(mNavGestureHeight);
        pw.print(innerPrefix + "mIsThreeButtonModeEnabled=");
        pw.println(mIsThreeButtonModeEnabled);
        pw.print(innerPrefix + "mRotation=");
        pw.println(mRotation);
    }

    /**
     * The touch(gesture) events to notify {@link OneHandedController} start or stop one handed
     */
    public interface OneHandedGestureEventCallback {
        /**
         * Handles the start gesture.
         */
        void onStart();

        /**
         * Handles the exit gesture.
         */
        void onStop();
    }
}
+0 −1
Original line number Diff line number Diff line
@@ -37,7 +37,6 @@ import java.io.PrintWriter;
/**
 * Manages all the touch handling for One Handed on the Phone, including user tap outside region
 * to exit, reset timer when user is in one-handed mode.
 * Refer {@link OneHandedGestureHandler} to see start and stop one handed gesture
 */
public class OneHandedTouchHandler implements OneHandedTransitionCallback {
    private static final String TAG = "OneHandedTouchHandler";
+0 −31
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -79,8 +78,6 @@ public class OneHandedControllerTest extends OneHandedTestCase {
    @Mock
    OneHandedTutorialHandler mMockTutorialHandler;
    @Mock
    OneHandedGestureHandler mMockGestureHandler;
    @Mock
    OneHandedSettingsUtil mMockSettingsUitl;
    @Mock
    OneHandedUiEventLogger mMockUiEventLogger;
@@ -131,7 +128,6 @@ public class OneHandedControllerTest extends OneHandedTestCase {
                mMockDisplayAreaOrganizer,
                mMockTouchHandler,
                mMockTutorialHandler,
                mMockGestureHandler,
                mMockSettingsUitl,
                mOneHandedAccessibilityUtil,
                mSpiedTimeoutHandler,
@@ -179,7 +175,6 @@ public class OneHandedControllerTest extends OneHandedTestCase {
    @Test
    public void testRegisterTransitionCallbackAfterInit() {
        verify(mMockDisplayAreaOrganizer).registerTransitionCallback(mMockTouchHandler);
        verify(mMockDisplayAreaOrganizer).registerTransitionCallback(mMockGestureHandler);
        verify(mMockDisplayAreaOrganizer).registerTransitionCallback(mMockTutorialHandler);
    }

@@ -205,7 +200,6 @@ public class OneHandedControllerTest extends OneHandedTestCase {
        mSpiedOneHandedController.setOneHandedEnabled(true);

        verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean());
        verify(mMockGestureHandler, atLeastOnce()).onGestureEnabled(anyBoolean());
    }

    @Test
@@ -213,7 +207,6 @@ public class OneHandedControllerTest extends OneHandedTestCase {
        mSpiedOneHandedController.setSwipeToNotificationEnabled(mDefaultSwipeToNotificationEnabled);

        verify(mMockTouchHandler, atLeastOnce()).onOneHandedEnabled(anyBoolean());
        verify(mMockGestureHandler, atLeastOnce()).onGestureEnabled(anyBoolean());
    }

    @Test
@@ -339,30 +332,6 @@ public class OneHandedControllerTest extends OneHandedTestCase {
        verify(mMockDisplayAreaOrganizer, never()).scheduleOffset(anyInt(), anyInt());
    }

    @Test
    public void testDisabled3ButtonGestureWhenKeyguardOn() {
        final boolean isOneHandedEnabled = true;
        final boolean isLockWhenKeyguardOn = true;
        final boolean isEnabledWhenKeyguardOn = false;
        mSpiedOneHandedController.setOneHandedEnabled(isOneHandedEnabled);
        mSpiedOneHandedController.setLockedDisabled(isLockWhenKeyguardOn, isEnabledWhenKeyguardOn);

        verify(mMockGestureHandler).onGestureEnabled(isEnabledWhenKeyguardOn);
    }

    @Test
    public void testEnabled3ButtonGestureWhenKeyguardGoingAway() {
        final boolean isOneHandedEnabled = true;
        final boolean isLockWhenKeyguardOn = false;
        final boolean isEnabledWhenKeyguardOn = false;
        mSpiedOneHandedController.setOneHandedEnabled(isOneHandedEnabled);
        reset(mMockGestureHandler);

        mSpiedOneHandedController.setLockedDisabled(isLockWhenKeyguardOn, isEnabledWhenKeyguardOn);

        verify(mMockGestureHandler).onGestureEnabled(isOneHandedEnabled);
    }

    @Test
    public void testStateActive_shortcutRequestActivate_skipActions() {
        when(mSpiedTransitionState.getState()).thenReturn(STATE_ACTIVE);
Loading