diff options
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; + } } |