summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt29
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl4
-rw-r--r--core/java/android/hardware/input/InputDeviceVibrator.java94
-rw-r--r--core/java/android/hardware/input/InputDeviceVibratorManager.java125
-rw-r--r--core/java/android/hardware/input/InputManager.java145
-rw-r--r--core/java/android/os/CombinedVibrationEffect.java4
-rw-r--r--core/java/android/os/VibratorManager.java61
-rw-r--r--core/java/android/view/InputDevice.java29
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java148
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp104
10 files changed, 624 insertions, 119 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index e302b25873b4..91b07909ae5f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -34845,6 +34845,27 @@ package android.os {
method public void onCancel();
}
+ public abstract class CombinedVibrationEffect implements android.os.Parcelable {
+ method @NonNull public static android.os.CombinedVibrationEffect createSynced(@NonNull android.os.VibrationEffect);
+ method public int describeContents();
+ method @NonNull public static android.os.CombinedVibrationEffect.SequentialCombination startSequential();
+ method @NonNull public static android.os.CombinedVibrationEffect.SyncedCombination startSynced();
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect> CREATOR;
+ }
+
+ public static final class CombinedVibrationEffect.SequentialCombination {
+ method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(int, @NonNull android.os.VibrationEffect);
+ method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(int, @NonNull android.os.VibrationEffect, int);
+ method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(@NonNull android.os.CombinedVibrationEffect);
+ method @NonNull public android.os.CombinedVibrationEffect.SequentialCombination addNext(@NonNull android.os.CombinedVibrationEffect, int);
+ method @NonNull public android.os.CombinedVibrationEffect combine();
+ }
+
+ public static final class CombinedVibrationEffect.SyncedCombination {
+ method @NonNull public android.os.CombinedVibrationEffect.SyncedCombination addVibrator(int, @NonNull android.os.VibrationEffect);
+ method @NonNull public android.os.CombinedVibrationEffect combine();
+ }
+
public class ConditionVariable {
ctor public ConditionVariable();
ctor public ConditionVariable(boolean);
@@ -36071,6 +36092,13 @@ package android.os {
field public static final int VIBRATION_EFFECT_SUPPORT_YES = 1; // 0x1
}
+ public abstract class VibratorManager {
+ method @NonNull public abstract android.os.Vibrator getDefaultVibrator();
+ method @NonNull public abstract android.os.Vibrator getVibrator(int);
+ method @NonNull public abstract int[] getVibratorIds();
+ method public abstract void vibrate(@NonNull android.os.CombinedVibrationEffect);
+ }
+
public class WorkSource implements android.os.Parcelable {
ctor public WorkSource();
ctor public WorkSource(android.os.WorkSource);
@@ -51147,6 +51175,7 @@ package android.view {
method public int getSources();
method public int getVendorId();
method public android.os.Vibrator getVibrator();
+ method @NonNull public android.os.VibratorManager getVibratorManager();
method public boolean[] hasKeys(int...);
method public boolean hasMicrophone();
method public boolean isEnabled();
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index dc6f5799156d..57c039831112 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -22,6 +22,7 @@ import android.hardware.input.KeyboardLayout;
import android.hardware.input.IInputDevicesChangedListener;
import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.TouchCalibration;
+import android.os.CombinedVibrationEffect;
import android.os.IBinder;
import android.os.VibrationEffect;
import android.view.InputDevice;
@@ -85,7 +86,10 @@ interface IInputManager {
// Input device vibrator control.
void vibrate(int deviceId, in VibrationEffect effect, IBinder token);
+ void vibrateCombined(int deviceId, in CombinedVibrationEffect effect, IBinder token);
void cancelVibrate(int deviceId, IBinder token);
+ int[] getVibratorIds(int deviceId);
+ boolean isVibrating(int deviceId);
void setPointerIconType(int typeId);
void setCustomPointerIcon(in PointerIcon icon);
diff --git a/core/java/android/hardware/input/InputDeviceVibrator.java b/core/java/android/hardware/input/InputDeviceVibrator.java
new file mode 100644
index 000000000000..c60d6ce46fdb
--- /dev/null
+++ b/core/java/android/hardware/input/InputDeviceVibrator.java
@@ -0,0 +1,94 @@
+/*
+ * 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.hardware.input;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.VibrationAttributes;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Vibrator implementation that communicates with the input device vibrators.
+ */
+final class InputDeviceVibrator extends Vibrator {
+ // mDeviceId represents InputDevice ID the vibrator belongs to
+ private final int mDeviceId;
+ private final int mVibratorId;
+ private final Binder mToken;
+ private final InputManager mInputManager;
+
+ InputDeviceVibrator(InputManager inputManager, int deviceId, int vibratorId) {
+ mInputManager = inputManager;
+ mDeviceId = deviceId;
+ mVibratorId = vibratorId;
+ mToken = new Binder();
+ }
+
+ @Override
+ public boolean hasVibrator() {
+ return true;
+ }
+
+ @Override
+ public boolean isVibrating() {
+ return mInputManager.isVibrating(mDeviceId);
+ }
+
+ /* TODO: b/161634264 Support Vibrator listener API in input devices */
+ @Override
+ public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ throw new UnsupportedOperationException(
+ "addVibratorStateListener not supported in InputDeviceVibrator");
+ }
+
+ @Override
+ public void addVibratorStateListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnVibratorStateChangedListener listener) {
+ throw new UnsupportedOperationException(
+ "addVibratorStateListener not supported in InputDeviceVibrator");
+ }
+
+ @Override
+ public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
+ throw new UnsupportedOperationException(
+ "removeVibratorStateListener not supported in InputDeviceVibrator");
+ }
+
+ @Override
+ public boolean hasAmplitudeControl() {
+ return true;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect,
+ String reason, @NonNull VibrationAttributes attributes) {
+ mInputManager.vibrate(mDeviceId, effect, mToken);
+ }
+
+ @Override
+ public void cancel() {
+ mInputManager.cancelVibrate(mDeviceId, mToken);
+ }
+}
diff --git a/core/java/android/hardware/input/InputDeviceVibratorManager.java b/core/java/android/hardware/input/InputDeviceVibratorManager.java
new file mode 100644
index 000000000000..a381b02ab2a6
--- /dev/null
+++ b/core/java/android/hardware/input/InputDeviceVibratorManager.java
@@ -0,0 +1,125 @@
+/*
+ * 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.hardware.input;
+
+import android.os.Binder;
+import android.os.CombinedVibrationEffect;
+import android.os.NullVibrator;
+import android.os.Vibrator;
+import android.os.VibratorManager;
+import android.util.SparseArray;
+import android.view.InputDevice;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Vibrator manager implementation that communicates with the input device vibrators.
+ *
+ * @hide
+ */
+public class InputDeviceVibratorManager extends VibratorManager
+ implements InputManager.InputDeviceListener {
+ private static final String TAG = "InputDeviceVibratorManager";
+ private static final boolean DEBUG = false;
+
+ private final Binder mToken;
+ private final InputManager mInputManager;
+
+ // The input device Id.
+ private final int mDeviceId;
+ // Vibrator map from Vibrator Id to Vibrator
+ @GuardedBy("mVibrators")
+ private final SparseArray<Vibrator> mVibrators = new SparseArray<>();
+
+ public InputDeviceVibratorManager(InputManager inputManager, int deviceId) {
+ mInputManager = inputManager;
+ mDeviceId = deviceId;
+ mToken = new Binder();
+
+ initializeVibrators();
+ }
+
+ private void initializeVibrators() {
+ synchronized (mVibrators) {
+ mVibrators.clear();
+ InputDevice inputDevice = InputDevice.getDevice(mDeviceId);
+ final int[] vibratorIds =
+ mInputManager.getVibratorIds(mDeviceId);
+ for (int i = 0; i < vibratorIds.length; i++) {
+ mVibrators.put(vibratorIds[i],
+ new InputDeviceVibrator(mInputManager, mDeviceId, vibratorIds[i]));
+ }
+ }
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ synchronized (mVibrators) {
+ if (deviceId == mDeviceId) {
+ mVibrators.clear();
+ }
+ }
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ if (deviceId == mDeviceId) {
+ initializeVibrators();
+ }
+ }
+
+ @Override
+ public int[] getVibratorIds() {
+ synchronized (mVibrators) {
+ int[] vibratorIds = new int[mVibrators.size()];
+ for (int idx = 0; idx < mVibrators.size(); idx++) {
+ vibratorIds[idx++] = mVibrators.keyAt(idx);
+ }
+ return vibratorIds;
+ }
+ }
+
+ @Override
+ public Vibrator getVibrator(int vibratorId) {
+ synchronized (mVibrators) {
+ if (mVibrators.contains(vibratorId)) {
+ return mVibrators.get(vibratorId);
+ }
+ }
+ return NullVibrator.getInstance();
+ }
+
+ @Override
+ public Vibrator getDefaultVibrator() {
+ // Returns vibrator ID 0
+ synchronized (mVibrators) {
+ if (mVibrators.size() > 0) {
+ return mVibrators.valueAt(0);
+ }
+ }
+ return NullVibrator.getInstance();
+ }
+
+ @Override
+ public void vibrate(CombinedVibrationEffect effect) {
+ mInputManager.vibrate(mDeviceId, effect, mToken);
+ }
+}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 81ea2f5a17dd..f1d60f6cec83 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -17,7 +17,6 @@
package android.hardware.input;
import android.Manifest;
-import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,9 +28,9 @@ import android.annotation.TestApi;
import android.compat.annotation.ChangeId;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.os.Binder;
import android.os.BlockUntrustedTouchesMode;
import android.os.Build;
+import android.os.CombinedVibrationEffect;
import android.os.Handler;
import android.os.IBinder;
import android.os.InputEventInjectionSync;
@@ -41,9 +40,9 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.SystemClock;
-import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import android.os.VibratorManager;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.Log;
@@ -64,7 +63,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.Executor;
/**
* Provides information about input devices and available key layouts.
@@ -1287,8 +1285,75 @@ public final class InputManager {
* @return The vibrator, never null.
* @hide
*/
- public Vibrator getInputDeviceVibrator(int deviceId) {
- return new InputDeviceVibrator(deviceId);
+ public Vibrator getInputDeviceVibrator(int deviceId, int vibratorId) {
+ return new InputDeviceVibrator(this, deviceId, vibratorId);
+ }
+
+ /**
+ * Gets a vibrator manager service associated with an input device, always create a new
+ * instance.
+ * @return The vibrator manager, never null.
+ * @hide
+ */
+ @NonNull
+ public VibratorManager getInputDeviceVibratorManager(int deviceId) {
+ return new InputDeviceVibratorManager(InputManager.this, deviceId);
+ }
+
+ /*
+ * Get the list of device vibrators
+ * @return The list of vibrators IDs
+ */
+ int[] getVibratorIds(int deviceId) {
+ try {
+ return mIm.getVibratorIds(deviceId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /*
+ * Perform vibration effect
+ */
+ void vibrate(int deviceId, VibrationEffect effect, IBinder token) {
+ try {
+ mIm.vibrate(deviceId, effect, token);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /*
+ * Perform combined vibration effect
+ */
+ void vibrate(int deviceId, CombinedVibrationEffect effect, IBinder token) {
+ try {
+ mIm.vibrateCombined(deviceId, effect, token);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /*
+ * Cancel an ongoing vibration
+ */
+ void cancelVibrate(int deviceId, IBinder token) {
+ try {
+ mIm.cancelVibrate(deviceId, token);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /*
+ * Check if input device is vibrating
+ */
+ boolean isVibrating(int deviceId) {
+ try {
+ return mIm.isVibrating(deviceId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
}
/**
@@ -1401,72 +1466,4 @@ public final class InputManager {
}
}
}
-
- private final class InputDeviceVibrator extends Vibrator {
- private final int mDeviceId;
- private final Binder mToken;
-
- public InputDeviceVibrator(int deviceId) {
- mDeviceId = deviceId;
- mToken = new Binder();
- }
-
- @Override
- public boolean hasVibrator() {
- return true;
- }
-
- @Override
- public boolean isVibrating() {
- throw new UnsupportedOperationException(
- "isVibrating not supported in InputDeviceVibrator");
- }
-
- @Override
- public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
- throw new UnsupportedOperationException(
- "addVibratorStateListener not supported in InputDeviceVibrator");
- }
-
- @Override
- public void addVibratorStateListener(
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OnVibratorStateChangedListener listener) {
- throw new UnsupportedOperationException(
- "addVibratorStateListener not supported in InputDeviceVibrator");
- }
-
- @Override
- public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
- throw new UnsupportedOperationException(
- "removeVibratorStateListener not supported in InputDeviceVibrator");
- }
-
- @Override
- public boolean hasAmplitudeControl() {
- return true;
- }
-
- /**
- * @hide
- */
- @Override
- public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect,
- String reason, @NonNull VibrationAttributes attributes) {
- try {
- mIm.vibrate(mDeviceId, effect, mToken);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
-
- @Override
- public void cancel() {
- try {
- mIm.cancelVibrate(mDeviceId, mToken);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
- }
}
diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java
index c9ebc1b7a3c5..7ec7fffd3588 100644
--- a/core/java/android/os/CombinedVibrationEffect.java
+++ b/core/java/android/os/CombinedVibrationEffect.java
@@ -30,8 +30,6 @@ import java.util.Objects;
* Vibrator Vibrators}.
*
* These effects may be any number of things, from single shot vibrations to complex waveforms.
- *
- * @hide
* @see VibrationEffect
*/
@SuppressWarnings({"ParcelNotFinal", "ParcelCreator"}) // Parcel only extended here.
@@ -94,7 +92,6 @@ public abstract class CombinedVibrationEffect implements Parcelable {
/**
* A combination of haptic effects that should be played in multiple vibrators in sync.
*
- * @hide
* @see CombinedVibrationEffect#startSynced()
*/
public static final class SyncedCombination {
@@ -144,7 +141,6 @@ public abstract class CombinedVibrationEffect implements Parcelable {
/**
* A combination of haptic effects that should be played in multiple vibrators in sequence.
*
- * @hide
* @see CombinedVibrationEffect#startSequential()
*/
public static final class SequentialCombination {
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
new file mode 100644
index 000000000000..1d5a58745279
--- /dev/null
+++ b/core/java/android/os/VibratorManager.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+/**
+ * VibratorManager provides access to multiple vibrators, as well as the ability to run them in
+ * a synchronized fashion.
+ */
+public abstract class VibratorManager {
+ /** @hide */
+ protected static final String TAG = "VibratorManager";
+
+ /**
+ * {@hide}
+ */
+ public VibratorManager() {
+ }
+
+ /**
+ * This method lists all available actuator ids, returning a possible empty list.
+ * If the device has only a single actuator, this should return a single entry with a
+ * default id.
+ */
+ @NonNull
+ public abstract int[] getVibratorIds();
+
+ /**
+ * Returns a Vibrator service for given id.
+ * This allows users to perform a vibration effect on a single actuator.
+ */
+ @NonNull
+ public abstract Vibrator getVibrator(int vibratorId);
+
+ /**
+ * Returns the system default Vibrator service.
+ */
+ @NonNull
+ public abstract Vibrator getDefaultVibrator();
+
+ /**
+ * Vibrates all actuators by passing each VibrationEffect within CombinedVibrationEffect
+ * to the respective actuator, in sync.
+ */
+ public abstract void vibrate(@NonNull CombinedVibrationEffect effect);
+}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 8da833a28efa..df96dc332ff0 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -17,6 +17,7 @@
package android.view;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -28,7 +29,9 @@ import android.os.NullVibrator;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Vibrator;
+import android.os.VibratorManager;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
@@ -72,6 +75,9 @@ public final class InputDevice implements Parcelable {
private Vibrator mVibrator; // guarded by mMotionRanges during initialization
+ @GuardedBy("mMotionRanges")
+ private VibratorManager mVibratorManager;
+
/**
* A mask for input source classes.
*
@@ -415,6 +421,8 @@ public final class InputDevice implements Parcelable {
private static final int MAX_RANGES = 1000;
+ private static final int VIBRATOR_ID_ALL = -1;
+
public static final @android.annotation.NonNull Parcelable.Creator<InputDevice> CREATOR =
new Parcelable.Creator<InputDevice>() {
public InputDevice createFromParcel(Parcel in) {
@@ -785,7 +793,8 @@ public final class InputDevice implements Parcelable {
synchronized (mMotionRanges) {
if (mVibrator == null) {
if (mHasVibrator) {
- mVibrator = InputManager.getInstance().getInputDeviceVibrator(mId);
+ mVibrator = InputManager.getInstance().getInputDeviceVibrator(mId,
+ VIBRATOR_ID_ALL);
} else {
mVibrator = NullVibrator.getInstance();
}
@@ -795,6 +804,24 @@ public final class InputDevice implements Parcelable {
}
/**
+ * Gets the vibrator manager associated with the device.
+ * Even if the device does not have a vibrator manager, the result is never null.
+ * Use {@link VibratorManager#getVibratorIds} to determine whether any vibrator is
+ * present.
+ *
+ * @return The vibrator manager associated with the device, never null.
+ */
+ @NonNull
+ public VibratorManager getVibratorManager() {
+ synchronized (mMotionRanges) {
+ if (mVibratorManager == null) {
+ mVibratorManager = InputManager.getInstance().getInputDeviceVibratorManager(mId);
+ }
+ }
+ return mVibratorManager;
+ }
+
+ /**
* Returns true if input device is enabled.
* @return Whether the input device is enabled.
*/
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f2eb5af51616..ead5ba59d860 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -51,6 +51,7 @@ import android.hardware.input.TouchCalibration;
import android.media.AudioManager;
import android.os.Binder;
import android.os.Bundle;
+import android.os.CombinedVibrationEffect;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
@@ -253,7 +254,11 @@ public class InputManagerService extends IInputManager.Stub
private static native void nativeReloadCalibration(long ptr);
private static native void nativeVibrate(long ptr, int deviceId, long[] pattern,
int[] amplitudes, int repeat, int token);
+ private static native void nativeVibrateCombined(long ptr, int deviceId, long[] pattern,
+ SparseArray<int[]> amplitudes, int repeat, int token);
private static native void nativeCancelVibrate(long ptr, int deviceId, int token);
+ private static native boolean nativeIsVibrating(long ptr, int deviceId);
+ private static native int[] nativeGetVibratorIds(long ptr, int deviceId);
private static native void nativeReloadKeyboardLayouts(long ptr);
private static native void nativeReloadDeviceAliases(long ptr);
private static native String nativeDump(long ptr);
@@ -1790,43 +1795,57 @@ public class InputManagerService extends IInputManager.Stub
return result;
}
- // Binder call
- @Override
- public void vibrate(int deviceId, VibrationEffect effect, IBinder token) {
- long[] pattern;
- int[] amplitudes;
- int repeat;
- if (effect instanceof VibrationEffect.OneShot) {
- VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
- pattern = new long[] { 0, oneShot.getDuration() };
- int amplitude = oneShot.getAmplitude();
- // android framework uses DEFAULT_AMPLITUDE to signal that the vibration
- // should use some built-in default value, denoted here as DEFAULT_VIBRATION_MAGNITUDE
- if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
- amplitude = DEFAULT_VIBRATION_MAGNITUDE;
- }
- amplitudes = new int[] { 0, amplitude };
- repeat = -1;
- } else if (effect instanceof VibrationEffect.Waveform) {
- VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
- pattern = waveform.getTimings();
- amplitudes = waveform.getAmplitudes();
- for (int i = 0; i < amplitudes.length; i++) {
- if (amplitudes[i] == VibrationEffect.DEFAULT_AMPLITUDE) {
- amplitudes[i] = DEFAULT_VIBRATION_MAGNITUDE;
- }
- }
- repeat = waveform.getRepeatIndex();
- } else {
- // TODO: Add support for prebaked effects
- Log.w(TAG, "Pre-baked effects aren't supported on input devices");
- return;
+ private static class VibrationInfo {
+ private long[] mPattern = new long[0];
+ private int[] mAmplitudes = new int[0];
+ private int mRepeat = -1;
+
+ public long[] getPattern() {
+ return mPattern;
+ }
+
+ public int[] getAmplitudes() {
+ return mAmplitudes;
}
- if (repeat >= pattern.length) {
- throw new ArrayIndexOutOfBoundsException();
+ public int getRepeatIndex() {
+ return mRepeat;
}
+ VibrationInfo(VibrationEffect effect) {
+ if (effect instanceof VibrationEffect.OneShot) {
+ VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
+ mPattern = new long[] { 0, oneShot.getDuration() };
+ int amplitude = oneShot.getAmplitude();
+ // android framework uses DEFAULT_AMPLITUDE to signal that the vibration
+ // should use some built-in default value, denoted here as
+ // DEFAULT_VIBRATION_MAGNITUDE
+ if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
+ amplitude = DEFAULT_VIBRATION_MAGNITUDE;
+ }
+ mAmplitudes = new int[] { 0, amplitude };
+ mRepeat = -1;
+ } else if (effect instanceof VibrationEffect.Waveform) {
+ VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
+ mPattern = waveform.getTimings();
+ mAmplitudes = waveform.getAmplitudes();
+ for (int i = 0; i < mAmplitudes.length; i++) {
+ if (mAmplitudes[i] == VibrationEffect.DEFAULT_AMPLITUDE) {
+ mAmplitudes[i] = DEFAULT_VIBRATION_MAGNITUDE;
+ }
+ }
+ mRepeat = waveform.getRepeatIndex();
+ if (mRepeat >= mPattern.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ } else {
+ // TODO: Add support for prebaked effects
+ Slog.w(TAG, "Pre-baked effects aren't supported on input devices");
+ }
+ }
+ }
+
+ private VibratorToken getVibratorToken(int deviceId, IBinder token) {
VibratorToken v;
synchronized (mVibratorLock) {
v = mVibratorTokens.get(token);
@@ -1841,9 +1860,70 @@ public class InputManagerService extends IInputManager.Stub
mVibratorTokens.put(token, v);
}
}
+ return v;
+ }
+
+ // Binder call
+ @Override
+ public void vibrate(int deviceId, VibrationEffect effect, IBinder token) {
+ VibrationInfo info = new VibrationInfo(effect);
+ VibratorToken v = getVibratorToken(deviceId, token);
synchronized (v) {
v.mVibrating = true;
- nativeVibrate(mPtr, deviceId, pattern, amplitudes, repeat, v.mTokenValue);
+ nativeVibrate(mPtr, deviceId, info.getPattern(), info.getAmplitudes(),
+ info.getRepeatIndex(), v.mTokenValue);
+ }
+ }
+
+ // Binder call
+ @Override
+ public int[] getVibratorIds(int deviceId) {
+ return nativeGetVibratorIds(mPtr, deviceId);
+ }
+
+ // Binder call
+ @Override
+ public boolean isVibrating(int deviceId) {
+ return nativeIsVibrating(mPtr, deviceId);
+ }
+
+ // Binder call
+ @Override
+ public void vibrateCombined(int deviceId, CombinedVibrationEffect effect, IBinder token) {
+ VibratorToken v = getVibratorToken(deviceId, token);
+ synchronized (v) {
+ if (!(effect instanceof CombinedVibrationEffect.Mono)
+ && !(effect instanceof CombinedVibrationEffect.Stereo)) {
+ Slog.e(TAG, "Only Mono and Stereo effects are supported");
+ return;
+ }
+
+ v.mVibrating = true;
+ if (effect instanceof CombinedVibrationEffect.Mono) {
+ CombinedVibrationEffect.Mono mono = (CombinedVibrationEffect.Mono) effect;
+ VibrationInfo info = new VibrationInfo(mono.getEffect());
+ nativeVibrate(mPtr, deviceId, info.getPattern(), info.getAmplitudes(),
+ info.getRepeatIndex(), v.mTokenValue);
+ } else if (effect instanceof CombinedVibrationEffect.Stereo) {
+ CombinedVibrationEffect.Stereo stereo = (CombinedVibrationEffect.Stereo) effect;
+ SparseArray<VibrationEffect> effects = stereo.getEffects();
+ long[] pattern = new long[0];
+ int repeat = Integer.MIN_VALUE;
+ SparseArray<int[]> amplitudes = new SparseArray<int[]>(effects.size());
+ for (int i = 0; i < effects.size(); i++) {
+ VibrationInfo info = new VibrationInfo(effects.valueAt(i));
+ // Pattern of all effects should be same
+ if (pattern.length == 0) {
+ pattern = info.getPattern();
+ }
+ if (repeat == Integer.MIN_VALUE) {
+ repeat = info.getRepeatIndex();
+ }
+ amplitudes.put(effects.keyAt(i), info.getAmplitudes());
+ }
+ nativeVibrateCombined(mPtr, deviceId, pattern, amplitudes, repeat,
+ v.mTokenValue);
+ }
}
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 9e0fb567975a..bd55353e577b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -149,6 +149,14 @@ static struct {
jmethodID getAffineTransform;
} gTouchCalibrationClassInfo;
+static struct {
+ jclass clazz;
+ jmethodID constructor;
+ jmethodID keyAt;
+ jmethodID valueAt;
+ jmethodID size;
+} gSparseArrayClassInfo;
+
// --- Global functions ---
template<typename T>
@@ -1705,19 +1713,73 @@ static void nativeVibrate(JNIEnv* env, jclass /* clazz */, jlong ptr, jint devic
patternObj, nullptr));
jint* amplitudes = static_cast<jint*>(env->GetPrimitiveArrayCritical(amplitudesObj, nullptr));
- std::vector<VibrationElement> elements(patternSize);
+ VibrationSequence sequence(patternSize);
+ std::vector<int32_t> vibrators = im->getInputManager()->getReader()->getVibratorIds(deviceId);
for (size_t i = 0; i < patternSize; i++) {
// VibrationEffect.validate guarantees duration > 0.
std::chrono::milliseconds duration(patternMillis[i]);
- elements[i].duration = std::min(duration, MAX_VIBRATE_PATTERN_DELAY_MILLIS);
- // TODO: (b/161629089) apply channel specific amplitudes from development API.
- elements[i].channels = {static_cast<uint8_t>(amplitudes[i]),
- static_cast<uint8_t>(amplitudes[i])};
+ VibrationElement element(CHANNEL_SIZE);
+ element.duration = std::min(duration, MAX_VIBRATE_PATTERN_DELAY_MILLIS);
+ // Vibrate on both channels
+ for (int32_t channel = 0; channel < vibrators.size(); channel++) {
+ element.addChannel(vibrators[channel], static_cast<uint8_t>(amplitudes[i]));
+ }
+ sequence.addElement(element);
}
env->ReleasePrimitiveArrayCritical(patternObj, patternMillis, JNI_ABORT);
env->ReleasePrimitiveArrayCritical(amplitudesObj, amplitudes, JNI_ABORT);
- im->getInputManager()->getReader()->vibrate(deviceId, elements, repeat, token);
+ im->getInputManager()->getReader()->vibrate(deviceId, sequence, repeat, token);
+}
+
+static void nativeVibrateCombined(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+ jlongArray patternObj, jobject amplitudesObj, jint repeat,
+ jint token) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ size_t patternSize = env->GetArrayLength(patternObj);
+
+ if (patternSize > MAX_VIBRATE_PATTERN_SIZE) {
+ ALOGI("Skipped requested vibration because the pattern size is %zu "
+ "which is more than the maximum supported size of %d.",
+ patternSize, MAX_VIBRATE_PATTERN_SIZE);
+ return; // limit to reasonable size
+ }
+ const jlong* patternMillis = env->GetLongArrayElements(patternObj, nullptr);
+
+ std::array<jint*, CHANNEL_SIZE> amplitudesArray;
+ std::array<jint, CHANNEL_SIZE> vibratorIdArray;
+ jint amplSize = env->CallIntMethod(amplitudesObj, gSparseArrayClassInfo.size);
+ if (amplSize > CHANNEL_SIZE) {
+ ALOGE("Can not fit into input device vibration element.");
+ return;
+ }
+
+ for (int i = 0; i < amplSize; i++) {
+ vibratorIdArray[i] = env->CallIntMethod(amplitudesObj, gSparseArrayClassInfo.keyAt, i);
+ jintArray arr = static_cast<jintArray>(
+ env->CallObjectMethod(amplitudesObj, gSparseArrayClassInfo.valueAt, i));
+ amplitudesArray[i] = env->GetIntArrayElements(arr, nullptr);
+ if (env->GetArrayLength(arr) != patternSize) {
+ ALOGE("Amplitude length not equal to pattern length!");
+ return;
+ }
+ }
+
+ VibrationSequence sequence(patternSize);
+ for (size_t i = 0; i < patternSize; i++) {
+ VibrationElement element(CHANNEL_SIZE);
+ // VibrationEffect.validate guarantees duration > 0.
+ std::chrono::milliseconds duration(patternMillis[i]);
+ element.duration = std::min(duration, MAX_VIBRATE_PATTERN_DELAY_MILLIS);
+ for (int32_t channel = 0; channel < CHANNEL_SIZE; channel++) {
+ element.addChannel(vibratorIdArray[channel],
+ static_cast<uint8_t>(amplitudesArray[channel][i]));
+ }
+ sequence.addElement(element);
+ }
+
+ im->getInputManager()->getReader()->vibrate(deviceId, sequence, repeat, token);
}
static void nativeCancelVibrate(JNIEnv* /* env */,
@@ -1727,6 +1789,23 @@ static void nativeCancelVibrate(JNIEnv* /* env */,
im->getInputManager()->getReader()->cancelVibrate(deviceId, token);
}
+static bool nativeIsVibrating(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint deviceId) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ return im->getInputManager()->getReader()->isVibrating(deviceId);
+}
+
+static jintArray nativeGetVibratorIds(JNIEnv* env, jclass clazz, jlong ptr, jint deviceId) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ std::vector<int32_t> vibrators = im->getInputManager()->getReader()->getVibratorIds(deviceId);
+
+ jintArray vibIdArray = env->NewIntArray(vibrators.size());
+ if (vibIdArray != nullptr) {
+ env->SetIntArrayRegion(vibIdArray, 0, vibrators.size(), vibrators.data());
+ }
+ return vibIdArray;
+}
+
static void nativeReloadKeyboardLayouts(JNIEnv* /* env */,
jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1872,7 +1951,11 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"nativeSetInteractive", "(JZ)V", (void*)nativeSetInteractive},
{"nativeReloadCalibration", "(J)V", (void*)nativeReloadCalibration},
{"nativeVibrate", "(JI[J[III)V", (void*)nativeVibrate},
+ {"nativeVibrateCombined", "(JI[JLandroid/util/SparseArray;II)V",
+ (void*)nativeVibrateCombined},
{"nativeCancelVibrate", "(JII)V", (void*)nativeCancelVibrate},
+ {"nativeIsVibrating", "(JI)Z", (void*)nativeIsVibrating},
+ {"nativeGetVibratorIds", "(JI)[I", (void*)nativeGetVibratorIds},
{"nativeReloadKeyboardLayouts", "(J)V", (void*)nativeReloadKeyboardLayouts},
{"nativeReloadDeviceAliases", "(J)V", (void*)nativeReloadDeviceAliases},
{"nativeDump", "(J)Ljava/lang/String;", (void*)nativeDump},
@@ -2048,6 +2131,15 @@ int register_android_server_InputManager(JNIEnv* env) {
GET_METHOD_ID(gTouchCalibrationClassInfo.getAffineTransform, gTouchCalibrationClassInfo.clazz,
"getAffineTransform", "()[F");
+ // SparseArray
+ FIND_CLASS(gSparseArrayClassInfo.clazz, "android/util/SparseArray");
+ gSparseArrayClassInfo.clazz = jclass(env->NewGlobalRef(gSparseArrayClassInfo.clazz));
+ GET_METHOD_ID(gSparseArrayClassInfo.constructor, gSparseArrayClassInfo.clazz, "<init>", "()V");
+ GET_METHOD_ID(gSparseArrayClassInfo.keyAt, gSparseArrayClassInfo.clazz, "keyAt", "(I)I");
+ GET_METHOD_ID(gSparseArrayClassInfo.valueAt, gSparseArrayClassInfo.clazz, "valueAt",
+ "(I)Ljava/lang/Object;");
+ GET_METHOD_ID(gSparseArrayClassInfo.size, gSparseArrayClassInfo.clazz, "size", "()I");
+
return 0;
}