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

Commit 2b5deb2f authored by Fengjiang Li's avatar Fengjiang Li Committed by Android (Google) Code Review
Browse files

Merge "[Test Week] Add VibratorWrapperTest" into main

parents 7c0fe2d1 979adecc
Loading
Loading
Loading
Loading
+33 −22
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.os.Vibrator;
import android.provider.Settings;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.android.launcher3.Utilities;

@@ -49,14 +50,14 @@ public class VibratorWrapper implements SafeCloseable {

    public static final VibrationEffect EFFECT_CLICK =
            createPredefined(VibrationEffect.EFFECT_CLICK);
    private static final Uri HAPTIC_FEEDBACK_URI =
            Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED);
    @VisibleForTesting
    static final Uri HAPTIC_FEEDBACK_URI = Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED);

    private static final float LOW_TICK_SCALE = 0.9f;
    private static final float DRAG_TEXTURE_SCALE = 0.03f;
    private static final float DRAG_COMMIT_SCALE = 0.5f;
    private static final float DRAG_BUMP_SCALE = 0.4f;
    private static final int DRAG_TEXTURE_EFFECT_SIZE = 200;
    @VisibleForTesting static final float LOW_TICK_SCALE = 0.9f;
    @VisibleForTesting static final float DRAG_TEXTURE_SCALE = 0.03f;
    @VisibleForTesting static final float DRAG_COMMIT_SCALE = 0.5f;
    @VisibleForTesting static final float DRAG_BUMP_SCALE = 0.4f;
    @VisibleForTesting static final int DRAG_TEXTURE_EFFECT_SIZE = 200;

    @Nullable
    private final VibrationEffect mDragEffect;
