diff options
| -rw-r--r-- | core/api/current.txt | 29 | ||||
| -rw-r--r-- | core/java/android/hardware/input/IInputManager.aidl | 4 | ||||
| -rw-r--r-- | core/java/android/hardware/input/InputDeviceVibrator.java | 94 | ||||
| -rw-r--r-- | core/java/android/hardware/input/InputDeviceVibratorManager.java | 125 | ||||
| -rw-r--r-- | core/java/android/hardware/input/InputManager.java | 145 | ||||
| -rw-r--r-- | core/java/android/os/CombinedVibrationEffect.java | 4 | ||||
| -rw-r--r-- | core/java/android/os/VibratorManager.java | 61 | ||||
| -rw-r--r-- | core/java/android/view/InputDevice.java | 29 | ||||
| -rw-r--r-- | services/core/java/com/android/server/input/InputManagerService.java | 148 | ||||
| -rw-r--r-- | services/core/jni/com_android_server_input_InputManagerService.cpp | 104 |
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; } |