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

Commit c3578967 authored by Joshua Mokut's avatar Joshua Mokut Committed by Android (Google) Code Review
Browse files

Merge "Enabling QS pages scrolling with keyboard" into main

parents a3fa0b20 e59399a0
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -53,6 +53,8 @@
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_gravity="center_vertical"
                android:focusable="true"
                android:importantForAccessibility="no"
                android:tint="?attr/shadeActive"
                android:visibility="gone" />

+38 −4
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -22,6 +23,7 @@ import android.view.animation.OvershootInterpolator;
import android.widget.Scroller;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;

@@ -43,6 +45,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
    private static final int NO_PAGE = -1;

    private static final int REVEAL_SCROLL_DURATION_MILLIS = 750;
    private static final int SINGLE_PAGE_SCROLL_DURATION_MILLIS = 300;
    private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
    private static final long BOUNCE_ANIMATION_DURATION = 450L;
    private static final int TILE_ANIMATION_STAGGER_DELAY = 85;
@@ -63,8 +66,9 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
    private PageListener mPageListener;

    private boolean mListening;
    private Scroller mScroller;
    @VisibleForTesting Scroller mScroller;

    /* set of animations used to indicate which tiles were just revealed  */
    @Nullable
    private AnimatorSet mBounceAnimatorSet;
    private float mLastExpansion;
@@ -306,6 +310,38 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
        mPageIndicator = indicator;
        mPageIndicator.setNumPages(mPages.size());
        mPageIndicator.setLocation(mPageIndicatorPosition);
        mPageIndicator.setOnKeyListener((view, keyCode, keyEvent) -> {
            if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
                // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need
                // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we
                // have a chance to intercept ACTION_UP.
                if (keyEvent.getAction() == KeyEvent.ACTION_UP && mScroller.isFinished()) {
                    scrollByX(getDeltaXForKeyboardScrolling(keyCode),
                            SINGLE_PAGE_SCROLL_DURATION_MILLIS);
                }
                return true;
            }
            return false;
        });
    }

    private int getDeltaXForKeyboardScrolling(int keyCode) {
        if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && getCurrentItem() != 0) {
            return -getWidth();
        } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
                && getCurrentItem() != mPages.size() - 1) {
            return getWidth();
        }
        return 0;
    }

    private void scrollByX(int x, int durationMillis) {
        if (x != 0) {
            mScroller.startScroll(/* startX= */ getScrollX(), /* startY= */ getScrollY(),
                    /* dx= */ x, /* dy= */ 0, /* duration= */ durationMillis);
            // scroller just sets its state, we need to invalidate view to actually start scrolling
            postInvalidateOnAnimation();
        }
    }

    @Override
@@ -596,9 +632,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
        });
        setOffscreenPageLimit(lastPageNumber); // Ensure the page to reveal has been inflated.
        int dx = getWidth() * lastPageNumber;
        mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0,
                REVEAL_SCROLL_DURATION_MILLIS);
        postInvalidateOnAnimation();
        scrollByX(isLayoutRtl() ? -dx : dx, REVEAL_SCROLL_DURATION_MILLIS);
    }

    private boolean shouldNotRunAnimation(Set<String> tilesToReveal) {
+86 −0
Original line number Diff line number Diff line
package com.android.systemui.qs

import android.content.Context
import android.testing.AndroidTestingRunner
import android.view.KeyEvent
import android.view.View
import android.widget.Scroller
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations

@RunWith(AndroidTestingRunner::class)
@SmallTest
class PagedTileLayoutTest : SysuiTestCase() {

    @Mock private lateinit var pageIndicator: PageIndicator
    @Captor private lateinit var captor: ArgumentCaptor<View.OnKeyListener>

    private lateinit var pageTileLayout: TestPagedTileLayout
    private lateinit var scroller: Scroller

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        pageTileLayout = TestPagedTileLayout(mContext)
        pageTileLayout.setPageIndicator(pageIndicator)
        verify(pageIndicator).setOnKeyListener(captor.capture())
        setViewWidth(pageTileLayout, width = PAGE_WIDTH)
        scroller = pageTileLayout.mScroller
    }

    private fun setViewWidth(view: View, width: Int) {
        view.left = 0
        view.right = width
    }

    @Test
    fun scrollsRight_afterRightArrowPressed_whenFocusOnPagerIndicator() {
        pageTileLayout.currentPageIndex = 0

        sendUpEvent(KeyEvent.KEYCODE_DPAD_RIGHT)

        assertThat(scroller.isFinished).isFalse() // aka we're scrolling
        assertThat(scroller.finalX).isEqualTo(scroller.currX + PAGE_WIDTH)
    }

    @Test
    fun scrollsLeft_afterLeftArrowPressed_whenFocusOnPagerIndicator() {
        pageTileLayout.currentPageIndex = 1 // we won't scroll left if we're on the first page

        sendUpEvent(KeyEvent.KEYCODE_DPAD_LEFT)

        assertThat(scroller.isFinished).isFalse() // aka we're scrolling
        assertThat(scroller.finalX).isEqualTo(scroller.currX - PAGE_WIDTH)
    }

    private fun sendUpEvent(keyCode: Int) {
        val event = KeyEvent(KeyEvent.ACTION_UP, keyCode)
        captor.value.onKey(pageIndicator, keyCode, event)
    }

    /**
     * Custom PagedTileLayout to easy mock "currentItem" i.e. currently visible page. Setting this
     * up otherwise would require setting adapter etc
     */
    class TestPagedTileLayout(context: Context) : PagedTileLayout(context, null) {

        var currentPageIndex: Int = 0

        override fun getCurrentItem(): Int {
            return currentPageIndex
        }
    }

    companion object {
        const val PAGE_WIDTH = 200
    }
}