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

Commit 0aaf6a43 authored by Yeabkal Wubshit's avatar Yeabkal Wubshit
Browse files

Create default ScrollFeedbackProvider creator

We're now hiding the specific platform implementations of the
ScrollFeedbackProvider interface, and instead, creating a method for
clients to get a default implementation of the interface. This is
because we think that
- there is not much value for clients to know what kind of feedback is
  being provided on the device
- more work for client to identify and create the right feedback
  provider

Currently, this creator just gives an instance of the Haptic feedback
provider. If/when we get more implementations of the interface, we
can wrap all of those in a single implementation of the interface
and return it from this new creator method.

This allows for a consistent scroll feedback across different
surfaces on the device, while also giving the OEM control of the
device's feedback behavior.

We're also removing the usage of IntDefs for the scroll feedback
params, as these are expected to come from a system generated motion
event, as documented in the interface's Javadoc.

Bug: 299647683
Test: unit tests
Change-Id: I3046da85c62a2c16489103e49d4d928f8d94d68a
parent 07a7e3ed
Loading
Loading
Loading
Loading
+4 −10
Original line number Diff line number Diff line
@@ -50129,13 +50129,6 @@ package android.view {
    field public static final int VIRTUAL_KEY_RELEASE = 8; // 0x8
  }
  @FlaggedApi("android.view.flags.scroll_feedback_api") public class HapticScrollFeedbackProvider implements android.view.ScrollFeedbackProvider {
    ctor public HapticScrollFeedbackProvider(@NonNull android.view.View);
    method public void onScrollLimit(int, int, int, boolean);
    method public void onScrollProgress(int, int, int, int);
    method public void onSnapToItem(int, int, int);
  }
  public class InflateException extends java.lang.RuntimeException {
    ctor public InflateException();
    ctor public InflateException(String, Throwable);
@@ -51302,9 +51295,10 @@ package android.view {
  }
  @FlaggedApi("android.view.flags.scroll_feedback_api") public interface ScrollFeedbackProvider {
    method public void onScrollLimit(int, int, int, boolean);
    method public void onScrollProgress(int, int, int, int);
    method public void onSnapToItem(int, int, int);
    method @FlaggedApi("android.view.flags.scroll_feedback_api") @NonNull public static android.view.ScrollFeedbackProvider createProvider(@NonNull android.view.View);
    method @FlaggedApi("android.view.flags.scroll_feedback_api") public void onScrollLimit(int, int, int, boolean);
    method @FlaggedApi("android.view.flags.scroll_feedback_api") public void onScrollProgress(int, int, int, int);
    method @FlaggedApi("android.view.flags.scroll_feedback_api") public void onSnapToItem(int, int, int);
  }
  public class SearchEvent {
+5 −17
Original line number Diff line number Diff line
@@ -16,16 +16,10 @@

package android.view;

import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.view.flags.Flags;

import com.android.internal.annotations.VisibleForTesting;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * {@link ScrollFeedbackProvider} that performs haptic feedback when scrolling.
 *
@@ -36,16 +30,12 @@ import java.lang.annotation.RetentionPolicy;
 * methods in this class. To check if your input device ID, source, and motion axis are valid for
 * haptic feedback, you can use the
 * {@link ViewConfiguration#isHapticScrollFeedbackEnabled(int, int, int)} API.
 *
 * @hide
 */
@FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
    private static final String TAG = "HapticScrollFeedbackProvider";

    /** @hide */
    @IntDef(value = {MotionEvent.AXIS_SCROLL})
    @Retention(RetentionPolicy.SOURCE)
    public @interface HapticScrollFeedbackAxis {}

    private static final int TICK_INTERVAL_NO_TICK = 0;
    private static final boolean INITIAL_END_OF_LIST_HAPTICS_ENABLED = false;

@@ -89,8 +79,7 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
    }

    @Override
    public void onScrollProgress(
            int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis, int deltaInPixels) {
    public void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels) {
        maybeUpdateCurrentConfig(inputDeviceId, source, axis);
        if (!mHapticScrollFeedbackEnabled) {
            return;
@@ -117,8 +106,7 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
    }

    @Override
    public void onScrollLimit(
            int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis, boolean isStart) {
    public void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart) {
        maybeUpdateCurrentConfig(inputDeviceId, source, axis);
        if (!mHapticScrollFeedbackEnabled) {
            return;
@@ -135,7 +123,7 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
    }

    @Override
    public void onSnapToItem(int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis) {
    public void onSnapToItem(int inputDeviceId, int source, int axis) {
        maybeUpdateCurrentConfig(inputDeviceId, source, axis);
        if (!mHapticScrollFeedbackEnabled) {
            return;
+23 −6
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.view;

import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.view.flags.Flags;

/**
@@ -62,23 +63,37 @@ import android.view.flags.Flags;
 * </ul>
 *
 * <b>Note</b> that not all valid input device source and motion axis inputs are necessarily
 * supported for scroll feedback. If you are implementing this interface, provide clear
 * documentation in your implementation class about which input device source and motion axis are
 * supported for your specific implementation. If you are using one of the implementations of this
 * interface, please refer to the documentation of the implementation for details on which input
 * device source and axis are supported.
 * supported for scroll feedback; the implementation may choose to provide no feedback for some
 * valid input device source and motion axis arguments.
 */
@FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
public interface ScrollFeedbackProvider {

    /**
     * Call this when the view has snapped to an item.
     * Creates a {@link ScrollFeedbackProvider} implementation for this device.
     *
     * <p>Use a feedback provider created by this method, unless you intend to use your custom
     * scroll feedback providing logic. This allows your use cases to generate scroll feedback that
     * is consistent with the rest of the use cases on the device.
     *
     * @param view the {@link View} for which to provide scroll feedback.
     * @return the default {@link ScrollFeedbackProvider} implementation for the device.
     */
    @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
    @NonNull
    static ScrollFeedbackProvider createProvider(@NonNull View view) {
        return new HapticScrollFeedbackProvider(view);
    }

    /**
     * Call this when the view has snapped to an item.
     *
     * @param inputDeviceId the ID of the {@link InputDevice} that generated the motion triggering
     *          the snap.
     * @param source the input source of the motion causing the snap.
     * @param axis the axis of {@code event} that caused the item to snap.
     */
    @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
    void onSnapToItem(int inputDeviceId, int source, int axis);

    /**
@@ -99,6 +114,7 @@ public interface ScrollFeedbackProvider {
     *                "start" for some views may be at the bottom of a scrolling list, while it may
     *                be at the top of scrolling list for others.
     */
    @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
    void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart);

    /**
@@ -122,5 +138,6 @@ public interface ScrollFeedbackProvider {
     * @param axis the axis of {@code event} that caused scroll progress.
     * @param deltaInPixels the amount of scroll progress, in pixels.
     */
    @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
    void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels);
}
+2 −11
Original line number Diff line number Diff line
@@ -1274,10 +1274,7 @@ public class ViewConfiguration {
     * @see InputDevice#getMotionRange(int, int)
     */
    @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
    public boolean isHapticScrollFeedbackEnabled(
            int inputDeviceId,
            @HapticScrollFeedbackProvider.HapticScrollFeedbackAxis int axis,
            int source) {
    public boolean isHapticScrollFeedbackEnabled(int inputDeviceId, int axis, int source) {
        if (!isInputDeviceInfoValid(inputDeviceId, axis, source)) return false;

        if (source == InputDevice.SOURCE_ROTARY_ENCODER && axis == MotionEvent.AXIS_SCROLL) {
@@ -1320,10 +1317,7 @@ public class ViewConfiguration {
     * @see #isHapticScrollFeedbackEnabled(int, int, int)
     */
    @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
    public int getHapticScrollFeedbackTickInterval(
            int inputDeviceId,
            @HapticScrollFeedbackProvider.HapticScrollFeedbackAxis int axis,
            int source) {
    public int getHapticScrollFeedbackTickInterval(int inputDeviceId, int axis, int source) {
        if (!mRotaryEncoderHapticScrollFeedbackEnabled) {
            return NO_HAPTIC_SCROLL_TICK_INTERVAL;
        }
@@ -1343,9 +1337,6 @@ public class ViewConfiguration {
     * Checks if the View-based haptic scroll feedback implementation is enabled for
     * {@link InputDevice#SOURCE_ROTARY_ENCODER}s.
     *
     * <p>If this method returns {@code true}, the {@link HapticScrollFeedbackProvider} will be
     * muted for rotary encoders in favor of View's scroll haptics implementation.
     *
     * @hide
     */
    public boolean isViewBasedRotaryEncoderHapticScrollFeedbackEnabled() {
+58 −0
Original line number 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 android.view;

import static com.google.common.truth.Truth.assertThat;

import android.content.Context;
import android.platform.test.annotations.Presubmit;

import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import org.junit.Test;
import org.junit.runner.RunWith;

@SmallTest
@RunWith(AndroidJUnit4.class)
@Presubmit
public final class ScrollFeedbackProviderTest {
    private final Context mContext = InstrumentationRegistry.getContext();

    @Test
    public void testDefaultProviderType() {
        View view = new View(mContext);

        ScrollFeedbackProvider provider = ScrollFeedbackProvider.createProvider(view);

        assertThat(provider).isInstanceOf(HapticScrollFeedbackProvider.class);
    }

    @Test
    public void testDefaultProvider_createsDistinctProvidesOnMultipleCalls() {
        View view1 = new View(mContext);
        View view2 = new View(mContext);

        ScrollFeedbackProvider view1Provider1 = ScrollFeedbackProvider.createProvider(view1);
        ScrollFeedbackProvider view1Provider2 = ScrollFeedbackProvider.createProvider(view1);
        ScrollFeedbackProvider view2Provider = ScrollFeedbackProvider.createProvider(view2);

        assertThat(view1Provider1 == view1Provider2).isFalse();
        assertThat(view1Provider1 == view2Provider).isFalse();
    }
}