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

Commit 819c70b4 authored by Ahmad Khalil's avatar Ahmad Khalil
Browse files

Add adaptive haptics caching and scaling in VibrationScaler

We're adding the ability to cache the adaptive haptics scales received in the VibratorControlService. These params are cached in the VibrationScaler. We're also applying these scales to the vibration segements.

Bug: 305939964
Bug: 305942827
Test: atest VibratorControlServiceTest
Change-Id: Ie345069c18dadb0e7475edb8b26638df9ae44f8e
parent 53182625
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -17,11 +17,13 @@
package com.android.server.vibrator;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.vibrator.V1_0.EffectStrength;
import android.os.IExternalVibratorService;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.util.Slog;
@@ -56,6 +58,8 @@ final class VibrationScaler {
    private final VibrationSettings mSettingsController;
    private final int mDefaultVibrationAmplitude;

    private SparseArray<Float> mAdaptiveHapticsScales;

    VibrationScaler(Context context, VibrationSettings settingsController) {
        mSettingsController = settingsController;
        mDefaultVibrationAmplitude = context.getResources().getInteger(
@@ -140,6 +144,15 @@ final class VibrationScaler {
            if (scaleLevel != null) {
                segment = segment.scale(scaleLevel.factor);
            }

            // If adaptive haptics scaling is available for this usage, apply it to the segment.
            if (Flags.adaptiveHapticsEnabled()
                    && mAdaptiveHapticsScales != null && mAdaptiveHapticsScales.size() > 0
                    && mAdaptiveHapticsScales.contains(usageHint)) {
                float adaptiveScale = mAdaptiveHapticsScales.get(usageHint);
                segment = segment.scale(adaptiveScale);
            }

            segments.set(i, segment);
        }
        if (segments.equals(composedEffect.getSegments())) {
@@ -173,6 +186,16 @@ final class VibrationScaler {
        return prebaked.applyEffectStrength(newEffectStrength);
    }

    /**
     * Updates the adaptive haptics scales.
     * @param scales the new vibration scales to apply.
     *
     * @hide
     */
    public void updateAdaptiveHapticsScales(@Nullable SparseArray<Float> scales) {
        mAdaptiveHapticsScales = scales;
    }

    /** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
    private static int intensityToEffectStrength(int intensity) {
        switch (intensity) {
+108 −8
Original line number Diff line number Diff line
@@ -16,14 +16,26 @@

package com.android.server.vibrator;

import static android.os.VibrationAttributes.USAGE_ALARM;
import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
import static android.os.VibrationAttributes.USAGE_MEDIA;
import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
import static android.os.VibrationAttributes.USAGE_RINGTONE;
import static android.os.VibrationAttributes.USAGE_TOUCH;
import static android.os.VibrationAttributes.USAGE_UNKNOWN;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.frameworks.vibrator.IVibratorControlService;
import android.frameworks.vibrator.IVibratorController;
import android.frameworks.vibrator.ScaleParam;
import android.frameworks.vibrator.VibrationParam;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;

import java.util.Objects;

@@ -37,10 +49,13 @@ public final class VibratorControlService extends IVibratorControlService.Stub {
    private static final String TAG = "VibratorControlService";

    private final VibratorControllerHolder mVibratorControllerHolder;
    private final VibrationScaler mVibrationScaler;
    private final Object mLock;

    public VibratorControlService(VibratorControllerHolder vibratorControllerHolder, Object lock) {
    public VibratorControlService(VibratorControllerHolder vibratorControllerHolder,
            VibrationScaler vibrationScaler, Object lock) {
        mVibratorControllerHolder = vibratorControllerHolder;
        mVibrationScaler = vibrationScaler;
        mLock = lock;
    }

@@ -70,25 +85,62 @@ public final class VibratorControlService extends IVibratorControlService.Stub {
                        + "controller doesn't match the registered one. " + this);
                return;
            }
            updateAdaptiveHapticsScales(/* params= */ null);
            mVibratorControllerHolder.setVibratorController(null);
        }
    }

    @Override
    public void setVibrationParams(
            @SuppressLint("ArrayReturn") VibrationParam[] params, IVibratorController token)
            throws RemoteException {
        // TODO(b/305939964): Add set vibration implementation.
    public void setVibrationParams(@SuppressLint("ArrayReturn") VibrationParam[] params,
            @NonNull IVibratorController token) throws RemoteException {
        Objects.requireNonNull(token);

        synchronized (mLock) {
            if (mVibratorControllerHolder.getVibratorController() == null) {
                Slog.w(TAG, "Received request to set VibrationParams for IVibratorController = "
                        + token + ", but no controller was previously registered. Request "
                        + "Ignored.");
                return;
            }
            if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(),
                    token.asBinder())) {
                Slog.wtf(TAG, "Failed to set new VibrationParams. The provided "
                        + "controller doesn't match the registered one. " + this);
                return;
            }

            updateAdaptiveHapticsScales(params);
        }
    }

    @Override
    public void clearVibrationParams(int types, IVibratorController token) throws RemoteException {
        // TODO(b/305939964): Add clear vibration implementation.
    public void clearVibrationParams(int types, @NonNull IVibratorController token)
            throws RemoteException {
        Objects.requireNonNull(token);

        synchronized (mLock) {
            if (mVibratorControllerHolder.getVibratorController() == null) {
                Slog.w(TAG, "Received request to clear VibrationParams for IVibratorController = "
                        + token + ", but no controller was previously registered. Request "
                        + "Ignored.");
                return;
            }
            if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(),
                    token.asBinder())) {
                Slog.wtf(TAG, "Failed to clear VibrationParams. The provided "
                        + "controller doesn't match the registered one. " + this);
                return;
            }
            //TODO(305942827): Update this method to only clear the specified vibration types.
            // Perhaps look into whether it makes more sense to have this clear all scales and
            // rely on setVibrationParams for clearing the scales for specific vibrations.
            updateAdaptiveHapticsScales(/* params= */ null);
        }
    }

    @Override
    public void onRequestVibrationParamsComplete(
            IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result)
            @NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result)
            throws RemoteException {
        // TODO(305942827): Cache the vibration params in VibrationScaler
    }
