Merge "Support multi-channel FF vibration effect for input device."

This commit is contained in:
TreeHugger Robot
2020-12-08 23:33:24 +00:00
committed by Android (Google) Code Review
10 changed files with 624 additions and 119 deletions

View File

@@ -34862,6 +34862,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);
@@ -36088,6 +36109,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);
@@ -51337,6 +51365,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();

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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();
}
@@ -794,6 +803,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.

View File

@@ -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;
@@ -264,7 +265,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);
@@ -1801,43 +1806,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;
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;
}
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");
}
repeat = waveform.getRepeatIndex();
} else {
// TODO: Add support for prebaked effects
Log.w(TAG, "Pre-baked effects aren't supported on input devices");
return;
}
if (repeat >= pattern.length) {
throw new ArrayIndexOutOfBoundsException();
}
}
private VibratorToken getVibratorToken(int deviceId, IBinder token) {
VibratorToken v;
synchronized (mVibratorLock) {
v = mVibratorTokens.get(token);
@@ -1852,9 +1871,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);
}
}
}

View File

@@ -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;
}