Loading core/java/android/hardware/input/InputManager.java +17 −1 Original line number Diff line number Diff line Loading @@ -56,6 +56,7 @@ import android.view.PointerIcon; import android.view.VerifiedInputEvent; import android.view.WindowManager.LayoutParams; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; Loading Loading @@ -268,6 +269,21 @@ public final class InputManager { mIm = im; } /** * Gets an instance of the input manager. * * @return The input manager instance. * * @hide */ @VisibleForTesting public static InputManager resetInstance(IInputManager inputManagerService) { synchronized (InputManager.class) { sInstance = new InputManager(inputManagerService); return sInstance; } } /** * Gets an instance of the input manager. * Loading Loading @@ -1428,7 +1444,7 @@ public final class InputManager { @Override public boolean hasAmplitudeControl() { return false; return true; } /** Loading core/java/android/view/InputDevice.java +8 −2 Original line number Diff line number Diff line Loading @@ -29,6 +29,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.Vibrator; import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; Loading Loading @@ -423,9 +425,13 @@ public final class InputDevice implements Parcelable { } }; // Called by native code. /** * Called by native code * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId, @VisibleForTesting public InputDevice(int id, int generation, int controllerNumber, String name, int vendorId, int productId, String descriptor, boolean isExternal, int sources, int keyboardType, KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasMicrophone, boolean hasButtonUnderPad) { Loading services/core/java/com/android/server/VibratorService.java +70 −123 Original line number Diff line number Diff line Loading @@ -29,7 +29,6 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.res.Resources; import android.hardware.input.InputManager; import android.hardware.vibrator.IVibrator; import android.os.BatteryStats; import android.os.Binder; Loading Loading @@ -60,13 +59,13 @@ import android.os.WorkSource; import android.util.Slog; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.InputDevice; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.server.vibrator.InputDeviceDelegate; import com.android.server.vibrator.VibrationScaler; import com.android.server.vibrator.VibrationSettings; Loading @@ -82,8 +81,8 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class VibratorService extends IVibratorService.Stub implements InputManager.InputDeviceListener { /** System implementation of {@link IVibratorService}. */ public class VibratorService extends IVibratorService.Stub { private static final String TAG = "VibratorService"; private static final SimpleDateFormat DEBUG_DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); Loading Loading @@ -122,19 +121,13 @@ public class VibratorService extends IVibratorService.Stub private final IBatteryStats mBatteryStatsService; private final String mSystemUiPackage; private PowerManagerInternal mPowerManagerInternal; private InputManager mIm; private VibrationSettings mVibrationSettings; private VibrationScaler mVibrationScaler; private InputDeviceDelegate mInputDeviceDelegate; private final NativeWrapper mNativeWrapper; private volatile VibrateWaveformThread mThread; // mInputDeviceVibrators lock should be acquired after mLock, if both are // to be acquired private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<>(); private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators @GuardedBy("mLock") private Vibration mCurrentVibration; private int mCurVibUid = -1; Loading Loading @@ -343,6 +336,7 @@ public class VibratorService extends IVibratorService.Stub public enum Status { RUNNING, FINISHED, FORWARDED_TO_INPUT_DEVICES, CANCELLED, ERROR_APP_OPS, IGNORED, Loading Loading @@ -577,9 +571,9 @@ public class VibratorService extends IVibratorService.Stub public void systemReady() { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorService#systemReady"); try { mIm = mContext.getSystemService(InputManager.class); mVibrationSettings = new VibrationSettings(mContext, mH); mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings); mInputDeviceDelegate = new InputDeviceDelegate(mContext, mH); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); mPowerManagerInternal.registerLowPowerModeObserver( Loading Loading @@ -707,12 +701,8 @@ public class VibratorService extends IVibratorService.Stub @Override // Binder call public boolean hasAmplitudeControl() { synchronized (mInputDeviceVibrators) { // Input device vibrators don't support amplitude controls yet, but are still used over // the system vibrator when connected. return hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL) && mInputDeviceVibrators.isEmpty(); } // Input device vibrators always support amplitude controls. return mInputDeviceDelegate.isAvailable() || hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL); } @Override // Binder call Loading Loading @@ -1059,10 +1049,8 @@ public class VibratorService extends IVibratorService.Stub Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); doVibratorOn(vib); } else if (vib.effect instanceof VibrationEffect.Waveform) { // mThread better be null here. doCancelVibrate should always be // called before startNextVibrationLocked or startVibrationLocked. mThread = new VibrateWaveformThread(vib); mThread.start(); Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); doVibratorWaveformEffectLocked(vib); } else if (vib.effect instanceof VibrationEffect.Prebaked) { Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); doVibratorPrebakedEffectLocked(vib); Loading Loading @@ -1193,7 +1181,8 @@ public class VibratorService extends IVibratorService.Stub private void updateVibrators() { synchronized (mLock) { boolean devicesUpdated = updateInputDeviceVibratorsLocked(); boolean devicesUpdated = mInputDeviceDelegate.updateInputDeviceVibrators( mVibrationSettings.shouldVibrateInputDevices()); boolean lowPowerModeUpdated = updateLowPowerModeLocked(); if (devicesUpdated || lowPowerModeUpdated) { Loading @@ -1205,41 +1194,6 @@ public class VibratorService extends IVibratorService.Stub } } private boolean updateInputDeviceVibratorsLocked() { boolean changed = false; boolean vibrateInputDevices = mVibrationSettings.shouldVibrateInputDevices(); if (vibrateInputDevices != mVibrateInputDevicesSetting) { changed = true; mVibrateInputDevicesSetting = vibrateInputDevices; } if (mVibrateInputDevicesSetting) { if (!mInputDeviceListenerRegistered) { mInputDeviceListenerRegistered = true; mIm.registerInputDeviceListener(this, mH); } } else { if (mInputDeviceListenerRegistered) { mInputDeviceListenerRegistered = false; mIm.unregisterInputDeviceListener(this); } } mInputDeviceVibrators.clear(); if (mVibrateInputDevicesSetting) { int[] ids = mIm.getInputDeviceIds(); for (int i = 0; i < ids.length; i++) { InputDevice device = mIm.getInputDevice(ids[i]); Vibrator vibrator = device.getVibrator(); if (vibrator.hasVibrator()) { mInputDeviceVibrators.add(vibrator); } } return true; } return changed; } private boolean updateLowPowerModeLocked() { boolean lowPowerMode = mPowerManagerInternal .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled; Loading Loading @@ -1268,58 +1222,37 @@ public class VibratorService extends IVibratorService.Stub } } @Override public void onInputDeviceAdded(int deviceId) { updateVibrators(); } @Override public void onInputDeviceChanged(int deviceId) { updateVibrators(); } @Override public void onInputDeviceRemoved(int deviceId) { updateVibrators(); } private boolean doVibratorExists() { // For now, we choose to ignore the presence of input devices that have vibrators // when reporting whether the device has a vibrator. Applications often use this // information to decide whether to enable certain features so they expect the // result of hasVibrator() to be constant. For now, just report whether // the device has a built-in vibrator. //synchronized (mInputDeviceVibrators) { // return !mInputDeviceVibrators.isEmpty() || vibratorExists(); //} return mNativeWrapper.vibratorExists(); } private void doVibratorOn(Vibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn"); try { synchronized (mInputDeviceVibrators) { final VibrationEffect.OneShot oneShot = vib.effect.resolve( mDefaultVibrationAmplitude); final VibrationEffect.OneShot oneShot = vib.effect.resolve(mDefaultVibrationAmplitude); if (DEBUG) { Slog.d(TAG, "Turning vibrator on for " + oneShot.getDuration() + " ms" + " with amplitude " + oneShot.getAmplitude() + "."); } noteVibratorOnLocked(vib.uid, oneShot.getDuration()); final int vibratorCount = mInputDeviceVibrators.size(); if (vibratorCount != 0) { for (int i = 0; i < vibratorCount; i++) { mInputDeviceVibrators.get(i).vibrate(vib.uid, vib.opPkg, oneShot, vib.reason, vib.attrs); } boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( vib.uid, vib.opPkg, oneShot, vib.reason, vib.attrs); if (inputDevicesAvailable) { // The set current vibration is no longer being played by this service, so drop it. mCurrentVibration = null; endVibrationLocked(vib, VibrationInfo.Status.FORWARDED_TO_INPUT_DEVICES); } else { noteVibratorOnLocked(vib.uid, oneShot.getDuration()); // Note: ordering is important here! Many haptic drivers will reset their // amplitude when enabled, so we always have to enable first, then set the // amplitude. mNativeWrapper.vibratorOn(oneShot.getDuration(), vib.id); doVibratorSetAmplitude(oneShot.getAmplitude()); } } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } Loading @@ -1334,19 +1267,34 @@ public class VibratorService extends IVibratorService.Stub private void doVibratorOff() { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOff"); try { synchronized (mInputDeviceVibrators) { if (DEBUG) { Slog.d(TAG, "Turning vibrator off."); } noteVibratorOffLocked(); final int vibratorCount = mInputDeviceVibrators.size(); if (vibratorCount != 0) { for (int i = 0; i < vibratorCount; i++) { mInputDeviceVibrators.get(i).cancel(); } } else { boolean inputDevicesAvailable = mInputDeviceDelegate.cancelVibrateIfAvailable(); if (!inputDevicesAvailable) { mNativeWrapper.vibratorOff(); } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } } @GuardedBy("mLock") private void doVibratorWaveformEffectLocked(Vibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorWaveformEffectLocked"); try { boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( vib.uid, vib.opPkg, vib.effect, vib.reason, vib.attrs); if (inputDevicesAvailable) { // The set current vibration is no longer being played by this service, so drop it. mCurrentVibration = null; endVibrationLocked(vib, VibrationInfo.Status.FORWARDED_TO_INPUT_DEVICES); } else { // mThread better be null here. doCancelVibrate should always be // called before startNextVibrationLocked or startVibrationLocked. mThread = new VibrateWaveformThread(vib); mThread.start(); } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); Loading @@ -1358,12 +1306,9 @@ public class VibratorService extends IVibratorService.Stub Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorPrebakedEffectLocked"); try { final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect; final boolean usingInputDeviceVibrators; synchronized (mInputDeviceVibrators) { usingInputDeviceVibrators = !mInputDeviceVibrators.isEmpty(); } // Input devices don't support prebaked effect, so skip trying it with them. if (!usingInputDeviceVibrators) { // Input devices don't support prebaked effect, so skip trying it with them and allow // fallback to be attempted. if (!mInputDeviceDelegate.isAvailable()) { long duration = mNativeWrapper.vibratorPerformEffect( prebaked.getId(), prebaked.getEffectStrength(), vib.id); if (duration > 0) { Loading Loading @@ -1401,15 +1346,17 @@ public class VibratorService extends IVibratorService.Stub try { final VibrationEffect.Composed composed = (VibrationEffect.Composed) vib.effect; final boolean usingInputDeviceVibrators; synchronized (mInputDeviceVibrators) { usingInputDeviceVibrators = !mInputDeviceVibrators.isEmpty(); } // Input devices don't support composed effect, so skip trying it with them. if (usingInputDeviceVibrators || !hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { endVibrationLocked(vib, VibrationInfo.Status.IGNORED_UNSUPPORTED); boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( vib.uid, vib.opPkg, composed, vib.reason, vib.attrs); if (inputDevicesAvailable) { // The set current vibration is no longer being played by this service, so drop it. mCurrentVibration = null; endVibrationLocked(vib, VibrationInfo.Status.FORWARDED_TO_INPUT_DEVICES); return; } else if (!hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { // The set current vibration is not actually playing, so drop it. mCurrentVibration = null; endVibrationLocked(vib, VibrationInfo.Status.IGNORED_UNSUPPORTED); return; } Loading services/core/java/com/android/server/vibrator/InputDeviceDelegate.java 0 → 100644 +171 −0 Original line number Diff line number Diff line /* * 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 com.android.server.vibrator; import android.content.Context; import android.hardware.input.InputManager; import android.os.Handler; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.util.SparseArray; import android.view.InputDevice; import com.android.internal.annotations.GuardedBy; /** Delegates vibrations to all connected {@link InputDevice} with available {@link Vibrator}. */ // TODO(b/159207608): Make this package-private once vibrator services are moved to this package public final class InputDeviceDelegate implements InputManager.InputDeviceListener { private static final String TAG = "InputDeviceDelegate"; private final Object mLock = new Object(); private final Handler mHandler; private final InputManager mInputManager; @GuardedBy("mLock") private final SparseArray<Vibrator> mInputDeviceVibrators = new SparseArray<>(); /** * Flag updated via {@link #updateInputDeviceVibrators(boolean)}, holding the value of {@link * android.provider.Settings.System#VIBRATE_INPUT_DEVICES}. */ @GuardedBy("mLock") private boolean mShouldVibrateInputDevices; public InputDeviceDelegate(Context context, Handler handler) { mHandler = handler; mInputManager = context.getSystemService(InputManager.class); } @Override public void onInputDeviceAdded(int deviceId) { updateInputDevice(deviceId); } @Override public void onInputDeviceChanged(int deviceId) { updateInputDevice(deviceId); } @Override public void onInputDeviceRemoved(int deviceId) { synchronized (mLock) { mInputDeviceVibrators.remove(deviceId); } } /** * Return {@code true} is there are input devices with vibrators available and vibrations should * be delegated to them. */ public boolean isAvailable() { synchronized (mLock) { // mInputDeviceVibrators is cleared when settings are disabled, so this check is enough. return mInputDeviceVibrators.size() > 0; } } /** * Vibrate all {@link InputDevice} with {@link Vibrator} available using given effect. * * @return {@link #isAvailable()} */ public boolean vibrateIfAvailable(int uid, String opPkg, VibrationEffect effect, String reason, VibrationAttributes attrs) { synchronized (mLock) { for (int i = 0; i < mInputDeviceVibrators.size(); i++) { mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, effect, reason, attrs); } return mInputDeviceVibrators.size() > 0; } } /** * Cancel vibration on all {@link InputDevice} with {@link Vibrator} available. * * @return {@link #isAvailable()} */ public boolean cancelVibrateIfAvailable() { synchronized (mLock) { for (int i = 0; i < mInputDeviceVibrators.size(); i++) { mInputDeviceVibrators.valueAt(i).cancel(); } return mInputDeviceVibrators.size() > 0; } } /** * Updates the list of {@link InputDevice} vibrators based on the {@link * VibrationSettings#shouldVibrateInputDevices()} setting current value and the * devices currently available in {@link InputManager#getInputDeviceIds()}. * * @return true if there was any change in input devices available or related settings. */ public boolean updateInputDeviceVibrators(boolean vibrateInputDevices) { synchronized (mLock) { if (vibrateInputDevices == mShouldVibrateInputDevices) { // No need to update if settings haven't changed. return false; } mShouldVibrateInputDevices = vibrateInputDevices; mInputDeviceVibrators.clear(); if (vibrateInputDevices) { // Register the listener first so any device added/updated/removed after the call to // getInputDeviceIds() will trigger the callbacks (which will wait on the lock for // this loop to finish). mInputManager.registerInputDeviceListener(this, mHandler); for (int deviceId : mInputManager.getInputDeviceIds()) { InputDevice device = mInputManager.getInputDevice(deviceId); if (device == null) { continue; } Vibrator vibrator = device.getVibrator(); if (vibrator.hasVibrator()) { mInputDeviceVibrators.put(device.getId(), vibrator); } } } else { mInputManager.unregisterInputDeviceListener(this); } } return true; } private void updateInputDevice(int deviceId) { synchronized (mLock) { if (!mShouldVibrateInputDevices) { // No need to keep this device vibrator if setting is off. return; } InputDevice device = mInputManager.getInputDevice(deviceId); if (device == null) { mInputDeviceVibrators.remove(deviceId); return; } Vibrator vibrator = device.getVibrator(); if (vibrator.hasVibrator()) { mInputDeviceVibrators.put(deviceId, vibrator); } else { mInputDeviceVibrators.remove(deviceId); } } } } services/tests/servicestests/src/com/android/server/VibratorServiceTest.java +96 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/hardware/input/InputManager.java +17 −1 Original line number Diff line number Diff line Loading @@ -56,6 +56,7 @@ import android.view.PointerIcon; import android.view.VerifiedInputEvent; import android.view.WindowManager.LayoutParams; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; Loading Loading @@ -268,6 +269,21 @@ public final class InputManager { mIm = im; } /** * Gets an instance of the input manager. * * @return The input manager instance. * * @hide */ @VisibleForTesting public static InputManager resetInstance(IInputManager inputManagerService) { synchronized (InputManager.class) { sInstance = new InputManager(inputManagerService); return sInstance; } } /** * Gets an instance of the input manager. * Loading Loading @@ -1428,7 +1444,7 @@ public final class InputManager { @Override public boolean hasAmplitudeControl() { return false; return true; } /** Loading
core/java/android/view/InputDevice.java +8 −2 Original line number Diff line number Diff line Loading @@ -29,6 +29,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.Vibrator; import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; Loading Loading @@ -423,9 +425,13 @@ public final class InputDevice implements Parcelable { } }; // Called by native code. /** * Called by native code * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId, @VisibleForTesting public InputDevice(int id, int generation, int controllerNumber, String name, int vendorId, int productId, String descriptor, boolean isExternal, int sources, int keyboardType, KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasMicrophone, boolean hasButtonUnderPad) { Loading
services/core/java/com/android/server/VibratorService.java +70 −123 Original line number Diff line number Diff line Loading @@ -29,7 +29,6 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.res.Resources; import android.hardware.input.InputManager; import android.hardware.vibrator.IVibrator; import android.os.BatteryStats; import android.os.Binder; Loading Loading @@ -60,13 +59,13 @@ import android.os.WorkSource; import android.util.Slog; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.InputDevice; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.server.vibrator.InputDeviceDelegate; import com.android.server.vibrator.VibrationScaler; import com.android.server.vibrator.VibrationSettings; Loading @@ -82,8 +81,8 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class VibratorService extends IVibratorService.Stub implements InputManager.InputDeviceListener { /** System implementation of {@link IVibratorService}. */ public class VibratorService extends IVibratorService.Stub { private static final String TAG = "VibratorService"; private static final SimpleDateFormat DEBUG_DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); Loading Loading @@ -122,19 +121,13 @@ public class VibratorService extends IVibratorService.Stub private final IBatteryStats mBatteryStatsService; private final String mSystemUiPackage; private PowerManagerInternal mPowerManagerInternal; private InputManager mIm; private VibrationSettings mVibrationSettings; private VibrationScaler mVibrationScaler; private InputDeviceDelegate mInputDeviceDelegate; private final NativeWrapper mNativeWrapper; private volatile VibrateWaveformThread mThread; // mInputDeviceVibrators lock should be acquired after mLock, if both are // to be acquired private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<>(); private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators @GuardedBy("mLock") private Vibration mCurrentVibration; private int mCurVibUid = -1; Loading Loading @@ -343,6 +336,7 @@ public class VibratorService extends IVibratorService.Stub public enum Status { RUNNING, FINISHED, FORWARDED_TO_INPUT_DEVICES, CANCELLED, ERROR_APP_OPS, IGNORED, Loading Loading @@ -577,9 +571,9 @@ public class VibratorService extends IVibratorService.Stub public void systemReady() { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorService#systemReady"); try { mIm = mContext.getSystemService(InputManager.class); mVibrationSettings = new VibrationSettings(mContext, mH); mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings); mInputDeviceDelegate = new InputDeviceDelegate(mContext, mH); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); mPowerManagerInternal.registerLowPowerModeObserver( Loading Loading @@ -707,12 +701,8 @@ public class VibratorService extends IVibratorService.Stub @Override // Binder call public boolean hasAmplitudeControl() { synchronized (mInputDeviceVibrators) { // Input device vibrators don't support amplitude controls yet, but are still used over // the system vibrator when connected. return hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL) && mInputDeviceVibrators.isEmpty(); } // Input device vibrators always support amplitude controls. return mInputDeviceDelegate.isAvailable() || hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL); } @Override // Binder call Loading Loading @@ -1059,10 +1049,8 @@ public class VibratorService extends IVibratorService.Stub Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); doVibratorOn(vib); } else if (vib.effect instanceof VibrationEffect.Waveform) { // mThread better be null here. doCancelVibrate should always be // called before startNextVibrationLocked or startVibrationLocked. mThread = new VibrateWaveformThread(vib); mThread.start(); Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); doVibratorWaveformEffectLocked(vib); } else if (vib.effect instanceof VibrationEffect.Prebaked) { Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); doVibratorPrebakedEffectLocked(vib); Loading Loading @@ -1193,7 +1181,8 @@ public class VibratorService extends IVibratorService.Stub private void updateVibrators() { synchronized (mLock) { boolean devicesUpdated = updateInputDeviceVibratorsLocked(); boolean devicesUpdated = mInputDeviceDelegate.updateInputDeviceVibrators( mVibrationSettings.shouldVibrateInputDevices()); boolean lowPowerModeUpdated = updateLowPowerModeLocked(); if (devicesUpdated || lowPowerModeUpdated) { Loading @@ -1205,41 +1194,6 @@ public class VibratorService extends IVibratorService.Stub } } private boolean updateInputDeviceVibratorsLocked() { boolean changed = false; boolean vibrateInputDevices = mVibrationSettings.shouldVibrateInputDevices(); if (vibrateInputDevices != mVibrateInputDevicesSetting) { changed = true; mVibrateInputDevicesSetting = vibrateInputDevices; } if (mVibrateInputDevicesSetting) { if (!mInputDeviceListenerRegistered) { mInputDeviceListenerRegistered = true; mIm.registerInputDeviceListener(this, mH); } } else { if (mInputDeviceListenerRegistered) { mInputDeviceListenerRegistered = false; mIm.unregisterInputDeviceListener(this); } } mInputDeviceVibrators.clear(); if (mVibrateInputDevicesSetting) { int[] ids = mIm.getInputDeviceIds(); for (int i = 0; i < ids.length; i++) { InputDevice device = mIm.getInputDevice(ids[i]); Vibrator vibrator = device.getVibrator(); if (vibrator.hasVibrator()) { mInputDeviceVibrators.add(vibrator); } } return true; } return changed; } private boolean updateLowPowerModeLocked() { boolean lowPowerMode = mPowerManagerInternal .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled; Loading Loading @@ -1268,58 +1222,37 @@ public class VibratorService extends IVibratorService.Stub } } @Override public void onInputDeviceAdded(int deviceId) { updateVibrators(); } @Override public void onInputDeviceChanged(int deviceId) { updateVibrators(); } @Override public void onInputDeviceRemoved(int deviceId) { updateVibrators(); } private boolean doVibratorExists() { // For now, we choose to ignore the presence of input devices that have vibrators // when reporting whether the device has a vibrator. Applications often use this // information to decide whether to enable certain features so they expect the // result of hasVibrator() to be constant. For now, just report whether // the device has a built-in vibrator. //synchronized (mInputDeviceVibrators) { // return !mInputDeviceVibrators.isEmpty() || vibratorExists(); //} return mNativeWrapper.vibratorExists(); } private void doVibratorOn(Vibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn"); try { synchronized (mInputDeviceVibrators) { final VibrationEffect.OneShot oneShot = vib.effect.resolve( mDefaultVibrationAmplitude); final VibrationEffect.OneShot oneShot = vib.effect.resolve(mDefaultVibrationAmplitude); if (DEBUG) { Slog.d(TAG, "Turning vibrator on for " + oneShot.getDuration() + " ms" + " with amplitude " + oneShot.getAmplitude() + "."); } noteVibratorOnLocked(vib.uid, oneShot.getDuration()); final int vibratorCount = mInputDeviceVibrators.size(); if (vibratorCount != 0) { for (int i = 0; i < vibratorCount; i++) { mInputDeviceVibrators.get(i).vibrate(vib.uid, vib.opPkg, oneShot, vib.reason, vib.attrs); } boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( vib.uid, vib.opPkg, oneShot, vib.reason, vib.attrs); if (inputDevicesAvailable) { // The set current vibration is no longer being played by this service, so drop it. mCurrentVibration = null; endVibrationLocked(vib, VibrationInfo.Status.FORWARDED_TO_INPUT_DEVICES); } else { noteVibratorOnLocked(vib.uid, oneShot.getDuration()); // Note: ordering is important here! Many haptic drivers will reset their // amplitude when enabled, so we always have to enable first, then set the // amplitude. mNativeWrapper.vibratorOn(oneShot.getDuration(), vib.id); doVibratorSetAmplitude(oneShot.getAmplitude()); } } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } Loading @@ -1334,19 +1267,34 @@ public class VibratorService extends IVibratorService.Stub private void doVibratorOff() { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOff"); try { synchronized (mInputDeviceVibrators) { if (DEBUG) { Slog.d(TAG, "Turning vibrator off."); } noteVibratorOffLocked(); final int vibratorCount = mInputDeviceVibrators.size(); if (vibratorCount != 0) { for (int i = 0; i < vibratorCount; i++) { mInputDeviceVibrators.get(i).cancel(); } } else { boolean inputDevicesAvailable = mInputDeviceDelegate.cancelVibrateIfAvailable(); if (!inputDevicesAvailable) { mNativeWrapper.vibratorOff(); } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } } @GuardedBy("mLock") private void doVibratorWaveformEffectLocked(Vibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorWaveformEffectLocked"); try { boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( vib.uid, vib.opPkg, vib.effect, vib.reason, vib.attrs); if (inputDevicesAvailable) { // The set current vibration is no longer being played by this service, so drop it. mCurrentVibration = null; endVibrationLocked(vib, VibrationInfo.Status.FORWARDED_TO_INPUT_DEVICES); } else { // mThread better be null here. doCancelVibrate should always be // called before startNextVibrationLocked or startVibrationLocked. mThread = new VibrateWaveformThread(vib); mThread.start(); } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); Loading @@ -1358,12 +1306,9 @@ public class VibratorService extends IVibratorService.Stub Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorPrebakedEffectLocked"); try { final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect; final boolean usingInputDeviceVibrators; synchronized (mInputDeviceVibrators) { usingInputDeviceVibrators = !mInputDeviceVibrators.isEmpty(); } // Input devices don't support prebaked effect, so skip trying it with them. if (!usingInputDeviceVibrators) { // Input devices don't support prebaked effect, so skip trying it with them and allow // fallback to be attempted. if (!mInputDeviceDelegate.isAvailable()) { long duration = mNativeWrapper.vibratorPerformEffect( prebaked.getId(), prebaked.getEffectStrength(), vib.id); if (duration > 0) { Loading Loading @@ -1401,15 +1346,17 @@ public class VibratorService extends IVibratorService.Stub try { final VibrationEffect.Composed composed = (VibrationEffect.Composed) vib.effect; final boolean usingInputDeviceVibrators; synchronized (mInputDeviceVibrators) { usingInputDeviceVibrators = !mInputDeviceVibrators.isEmpty(); } // Input devices don't support composed effect, so skip trying it with them. if (usingInputDeviceVibrators || !hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { endVibrationLocked(vib, VibrationInfo.Status.IGNORED_UNSUPPORTED); boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( vib.uid, vib.opPkg, composed, vib.reason, vib.attrs); if (inputDevicesAvailable) { // The set current vibration is no longer being played by this service, so drop it. mCurrentVibration = null; endVibrationLocked(vib, VibrationInfo.Status.FORWARDED_TO_INPUT_DEVICES); return; } else if (!hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { // The set current vibration is not actually playing, so drop it. mCurrentVibration = null; endVibrationLocked(vib, VibrationInfo.Status.IGNORED_UNSUPPORTED); return; } Loading
services/core/java/com/android/server/vibrator/InputDeviceDelegate.java 0 → 100644 +171 −0 Original line number Diff line number Diff line /* * 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 com.android.server.vibrator; import android.content.Context; import android.hardware.input.InputManager; import android.os.Handler; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.util.SparseArray; import android.view.InputDevice; import com.android.internal.annotations.GuardedBy; /** Delegates vibrations to all connected {@link InputDevice} with available {@link Vibrator}. */ // TODO(b/159207608): Make this package-private once vibrator services are moved to this package public final class InputDeviceDelegate implements InputManager.InputDeviceListener { private static final String TAG = "InputDeviceDelegate"; private final Object mLock = new Object(); private final Handler mHandler; private final InputManager mInputManager; @GuardedBy("mLock") private final SparseArray<Vibrator> mInputDeviceVibrators = new SparseArray<>(); /** * Flag updated via {@link #updateInputDeviceVibrators(boolean)}, holding the value of {@link * android.provider.Settings.System#VIBRATE_INPUT_DEVICES}. */ @GuardedBy("mLock") private boolean mShouldVibrateInputDevices; public InputDeviceDelegate(Context context, Handler handler) { mHandler = handler; mInputManager = context.getSystemService(InputManager.class); } @Override public void onInputDeviceAdded(int deviceId) { updateInputDevice(deviceId); } @Override public void onInputDeviceChanged(int deviceId) { updateInputDevice(deviceId); } @Override public void onInputDeviceRemoved(int deviceId) { synchronized (mLock) { mInputDeviceVibrators.remove(deviceId); } } /** * Return {@code true} is there are input devices with vibrators available and vibrations should * be delegated to them. */ public boolean isAvailable() { synchronized (mLock) { // mInputDeviceVibrators is cleared when settings are disabled, so this check is enough. return mInputDeviceVibrators.size() > 0; } } /** * Vibrate all {@link InputDevice} with {@link Vibrator} available using given effect. * * @return {@link #isAvailable()} */ public boolean vibrateIfAvailable(int uid, String opPkg, VibrationEffect effect, String reason, VibrationAttributes attrs) { synchronized (mLock) { for (int i = 0; i < mInputDeviceVibrators.size(); i++) { mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, effect, reason, attrs); } return mInputDeviceVibrators.size() > 0; } } /** * Cancel vibration on all {@link InputDevice} with {@link Vibrator} available. * * @return {@link #isAvailable()} */ public boolean cancelVibrateIfAvailable() { synchronized (mLock) { for (int i = 0; i < mInputDeviceVibrators.size(); i++) { mInputDeviceVibrators.valueAt(i).cancel(); } return mInputDeviceVibrators.size() > 0; } } /** * Updates the list of {@link InputDevice} vibrators based on the {@link * VibrationSettings#shouldVibrateInputDevices()} setting current value and the * devices currently available in {@link InputManager#getInputDeviceIds()}. * * @return true if there was any change in input devices available or related settings. */ public boolean updateInputDeviceVibrators(boolean vibrateInputDevices) { synchronized (mLock) { if (vibrateInputDevices == mShouldVibrateInputDevices) { // No need to update if settings haven't changed. return false; } mShouldVibrateInputDevices = vibrateInputDevices; mInputDeviceVibrators.clear(); if (vibrateInputDevices) { // Register the listener first so any device added/updated/removed after the call to // getInputDeviceIds() will trigger the callbacks (which will wait on the lock for // this loop to finish). mInputManager.registerInputDeviceListener(this, mHandler); for (int deviceId : mInputManager.getInputDeviceIds()) { InputDevice device = mInputManager.getInputDevice(deviceId); if (device == null) { continue; } Vibrator vibrator = device.getVibrator(); if (vibrator.hasVibrator()) { mInputDeviceVibrators.put(device.getId(), vibrator); } } } else { mInputManager.unregisterInputDeviceListener(this); } } return true; } private void updateInputDevice(int deviceId) { synchronized (mLock) { if (!mShouldVibrateInputDevices) { // No need to keep this device vibrator if setting is off. return; } InputDevice device = mInputManager.getInputDevice(deviceId); if (device == null) { mInputDeviceVibrators.remove(deviceId); return; } Vibrator vibrator = device.getVibrator(); if (vibrator.hasVibrator()) { mInputDeviceVibrators.put(deviceId, vibrator); } else { mInputDeviceVibrators.remove(deviceId); } } } }
services/tests/servicestests/src/com/android/server/VibratorServiceTest.java +96 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes