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

Commit 109d59d7 authored by Pat Manning's avatar Pat Manning
Browse files

Show double arrow pointer on split screen DividerView.

Bug: 249860781
Test: DividerViewTest.
Flag: CURSOR_HOVER_STATES_ENABLED
Change-Id: I879564175b0b7077f7ac93a3cf375c7a4d5139ce
parent 6d458fd0
Loading
Loading
Loading
Loading
+5 −0
Original line number Original line Diff line number Diff line
@@ -571,6 +571,11 @@ public final class SystemUiDeviceConfigFlags {
     */
     */
    public static final String COMBINED_BROADCAST_ENABLED = "combined_broadcast_enabled";
    public static final String COMBINED_BROADCAST_ENABLED = "combined_broadcast_enabled";


    /**
     * (boolean) Whether to allow cursor hover states for certain elements.
     */
    public static final String CURSOR_HOVER_STATES_ENABLED = "cursor_hover_states_enabled";

    private SystemUiDeviceConfigFlags() {
    private SystemUiDeviceConfigFlags() {
    }
    }
}
}
+23 −10
Original line number Original line Diff line number Diff line
@@ -76,6 +76,9 @@ public class DividerHandleView extends View {
    private int mCurrentHeight;
    private int mCurrentHeight;
    private AnimatorSet mAnimator;
    private AnimatorSet mAnimator;
    private boolean mTouching;
    private boolean mTouching;
    private boolean mHovering;
    private final int mHoveringWidth;
    private final int mHoveringHeight;


    public DividerHandleView(Context context, @Nullable AttributeSet attrs) {
    public DividerHandleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        super(context, attrs);
@@ -87,6 +90,8 @@ public class DividerHandleView extends View {
        mCurrentHeight = mHeight;
        mCurrentHeight = mHeight;
        mTouchingWidth = mWidth > mHeight ? mWidth / 2 : mWidth;
        mTouchingWidth = mWidth > mHeight ? mWidth / 2 : mWidth;
        mTouchingHeight = mHeight > mWidth ? mHeight / 2 : mHeight;
        mTouchingHeight = mHeight > mWidth ? mHeight / 2 : mHeight;
        mHoveringWidth = mWidth > mHeight ? ((int) (mWidth * 1.5f)) : mWidth;
        mHoveringHeight = mHeight > mWidth ? ((int) (mHeight * 1.5f)) : mHeight;
    }
    }


    /** Sets touching state for this handle view. */
    /** Sets touching state for this handle view. */
@@ -94,24 +99,32 @@ public class DividerHandleView extends View {
        if (touching == mTouching) {
        if (touching == mTouching) {
            return;
            return;
        }
        }
        setInputState(touching, animate, mTouchingWidth, mTouchingHeight);
        mTouching = touching;
    }

    /** Sets hovering state for this handle view. */
    public void setHovering(boolean hovering, boolean animate) {
        if (hovering == mHovering) {
            return;
        }
        setInputState(hovering, animate, mHoveringWidth, mHoveringHeight);
        mHovering = hovering;
    }

    private void setInputState(boolean stateOn, boolean animate, int stateWidth, int stateHeight) {
        if (mAnimator != null) {
        if (mAnimator != null) {
            mAnimator.cancel();
            mAnimator.cancel();
            mAnimator = null;
            mAnimator = null;
        }
        }
        if (!animate) {
        if (!animate) {
            if (touching) {
            mCurrentWidth = stateOn ? stateWidth : mWidth;
                mCurrentWidth = mTouchingWidth;
            mCurrentHeight = stateOn ? stateHeight : mHeight;
                mCurrentHeight = mTouchingHeight;
            } else {
                mCurrentWidth = mWidth;
                mCurrentHeight = mHeight;
            }
            invalidate();
            invalidate();
        } else {
        } else {
            animateToTarget(touching ? mTouchingWidth : mWidth,
            animateToTarget(stateOn ? stateWidth : mWidth,
                    touching ? mTouchingHeight : mHeight, touching);
                    stateOn ? stateHeight : mHeight, stateOn);
        }
        }
        mTouching = touching;
    }
    }


    private void animateToTarget(int targetWidth, int targetHeight, boolean touching) {
    private void animateToTarget(int targetWidth, int targetHeight, boolean touching) {
+50 −0
Original line number Original line Diff line number Diff line
@@ -17,14 +17,19 @@
package com.android.wm.shell.common.split;
package com.android.wm.shell.common.split;


import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;


import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CURSOR_HOVER_STATES_ENABLED;

import android.animation.Animator;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Bundle;
import android.provider.DeviceConfig;
import android.util.AttributeSet;
import android.util.AttributeSet;
import android.util.Property;
import android.util.Property;
import android.view.GestureDetector;
import android.view.GestureDetector;
@@ -32,6 +37,7 @@ import android.view.InsetsController;
import android.view.InsetsSource;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceControlViewHost;
import android.view.VelocityTracker;
import android.view.VelocityTracker;
import android.view.View;
import android.view.View;
@@ -46,6 +52,7 @@ import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Nullable;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.R;
@@ -269,6 +276,12 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
        }
        }
    }
    }


    @Override
    public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
        return PointerIcon.getSystemIcon(getContext(),
                isLandscape() ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW);
    }

    @Override
    @Override
    public boolean onTouch(View v, MotionEvent event) {
    public boolean onTouch(View v, MotionEvent event) {
        if (mSplitLayout == null || !mInteractive) {
        if (mSplitLayout == null || !mInteractive) {
@@ -371,6 +384,43 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
        mViewHost.relayout(lp);
        mViewHost.relayout(lp);
    }
    }


    @Override
    public boolean onHoverEvent(MotionEvent event) {
        if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED,
                /* defaultValue = */ false)) {
            return false;
        }

        if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
            setHovering();
            return true;
        } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
            releaseHovering();
            return true;
        }
        return false;
    }

    @VisibleForTesting
    void setHovering() {
        mHandle.setHovering(true, true);
        mHandle.animate()
                .setInterpolator(Interpolators.TOUCH_RESPONSE)
                .setDuration(TOUCH_ANIMATION_DURATION)
                .translationZ(mTouchElevation)
                .start();
    }

    @VisibleForTesting
    void releaseHovering() {
        mHandle.setHovering(false, true);
        mHandle.animate()
                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
                .translationZ(0)
                .start();
    }

    /**
    /**
     * Set divider should interactive to user or not.
     * Set divider should interactive to user or not.
     *
     *
+121 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2023 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.common.split;

import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;

import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CURSOR_HOVER_STATES_ENABLED;

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.SystemClock;
import android.provider.DeviceConfig;
import android.view.InputDevice;
import android.view.InsetsState;
import android.view.MotionEvent;

import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayImeController;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

/** Tests for {@link DividerView} */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DividerViewTest extends ShellTestCase {
    private @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
    private @Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler;
    private @Mock DisplayImeController mDisplayImeController;
    private @Mock ShellTaskOrganizer mTaskOrganizer;
    private SplitLayout mSplitLayout;
    private DividerView mDividerView;

    @Before
    @UiThreadTest
    public void setup() {
        MockitoAnnotations.initMocks(this);
        Configuration configuration = getConfiguration();
        mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration,
                mSplitLayoutHandler, mCallbacks, mDisplayImeController, mTaskOrganizer,
                SplitLayout.PARALLAX_NONE);
        SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
                mContext,
                configuration, mCallbacks);
        splitWindowManager.init(mSplitLayout, new InsetsState());
        mDividerView = spy((DividerView) splitWindowManager.getDividerView());
    }

    @Test
    @UiThreadTest
    public void testHoverDividerView() {
        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED,
                "true", false);

        Rect dividerBounds = mSplitLayout.getDividerBounds();
        int x = dividerBounds.centerX();
        int y = dividerBounds.centerY();
        long downTime = SystemClock.uptimeMillis();
        mDividerView.onHoverEvent(getMotionEvent(downTime, MotionEvent.ACTION_HOVER_ENTER, x, y));

        verify(mDividerView, times(1)).setHovering();

        mDividerView.onHoverEvent(getMotionEvent(downTime, MotionEvent.ACTION_HOVER_EXIT, x, y));

        verify(mDividerView, times(1)).releaseHovering();

        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CURSOR_HOVER_STATES_ENABLED,
                "false", false);
    }

    private static MotionEvent getMotionEvent(long eventTime, int action, float x, float y) {
        MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
        properties.id = 0;
        properties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN;

        MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
        coords.pressure = 1;
        coords.size = 1;
        coords.x = x;
        coords.y = y;

        return MotionEvent.obtain(eventTime, eventTime, action, 1,
                new MotionEvent.PointerProperties[]{properties},
                new MotionEvent.PointerCoords[]{coords}, 0, 0, 1.0f, 1.0f, 0, 0,
                InputDevice.SOURCE_TOUCHSCREEN, 0);
    }

    private static Configuration getConfiguration() {
        final Configuration configuration = new Configuration();
        configuration.unset();
        configuration.orientation = ORIENTATION_LANDSCAPE;
        configuration.windowConfiguration.setRotation(0);
        configuration.windowConfiguration.setBounds(new Rect(0, 0, 1080, 2160));
        return configuration;
    }
}