@@ -73,22 +74,29 @@ public class VibratorWrapper implements SafeCloseable {
     */
    public static final VibrationEffect OVERVIEW_HAPTIC = EFFECT_CLICK;

    private final Context mContext;
    private final Vibrator mVibrator;
    private final boolean mHasVibrator;
    private final SettingsCache.OnChangeListener mHapticChangeListener =

    private final SettingsCache mSettingsCache;

    @VisibleForTesting
    final SettingsCache.OnChangeListener mHapticChangeListener =
            isEnabled -> mIsHapticFeedbackEnabled = isEnabled;

    private boolean mIsHapticFeedbackEnabled;

    private VibratorWrapper(Context context) {
        mContext = context;
        mVibrator = context.getSystemService(Vibrator.class);
        this(context.getSystemService(Vibrator.class), SettingsCache.INSTANCE.get(context));
    }

    @VisibleForTesting
    VibratorWrapper(Vibrator vibrator, SettingsCache settingsCache) {
        mVibrator = vibrator;
        mHasVibrator = mVibrator.hasVibrator();
        mSettingsCache = settingsCache;
        if (mHasVibrator) {
            SettingsCache cache = SettingsCache.INSTANCE.get(mContext);
            cache.register(HAPTIC_FEEDBACK_URI, mHapticChangeListener);
            mIsHapticFeedbackEnabled = cache.getValue(HAPTIC_FEEDBACK_URI, 0);
            mSettingsCache.register(HAPTIC_FEEDBACK_URI, mHapticChangeListener);
            mIsHapticFeedbackEnabled = mSettingsCache.getValue(HAPTIC_FEEDBACK_URI, 0);
        } else {
            mIsHapticFeedbackEnabled = false;
        }
@@ -98,12 +106,7 @@ public class VibratorWrapper implements SafeCloseable {

            // Drag texture, Commit, and Bump should only be used for premium phones.
            // Before using these haptics make sure check if the device can use it
            VibrationEffect.Composition dragEffect = VibrationEffect.startComposition();
            for (int i = 0; i < DRAG_TEXTURE_EFFECT_SIZE; i++) {
                dragEffect.addPrimitive(
                        PRIMITIVE_LOW_TICK, DRAG_TEXTURE_SCALE);
            }
            mDragEffect = dragEffect.compose();
            mDragEffect = getDragEffect();
            mCommitEffect = VibrationEffect.startComposition().addPrimitive(
                    VibrationEffect.Composition.PRIMITIVE_TICK, DRAG_COMMIT_SCALE).compose();
            mBumpEffect = VibrationEffect.startComposition().addPrimitive(
@@ -124,8 +127,7 @@ public class VibratorWrapper implements SafeCloseable {
    @Override
    public void close() {
        if (mHasVibrator) {
            SettingsCache.INSTANCE.get(mContext)
                    .unregister(HAPTIC_FEEDBACK_URI, mHapticChangeListener);
            mSettingsCache.unregister(HAPTIC_FEEDBACK_URI, mHapticChangeListener);
        }
    }

@@ -215,4 +217,13 @@ public class VibratorWrapper implements SafeCloseable {
            vibrate(primitiveLowTickEffect);
        }
    }

    static VibrationEffect getDragEffect() {
        VibrationEffect.Composition dragEffect = VibrationEffect.startComposition();
        for (int i = 0; i < DRAG_TEXTURE_EFFECT_SIZE; i++) {
            dragEffect.addPrimitive(
                    PRIMITIVE_LOW_TICK, DRAG_TEXTURE_SCALE);
        }
        return dragEffect.compose();
    }
}
+192 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.launcher3.util

import android.media.AudioAttributes
import android.os.SystemClock
import android.os.VibrationEffect
import android.os.VibrationEffect.Composition.PRIMITIVE_LOW_TICK
import android.os.VibrationEffect.Composition.PRIMITIVE_TICK
import android.os.Vibrator
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.util.VibratorWrapper.HAPTIC_FEEDBACK_URI
import com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC
import com.android.launcher3.util.VibratorWrapper.VIBRATION_ATTRS
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.any
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.never
import org.mockito.kotlin.same
import org.mockito.kotlin.verifyNoMoreInteractions

@SmallTest
@RunWith(AndroidJUnit4::class)
class VibratorWrapperTest {

    @Mock private lateinit var settingsCache: SettingsCache
    @Mock private lateinit var vibrator: Vibrator
    @Captor private lateinit var vibrationEffectCaptor: ArgumentCaptor<VibrationEffect>

    private lateinit var underTest: VibratorWrapper

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        `when`(settingsCache.getValue(HAPTIC_FEEDBACK_URI, 0)).thenReturn(true)
        `when`(vibrator.hasVibrator()).thenReturn(true)
        `when`(vibrator.areAllPrimitivesSupported(PRIMITIVE_TICK)).thenReturn(true)
        `when`(vibrator.areAllPrimitivesSupported(PRIMITIVE_LOW_TICK)).thenReturn(true)
        `when`(vibrator.getPrimitiveDurations(PRIMITIVE_LOW_TICK)).thenReturn(intArrayOf(10))

        underTest = VibratorWrapper(vibrator, settingsCache)
    }

    @Test
    fun init_register_onChangeListener() {
        verify(settingsCache).register(HAPTIC_FEEDBACK_URI, underTest.mHapticChangeListener)
    }

    @Test
    fun close_unregister_onChangeListener() {
        underTest.close()

        verify(settingsCache).unregister(HAPTIC_FEEDBACK_URI, underTest.mHapticChangeListener)
    }

    @Test
    fun vibrate() {
        underTest.vibrate(OVERVIEW_HAPTIC)

        awaitTasksCompleted()
        verify(vibrator).vibrate(OVERVIEW_HAPTIC, VIBRATION_ATTRS)
    }

    @Test
    fun vibrate_primitive_id() {
        underTest.vibrate(PRIMITIVE_TICK, 1f, OVERVIEW_HAPTIC)

        awaitTasksCompleted()
        verify(vibrator).vibrate(vibrationEffectCaptor.capture(), same(VIBRATION_ATTRS))
        val expectedEffect =
            VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 1f).compose()
        assertThat(vibrationEffectCaptor.value).isEqualTo(expectedEffect)
    }

    @Test
    fun vibrate_with_invalid_primitive_id_use_fallback_effect() {
        underTest.vibrate(-1, 1f, OVERVIEW_HAPTIC)

        awaitTasksCompleted()
        verify(vibrator).vibrate(OVERVIEW_HAPTIC, VIBRATION_ATTRS)
    }

    @Test
    fun vibrate_for_taskbar_unstash() {
        underTest.vibrateForTaskbarUnstash()

        awaitTasksCompleted()
        verify(vibrator).vibrate(vibrationEffectCaptor.capture(), same(VIBRATION_ATTRS))
        val expectedEffect =
            VibrationEffect.startComposition()
                .addPrimitive(PRIMITIVE_LOW_TICK, VibratorWrapper.LOW_TICK_SCALE)
                .compose()
        assertThat(vibrationEffectCaptor.value).isEqualTo(expectedEffect)
    }

    @Test
    fun vibrate_for_drag_bump() {
        underTest.vibrateForDragBump()

        awaitTasksCompleted()
        verify(vibrator).vibrate(vibrationEffectCaptor.capture(), same(VIBRATION_ATTRS))
        val expectedEffect =
            VibrationEffect.startComposition()
                .addPrimitive(PRIMITIVE_LOW_TICK, VibratorWrapper.DRAG_BUMP_SCALE)
                .compose()
        assertThat(vibrationEffectCaptor.value).isEqualTo(expectedEffect)
    }

    @Test
    fun vibrate_for_drag_commit() {
        underTest.vibrateForDragCommit()

        awaitTasksCompleted()
        verify(vibrator).vibrate(vibrationEffectCaptor.capture(), same(VIBRATION_ATTRS))
        val expectedEffect =
            VibrationEffect.startComposition()
                .addPrimitive(PRIMITIVE_TICK, VibratorWrapper.DRAG_COMMIT_SCALE)
                .compose()
        assertThat(vibrationEffectCaptor.value).isEqualTo(expectedEffect)
    }

    @Test
    fun vibrate_for_drag_texture() {
        SystemClock.setCurrentTimeMillis(40000)

        underTest.vibrateForDragTexture()

        awaitTasksCompleted()
        verify(vibrator).vibrate(vibrationEffectCaptor.capture(), same(VIBRATION_ATTRS))
        assertThat(vibrationEffectCaptor.value).isEqualTo(VibratorWrapper.getDragEffect())
    }

    @Test
    fun vibrate_for_drag_texture_within_time_window_noOp() {
        SystemClock.setCurrentTimeMillis(40000)
        underTest.vibrateForDragTexture()
        awaitTasksCompleted()
        reset(vibrator)

        underTest.vibrateForDragTexture()

        verifyNoMoreInteractions(vibrator)
    }

    @Test
    fun haptic_feedback_disabled_no_vibrate() {
        `when`(vibrator.hasVibrator()).thenReturn(false)
        underTest = VibratorWrapper(vibrator, settingsCache)

        underTest.vibrate(OVERVIEW_HAPTIC)

        awaitTasksCompleted()
        verify(vibrator, never())
            .vibrate(any(VibrationEffect::class.java), any(AudioAttributes::class.java))
    }

    @Test
    fun cancel_vibrate() {
        underTest.cancelVibrate()

        awaitTasksCompleted()
        verify(vibrator).cancel()
    }

    private fun awaitTasksCompleted() {
        Executors.UI_HELPER_EXECUTOR.submit<Any> { null }.get()
    }
}