Support multi-channel FF vibration effect for input device.
Support multi-channel FF vibration effect with CombinedVibration effect API. Bug: 161629089 Test: atest InputDeviceVibratorTest Change-Id: Ie1bd82a324501704cfc673c7cea8af6aae974407
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
94
core/java/android/hardware/input/InputDeviceVibrator.java
Normal file
94
core/java/android/hardware/input/InputDeviceVibrator.java
Normal 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);
|
||||
}
|
||||
}
|
||||
125
core/java/android/hardware/input/InputDeviceVibratorManager.java
Normal file
125
core/java/android/hardware/input/InputDeviceVibratorManager.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
61
core/java/android/os/VibratorManager.java
Normal file
61
core/java/android/os/VibratorManager.java
Normal 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);
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
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);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user