summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/os/CombinedVibrationEffect.aidl19
-rw-r--r--core/java/android/os/CombinedVibrationEffect.java150
-rw-r--r--core/java/android/os/IVibratorManagerService.aidl4
-rw-r--r--core/java/android/os/VibrationEffect.java2
-rw-r--r--core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java52
-rw-r--r--core/tests/coretests/src/android/os/VibrationEffectTest.java68
-rw-r--r--services/core/java/com/android/server/VibratorManagerService.java14
-rw-r--r--services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java40
8 files changed, 345 insertions, 4 deletions
diff --git a/core/java/android/os/CombinedVibrationEffect.aidl b/core/java/android/os/CombinedVibrationEffect.aidl
new file mode 100644
index 000000000000..330733c2643f
--- /dev/null
+++ b/core/java/android/os/CombinedVibrationEffect.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 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.os;
+
+parcelable CombinedVibrationEffect;
diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java
new file mode 100644
index 000000000000..77bfa577babd
--- /dev/null
+++ b/core/java/android/os/CombinedVibrationEffect.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2020 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.os;
+
+import android.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * A CombinedVibrationEffect describes a haptic effect to be performed by one or more {@link
+ * Vibrator Vibrators}.
+ *
+ * These effects may be any number of things, from single shot vibrations to complex waveforms.
+ *
+ * @hide
+ * @see VibrationEffect
+ */
+public abstract class CombinedVibrationEffect implements Parcelable {
+ private static final int PARCEL_TOKEN_MONO = 1;
+
+ /** @hide to prevent subclassing from outside of the framework */
+ public CombinedVibrationEffect() {
+ }
+
+ /**
+ * Create a synced vibration effect.
+ *
+ * A synced vibration effect should be performed by multiple vibrators at the same time.
+ *
+ * @param effect The {@link VibrationEffect} to perform
+ * @return The desired combined effect.
+ */
+ @NonNull
+ public static CombinedVibrationEffect createSynced(@NonNull VibrationEffect effect) {
+ CombinedVibrationEffect combined = new Mono(effect);
+ combined.validate();
+ return combined;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public abstract void validate();
+
+ /**
+ * Represents a single {@link VibrationEffect} that should be executed in all vibrators in sync.
+ *
+ * @hide
+ */
+ public static final class Mono extends CombinedVibrationEffect {
+ private final VibrationEffect mEffect;
+
+ public Mono(Parcel in) {
+ mEffect = VibrationEffect.CREATOR.createFromParcel(in);
+ }
+
+ public Mono(@NonNull VibrationEffect effect) {
+ mEffect = effect;
+ }
+
+ public VibrationEffect getEffect() {
+ return mEffect;
+ }
+
+ /** @hide */
+ @Override
+ public void validate() {
+ mEffect.validate();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof CombinedVibrationEffect.Mono)) {
+ return false;
+ }
+ CombinedVibrationEffect.Mono other = (CombinedVibrationEffect.Mono) o;
+ return other.mEffect.equals(other.mEffect);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mEffect);
+ }
+
+ @Override
+ public String toString() {
+ return "Mono{mEffect=" + mEffect + '}';
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_MONO);
+ mEffect.writeToParcel(out, flags);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<Mono> CREATOR =
+ new Parcelable.Creator<Mono>() {
+ @Override
+ public Mono createFromParcel(@NonNull Parcel in) {
+ // Skip the type token
+ in.readInt();
+ return new Mono(in);
+ }
+
+ @Override
+ @NonNull
+ public Mono[] newArray(int size) {
+ return new Mono[size];
+ }
+ };
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<CombinedVibrationEffect> CREATOR =
+ new Parcelable.Creator<CombinedVibrationEffect>() {
+ @Override
+ public CombinedVibrationEffect createFromParcel(Parcel in) {
+ int token = in.readInt();
+ if (token == PARCEL_TOKEN_MONO) {
+ return new CombinedVibrationEffect.Mono(in);
+ } else {
+ throw new IllegalStateException(
+ "Unexpected combined vibration event type token in parcel.");
+ }
+ }
+
+ @Override
+ public CombinedVibrationEffect[] newArray(int size) {
+ return new CombinedVibrationEffect[size];
+ }
+ };
+}
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index e821e3194d21..08d201977c49 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -16,9 +16,13 @@
package android.os;
+import android.os.CombinedVibrationEffect;
import android.os.VibrationAttributes;
/** {@hide} */
interface IVibratorManagerService {
int[] getVibratorIds();
+ void vibrate(int uid, String opPkg, in CombinedVibrationEffect effect,
+ in VibrationAttributes attributes, String reason, IBinder token);
+ void cancelVibrate(IBinder token);
}
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 487e46886914..21ad38b0e371 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -983,6 +983,8 @@ public abstract class VibrationEffect implements Parcelable {
Composition.checkPrimitive(effect.id);
Preconditions.checkArgumentInRange(
effect.scale, 0.0f, 1.0f, "scale");
+ Preconditions.checkArgumentNonNegative(effect.delay,
+ "Primitive delay must be zero or positive");
}
}
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
new file mode 100644
index 000000000000..faa67a8bbd62
--- /dev/null
+++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 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.os;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@Presubmit
+@RunWith(JUnit4.class)
+public class CombinedVibrationEffectTest {
+
+ @Test
+ public void testValidateMono() {
+ CombinedVibrationEffect.createSynced(VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> CombinedVibrationEffect.createSynced(new VibrationEffect.OneShot(-1, -1)));
+ }
+
+ @Test
+ public void testSerializationMono() {
+ CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+
+ Parcel parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ CombinedVibrationEffect restored = CombinedVibrationEffect.CREATOR.createFromParcel(parcel);
+ assertEquals(original, restored);
+ }
+}
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index e6852929bbc2..c357414c8913 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -28,6 +28,7 @@ import static org.junit.Assert.assertArrayEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
import android.content.ContentInterface;
import android.content.ContentResolver;
@@ -100,6 +101,73 @@ public class VibrationEffectTest {
}
@Test
+ public void testValidateOneShot() {
+ VibrationEffect.createOneShot(1, 255).validate();
+ VibrationEffect.createOneShot(1, VibrationEffect.DEFAULT_AMPLITUDE).validate();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createOneShot(-1, 255).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createOneShot(0, 255).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createOneShot(1, -2).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createOneShot(1, 256).validate());
+ }
+
+ @Test
+ public void testValidatePrebaked() {
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK).validate();
+ VibrationEffect.createPredefined(VibrationEffect.RINGTONES[1]).validate();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createPredefined(-1).validate());
+ }
+
+ @Test
+ public void testValidateWaveform() {
+ VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1).validate();
+ VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0).validate();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createWaveform(new long[0], new int[0], -1).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createWaveform(TEST_TIMINGS, new int[0], -1).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createWaveform(
+ new long[]{0, 0, 0}, TEST_AMPLITUDES, -1).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createWaveform(
+ TEST_TIMINGS, new int[]{-1, -1, -2}, -1).validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createWaveform(
+ TEST_TIMINGS, TEST_AMPLITUDES, TEST_TIMINGS.length).validate());
+ }
+
+ @Test
+ public void testValidateComposed() {
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
+ .compose()
+ .validate();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.startComposition().addPrimitive(-1).compose().validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, -1, 10)
+ .compose()
+ .validate());
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, -1)
+ .compose()
+ .validate());
+ }
+
+ @Test
public void testScalePrebaked_ignoresScaleAndReturnsSameEffect() {
VibrationEffect initial = VibrationEffect.get(VibrationEffect.RINGTONES[1]);
assertSame(initial, initial.scale(0.5f));
diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/VibratorManagerService.java
index 2f35da79420e..356cd0cd937b 100644
--- a/services/core/java/com/android/server/VibratorManagerService.java
+++ b/services/core/java/com/android/server/VibratorManagerService.java
@@ -16,12 +16,15 @@
package com.android.server;
+import android.annotation.Nullable;
import android.content.Context;
+import android.os.CombinedVibrationEffect;
import android.os.IBinder;
import android.os.IVibratorManagerService;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.ShellCommand;
+import android.os.VibrationAttributes;
import com.android.internal.annotations.VisibleForTesting;
@@ -66,6 +69,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return Arrays.copyOf(mVibratorIds, mVibratorIds.length);
}
+ @Override // Binder call
+ public void vibrate(int uid, String opPkg, CombinedVibrationEffect effect,
+ @Nullable VibrationAttributes attrs, String reason, IBinder token) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override // Binder call
+ public void cancelVibrate(IBinder token) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback cb, ResultReceiver resultReceiver) {
diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
index 8a22a2f5d92f..044bdbadb946 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
@@ -16,10 +16,16 @@
package com.android.server;
+import static com.android.server.testutils.TestUtils.assertExpectException;
+
import static org.junit.Assert.assertArrayEquals;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.os.CombinedVibrationEffect;
+import android.os.Process;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
@@ -40,9 +46,16 @@ import org.mockito.junit.MockitoRule;
@Presubmit
public class VibratorManagerServiceTest {
- @Rule public MockitoRule rule = MockitoJUnit.rule();
+ private static final int UID = Process.ROOT_UID;
+ private static final String PACKAGE_NAME = "package";
+ private static final VibrationAttributes ALARM_ATTRS =
+ new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
+
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
- @Mock private VibratorManagerService.NativeWrapper mNativeWrapperMock;
+ @Mock
+ private VibratorManagerService.NativeWrapper mNativeWrapperMock;
@Before
public void setUp() throws Exception {
@@ -72,7 +85,26 @@ public class VibratorManagerServiceTest {
@Test
public void getVibratorIds_withNonEmptyResultFromNative_returnsSameArray() {
- when(mNativeWrapperMock.getVibratorIds()).thenReturn(new int[]{ 1, 2 });
- assertArrayEquals(new int[]{ 1, 2 }, createService().getVibratorIds());
+ when(mNativeWrapperMock.getVibratorIds()).thenReturn(new int[]{1, 2});
+ assertArrayEquals(new int[]{1, 2}, createService().getVibratorIds());
+ }
+
+ @Test
+ public void vibrate_isUnsupported() {
+ VibratorManagerService service = createService();
+ CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+ assertExpectException(UnsupportedOperationException.class,
+ "Not implemented",
+ () -> service.vibrate(UID, PACKAGE_NAME, effect, ALARM_ATTRS, "reason", service));
+ }
+
+ @Test
+ public void cancelVibrate_isUnsupported() {
+ VibratorManagerService service = createService();
+ CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
+ VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+ assertExpectException(UnsupportedOperationException.class,
+ "Not implemented", () -> service.cancelVibrate(service));
}
}