@@ -102,4 +154,52 @@ public final class VibratorControlService extends IVibratorControlService.Stub {
    public String getInterfaceHash() throws RemoteException {
        return this.HASH;
    }

    /**
     * Extracts the vibration scales and caches them in {@link VibrationScaler}.
     *
     * @param params the new vibration params to cache.
     */
    private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) {
        if (params == null || params.length == 0) {
            mVibrationScaler.updateAdaptiveHapticsScales(null);
            return;
        }

        SparseArray<Float> vibrationScales = new SparseArray<>();
        for (int i = 0; i < params.length; i++) {
            ScaleParam scaleParam = params[i].getScale();
            extractVibrationScales(scaleParam, vibrationScales);
        }
        mVibrationScaler.updateAdaptiveHapticsScales(vibrationScales);
    }

    /**
     * Extracts the vibration scales and map them to their corresponding
     * {@link android.os.VibrationAttributes} usages.
     */
    private void extractVibrationScales(ScaleParam scaleParam, SparseArray<Float> vibrationScales) {
        if ((ScaleParam.TYPE_ALARM & scaleParam.typesMask) != 0) {
            vibrationScales.put(USAGE_ALARM, scaleParam.scale);
        }

        if ((ScaleParam.TYPE_NOTIFICATION & scaleParam.typesMask) != 0) {
            vibrationScales.put(USAGE_NOTIFICATION, scaleParam.scale);
            vibrationScales.put(USAGE_COMMUNICATION_REQUEST, scaleParam.scale);
        }

        if ((ScaleParam.TYPE_RINGTONE & scaleParam.typesMask) != 0) {
            vibrationScales.put(USAGE_RINGTONE, scaleParam.scale);
        }

        if ((ScaleParam.TYPE_MEDIA & scaleParam.typesMask) != 0) {
            vibrationScales.put(USAGE_MEDIA, scaleParam.scale);
            vibrationScales.put(USAGE_UNKNOWN, scaleParam.scale);
        }

        if ((ScaleParam.TYPE_INTERACTIVE & scaleParam.typesMask) != 0) {
            vibrationScales.put(USAGE_TOUCH, scaleParam.scale);
            vibrationScales.put(USAGE_HARDWARE_FEEDBACK, scaleParam.scale);
        }
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -273,7 +273,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
        injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
        if (ServiceManager.isDeclared(VIBRATOR_CONTROL_SERVICE)) {
            injector.addService(VIBRATOR_CONTROL_SERVICE,
                    new VibratorControlService(new VibratorControllerHolder(), mLock));
                    new VibratorControlService(new VibratorControllerHolder(), mVibrationScaler,
                            mLock));
        }

    }
+2 −2
Original line number Diff line number Diff line
@@ -31,13 +31,13 @@ android_test {
        "frameworks-base-testutils",
        "frameworks-services-vibrator-testutils",
        "junit",
        "mockito-target-minus-junit4",
        "mockito-target-inline-minus-junit4",
        "platform-test-annotations",
        "service-permission.stubs.system_server",
        "services.core",
        "flag-junit",
    ],

    jni_libs: ["libdexmakerjvmtiagent"],
    platform_apis: true,
    certificate: "platform",
    dxflags: ["--multi-dex"],
+31 −0
Original line number Diff line number Diff line
@@ -43,12 +43,17 @@ import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.test.TestLooper;
import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.util.SparseArray;

import androidx.test.InstrumentationRegistry;

@@ -68,6 +73,9 @@ public class VibrationScalerTest {

    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
    @Rule
    public final CheckFlagsRule mCheckFlagsRule =
            DeviceFlagsValueProvider.createCheckFlagsRule();

    @Mock private PowerManagerInternal mPowerManagerInternalMock;
    @Mock private PackageManagerInternal mPackageManagerInternalMock;
@@ -256,6 +264,29 @@ public class VibrationScalerTest {
        assertEquals(0.5, scaled.getScale(), 1e-5);
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
    public void scale_withAdaptiveHaptics_scalesVibrationsCorrectly() {
        setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
        setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);

        SparseArray<Float> adaptiveHapticsScales = new SparseArray<>();
        adaptiveHapticsScales.put(USAGE_RINGTONE, 0.5f);
        adaptiveHapticsScales.put(USAGE_NOTIFICATION, 0.5f);
        mVibrationScaler.updateAdaptiveHapticsScales(adaptiveHapticsScales);

        StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
                VibrationEffect.createOneShot(128, 128), USAGE_RINGTONE));
        // Ringtone scales down.
        assertTrue(scaled.getAmplitude() < 0.5);

        scaled = getFirstSegment(mVibrationScaler.scale(
                VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1),
                USAGE_NOTIFICATION));
        // Notification scales down.
        assertTrue(scaled.getAmplitude() < 0.5);
    }

    private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
            @Vibrator.VibrationIntensity int intensity) {
        when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity);
Loading