summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Ahmad Khalil <khalilahmad@google.com> 2023-12-13 13:27:36 +0000
committer Ahmad Khalil <khalilahmad@google.com> 2023-12-14 15:50:57 +0000
commit819c70b48590e92d3df6f6fd9b7acb2ade72173f (patch)
tree3e75fe887d33e211f14dafa73f8d83d456197c7c
parent5318262587a76ab3eaa30c43061f0de1cc0ee36b (diff)
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
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationScaler.java23
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorControlService.java116
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java3
-rw-r--r--services/tests/vibrator/Android.bp4
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java31
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java117
6 files changed, 281 insertions, 13 deletions
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 59b55bf74df3..0a7872f5c25f 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -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) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index 2eeb903bb551..9d75249abdd9 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -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);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index fc824abd80f5..2c1ab955514e 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -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));
}
}
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index 6f37967bf7f0..66dcaff687c8 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -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"],
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
index bbca704e58c8..f9fe6a9ac313 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -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);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 49efd1bdd92a..1e0b1df4fc67 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -16,21 +16,49 @@
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_NOTIFICATION;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.frameworks.vibrator.ScaleParam;
+import android.frameworks.vibrator.VibrationParam;
import android.os.RemoteException;
+import android.util.SparseArray;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
public class VibratorControlServiceTest {
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ @Mock
+ private VibrationScaler mMockVibrationScaler;
+ @Captor
+ private ArgumentCaptor<SparseArray<Float>> mVibrationScalesCaptor;
+
private VibratorControlService mVibratorControlService;
private final Object mLock = new Object();
@Before
public void setUp() throws Exception {
- mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(), mLock);
+ mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(),
+ mMockVibrationScaler, mLock);
}
@Test
@@ -47,6 +75,8 @@ public class VibratorControlServiceTest {
FakeVibratorController fakeController = new FakeVibratorController();
mVibratorControlService.registerVibratorController(fakeController);
mVibratorControlService.unregisterVibratorController(fakeController);
+
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScales(null);
assertThat(fakeController.isLinkedToDeath).isFalse();
}
@@ -56,8 +86,91 @@ public class VibratorControlServiceTest {
FakeVibratorController fakeController1 = new FakeVibratorController();
FakeVibratorController fakeController2 = new FakeVibratorController();
mVibratorControlService.registerVibratorController(fakeController1);
-
mVibratorControlService.unregisterVibratorController(fakeController2);
+
+ verifyZeroInteractions(mMockVibrationScaler);
assertThat(fakeController1.isLinkedToDeath).isTrue();
}
+
+ @Test
+ public void testSetVibrationParams_cachesAdaptiveHapticsScalesCorrectly()
+ throws RemoteException {
+ FakeVibratorController fakeController = new FakeVibratorController();
+ mVibratorControlService.registerVibratorController(fakeController);
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
+ fakeController);
+
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScales(mVibrationScalesCaptor.capture());
+ SparseArray<Float> cachedVibrationScales = mVibrationScalesCaptor.getValue();
+ assertThat(cachedVibrationScales.size()).isEqualTo(3);
+ assertThat(cachedVibrationScales.keyAt(0)).isEqualTo(USAGE_ALARM);
+ assertThat(cachedVibrationScales.valueAt(0)).isEqualTo(0.7f);
+ assertThat(cachedVibrationScales.keyAt(1)).isEqualTo(USAGE_NOTIFICATION);
+ assertThat(cachedVibrationScales.valueAt(1)).isEqualTo(0.4f);
+ // Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
+ // notification and communication request usages.
+ assertThat(cachedVibrationScales.keyAt(2)).isEqualTo(USAGE_COMMUNICATION_REQUEST);
+ assertThat(cachedVibrationScales.valueAt(2)).isEqualTo(0.4f);
+ }
+
+ @Test
+ public void testSetVibrationParams_withUnregisteredController_ignoresRequest()
+ throws RemoteException {
+ FakeVibratorController fakeController = new FakeVibratorController();
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
+ fakeController);
+
+ verifyZeroInteractions(mMockVibrationScaler);
+ }
+
+ @Test
+ public void testClearVibrationParams_clearsCachedAdaptiveHapticsScales()
+ throws RemoteException {
+ FakeVibratorController fakeController = new FakeVibratorController();
+ mVibratorControlService.registerVibratorController(fakeController);
+ mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM, fakeController);
+
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScales(null);
+ }
+
+ @Test
+ public void testClearVibrationParams_withUnregisteredController_ignoresRequest()
+ throws RemoteException {
+ FakeVibratorController fakeController = new FakeVibratorController();
+
+ mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM, fakeController);
+
+ verifyZeroInteractions(mMockVibrationScaler);
+ }
+
+ private VibrationParam[] generateVibrationParams(SparseArray<Float> vibrationScales) {
+ List<VibrationParam> vibrationParamList = new ArrayList<>();
+ for (int i = 0; i < vibrationScales.size(); i++) {
+ int type = vibrationScales.keyAt(i);
+ float scale = vibrationScales.valueAt(i);
+
+ vibrationParamList.add(generateVibrationParam(type, scale));
+ }
+
+ return vibrationParamList.toArray(new VibrationParam[0]);
+ }
+
+ private VibrationParam generateVibrationParam(int type, float scale) {
+ ScaleParam scaleParam = new ScaleParam();
+ scaleParam.typesMask = type;
+ scaleParam.scale = scale;
+ VibrationParam vibrationParam = new VibrationParam();
+ vibrationParam.setScale(scaleParam);
+
+ return vibrationParam;
+ }
}