• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.vibrator;
18 
19 import android.annotation.Nullable;
20 import android.hardware.vibrator.IVibrator;
21 import android.os.Binder;
22 import android.os.IVibratorStateListener;
23 import android.os.RemoteCallbackList;
24 import android.os.RemoteException;
25 import android.os.VibratorInfo;
26 import android.os.vibrator.PrebakedSegment;
27 import android.os.vibrator.PrimitiveSegment;
28 import android.os.vibrator.RampSegment;
29 import android.util.Slog;
30 
31 import com.android.internal.annotations.GuardedBy;
32 import com.android.internal.annotations.VisibleForTesting;
33 
34 import libcore.util.NativeAllocationRegistry;
35 
36 /** Controls a single vibrator. */
37 final class VibratorController {
38     private static final String TAG = "VibratorController";
39     // TODO(b/167947076): load suggested range from config
40     private static final int SUGGESTED_FREQUENCY_SAFE_RANGE = 200;
41 
42     private final Object mLock = new Object();
43     private final NativeWrapper mNativeWrapper;
44     private final VibratorInfo.Builder mVibratorInfoBuilder;
45 
46     @GuardedBy("mLock")
47     private VibratorInfo mVibratorInfo;
48     @GuardedBy("mLock")
49     private boolean mVibratorInfoLoaded;
50     @GuardedBy("mLock")
51     private final RemoteCallbackList<IVibratorStateListener> mVibratorStateListeners =
52             new RemoteCallbackList<>();
53     @GuardedBy("mLock")
54     private boolean mIsVibrating;
55     @GuardedBy("mLock")
56     private boolean mIsUnderExternalControl;
57     @GuardedBy("mLock")
58     private float mCurrentAmplitude;
59 
60     /** Listener for vibration completion callbacks from native. */
61     public interface OnVibrationCompleteListener {
62 
63         /** Callback triggered when vibration is complete. */
onComplete(int vibratorId, long vibrationId)64         void onComplete(int vibratorId, long vibrationId);
65     }
66 
VibratorController(int vibratorId, OnVibrationCompleteListener listener)67     VibratorController(int vibratorId, OnVibrationCompleteListener listener) {
68         this(vibratorId, listener, new NativeWrapper());
69     }
70 
71     @VisibleForTesting
VibratorController(int vibratorId, OnVibrationCompleteListener listener, NativeWrapper nativeWrapper)72     VibratorController(int vibratorId, OnVibrationCompleteListener listener,
73             NativeWrapper nativeWrapper) {
74         mNativeWrapper = nativeWrapper;
75         mNativeWrapper.init(vibratorId, listener);
76         mVibratorInfoBuilder = new VibratorInfo.Builder(vibratorId);
77         mVibratorInfoLoaded = mNativeWrapper.getInfo(SUGGESTED_FREQUENCY_SAFE_RANGE,
78                 mVibratorInfoBuilder);
79         mVibratorInfo = mVibratorInfoBuilder.build();
80     }
81 
82     /** Register state listener for this vibrator. */
registerVibratorStateListener(IVibratorStateListener listener)83     public boolean registerVibratorStateListener(IVibratorStateListener listener) {
84         synchronized (mLock) {
85             final long token = Binder.clearCallingIdentity();
86             try {
87                 if (!mVibratorStateListeners.register(listener)) {
88                     return false;
89                 }
90                 // Notify its callback after new client registered.
91                 notifyStateListenerLocked(listener);
92                 return true;
93             } finally {
94                 Binder.restoreCallingIdentity(token);
95             }
96         }
97     }
98 
99     /** Remove registered state listener for this vibrator. */
unregisterVibratorStateListener(IVibratorStateListener listener)100     public boolean unregisterVibratorStateListener(IVibratorStateListener listener) {
101         synchronized (mLock) {
102             final long token = Binder.clearCallingIdentity();
103             try {
104                 return mVibratorStateListeners.unregister(listener);
105             } finally {
106                 Binder.restoreCallingIdentity(token);
107             }
108         }
109     }
110 
111     /** Return the {@link VibratorInfo} representing the vibrator controlled by this instance. */
getVibratorInfo()112     public VibratorInfo getVibratorInfo() {
113         synchronized (mLock) {
114             if (!mVibratorInfoLoaded) {
115                 // Try to load the vibrator metadata that has failed in the last attempt.
116                 mVibratorInfoLoaded = mNativeWrapper.getInfo(SUGGESTED_FREQUENCY_SAFE_RANGE,
117                         mVibratorInfoBuilder);
118                 mVibratorInfo = mVibratorInfoBuilder.build();
119             }
120             return mVibratorInfo;
121         }
122     }
123 
124     /**
125      * Return {@code true} is this vibrator is currently vibrating, false otherwise.
126      *
127      * <p>This state is controlled by calls to {@link #on} and {@link #off} methods, and is
128      * automatically notified to any registered {@link IVibratorStateListener} on change.
129      */
isVibrating()130     public boolean isVibrating() {
131         synchronized (mLock) {
132             return mIsVibrating;
133         }
134     }
135 
136     /**
137      * Returns the current amplitude the device is vibrating.
138      *
139      * <p>This value is set to 1 by the method {@link #on(long, long)}, and can be updated via
140      * {@link #setAmplitude(float)} if called while the device is vibrating.
141      *
142      * <p>If the device is vibrating via any other {@link #on} method then the current amplitude is
143      * unknown and this will return -1.
144      *
145      * <p>If {@link #isVibrating()} is false then this will be zero.
146      */
getCurrentAmplitude()147     public float getCurrentAmplitude() {
148         synchronized (mLock) {
149             return mCurrentAmplitude;
150         }
151     }
152 
153     /** Return {@code true} if this vibrator is under external control, false otherwise. */
isUnderExternalControl()154     public boolean isUnderExternalControl() {
155         synchronized (mLock) {
156             return mIsUnderExternalControl;
157         }
158     }
159 
160     /**
161      * Check against this vibrator capabilities.
162      *
163      * @param capability one of IVibrator.CAP_*
164      * @return true if this vibrator has this capability, false otherwise
165      */
hasCapability(long capability)166     public boolean hasCapability(long capability) {
167         return mVibratorInfo.hasCapability(capability);
168     }
169 
170     /** Return {@code true} if the underlying vibrator is currently available, false otherwise. */
isAvailable()171     public boolean isAvailable() {
172         return mNativeWrapper.isAvailable();
173     }
174 
175     /**
176      * Set the vibrator control to be external or not, based on given flag.
177      *
178      * <p>This will affect the state of {@link #isUnderExternalControl()}.
179      */
setExternalControl(boolean externalControl)180     public void setExternalControl(boolean externalControl) {
181         if (!mVibratorInfo.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
182             return;
183         }
184         synchronized (mLock) {
185             mIsUnderExternalControl = externalControl;
186             mNativeWrapper.setExternalControl(externalControl);
187         }
188     }
189 
190     /**
191      * Update the predefined vibration effect saved with given id. This will remove the saved effect
192      * if given {@code effect} is {@code null}.
193      */
updateAlwaysOn(int id, @Nullable PrebakedSegment prebaked)194     public void updateAlwaysOn(int id, @Nullable PrebakedSegment prebaked) {
195         if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
196             return;
197         }
198         synchronized (mLock) {
199             if (prebaked == null) {
200                 mNativeWrapper.alwaysOnDisable(id);
201             } else {
202                 mNativeWrapper.alwaysOnEnable(id, prebaked.getEffectId(),
203                         prebaked.getEffectStrength());
204             }
205         }
206     }
207 
208     /** Set the vibration amplitude. This will NOT affect the state of {@link #isVibrating()}. */
setAmplitude(float amplitude)209     public void setAmplitude(float amplitude) {
210         synchronized (mLock) {
211             if (mVibratorInfo.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
212                 mNativeWrapper.setAmplitude(amplitude);
213             }
214             if (mIsVibrating) {
215                 mCurrentAmplitude = amplitude;
216             }
217         }
218     }
219 
220     /**
221      * Turn on the vibrator for {@code milliseconds} time, using {@code vibrationId} or completion
222      * callback to {@link OnVibrationCompleteListener}.
223      *
224      * <p>This will affect the state of {@link #isVibrating()}.
225      *
226      * @return The positive duration of the vibration started, if successful, zero if the vibrator
227      * do not support the input or a negative number if the operation failed.
228      */
on(long milliseconds, long vibrationId)229     public long on(long milliseconds, long vibrationId) {
230         synchronized (mLock) {
231             long duration = mNativeWrapper.on(milliseconds, vibrationId);
232             if (duration > 0) {
233                 mCurrentAmplitude = -1;
234                 notifyVibratorOnLocked();
235             }
236             return duration;
237         }
238     }
239 
240     /**
241      * Plays predefined vibration effect, using {@code vibrationId} or completion callback to
242      * {@link OnVibrationCompleteListener}.
243      *
244      * <p>This will affect the state of {@link #isVibrating()}.
245      *
246      * @return The positive duration of the vibration started, if successful, zero if the vibrator
247      * do not support the input or a negative number if the operation failed.
248      */
on(PrebakedSegment prebaked, long vibrationId)249     public long on(PrebakedSegment prebaked, long vibrationId) {
250         synchronized (mLock) {
251             long duration = mNativeWrapper.perform(prebaked.getEffectId(),
252                     prebaked.getEffectStrength(), vibrationId);
253             if (duration > 0) {
254                 mCurrentAmplitude = -1;
255                 notifyVibratorOnLocked();
256             }
257             return duration;
258         }
259     }
260 
261     /**
262      * Plays a composition of vibration primitives, using {@code vibrationId} or completion callback
263      * to {@link OnVibrationCompleteListener}.
264      *
265      * <p>This will affect the state of {@link #isVibrating()}.
266      *
267      * @return The positive duration of the vibration started, if successful, zero if the vibrator
268      * do not support the input or a negative number if the operation failed.
269      */
on(PrimitiveSegment[] primitives, long vibrationId)270     public long on(PrimitiveSegment[] primitives, long vibrationId) {
271         if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
272             return 0;
273         }
274         synchronized (mLock) {
275             long duration = mNativeWrapper.compose(primitives, vibrationId);
276             if (duration > 0) {
277                 mCurrentAmplitude = -1;
278                 notifyVibratorOnLocked();
279             }
280             return duration;
281         }
282     }
283 
284     /**
285      * Plays a composition of pwle primitives, using {@code vibrationId} or completion callback
286      * to {@link OnVibrationCompleteListener}.
287      *
288      * <p>This will affect the state of {@link #isVibrating()}.
289      *
290      * @return The duration of the effect playing, or 0 if unsupported.
291      */
on(RampSegment[] primitives, long vibrationId)292     public long on(RampSegment[] primitives, long vibrationId) {
293         if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
294             return 0;
295         }
296         synchronized (mLock) {
297             int braking = mVibratorInfo.getDefaultBraking();
298             long duration = mNativeWrapper.composePwle(primitives, braking, vibrationId);
299             if (duration > 0) {
300                 mCurrentAmplitude = -1;
301                 notifyVibratorOnLocked();
302             }
303             return duration;
304         }
305     }
306 
307     /** Turns off the vibrator.This will affect the state of {@link #isVibrating()}. */
off()308     public void off() {
309         synchronized (mLock) {
310             mNativeWrapper.off();
311             mCurrentAmplitude = 0;
312             notifyVibratorOffLocked();
313         }
314     }
315 
316     @Override
toString()317     public String toString() {
318         synchronized (mLock) {
319             return "VibratorController{"
320                     + "mVibratorInfo=" + mVibratorInfo
321                     + ", mIsVibrating=" + mIsVibrating
322                     + ", mCurrentAmplitude=" + mCurrentAmplitude
323                     + ", mIsUnderExternalControl=" + mIsUnderExternalControl
324                     + ", mVibratorStateListeners count="
325                     + mVibratorStateListeners.getRegisteredCallbackCount()
326                     + '}';
327         }
328     }
329 
330     @GuardedBy("mLock")
notifyVibratorOnLocked()331     private void notifyVibratorOnLocked() {
332         if (!mIsVibrating) {
333             mIsVibrating = true;
334             notifyStateListenersLocked();
335         }
336     }
337 
338     @GuardedBy("mLock")
notifyVibratorOffLocked()339     private void notifyVibratorOffLocked() {
340         if (mIsVibrating) {
341             mIsVibrating = false;
342             notifyStateListenersLocked();
343         }
344     }
345 
346     @GuardedBy("mLock")
notifyStateListenersLocked()347     private void notifyStateListenersLocked() {
348         final int length = mVibratorStateListeners.beginBroadcast();
349         try {
350             for (int i = 0; i < length; i++) {
351                 notifyStateListenerLocked(mVibratorStateListeners.getBroadcastItem(i));
352             }
353         } finally {
354             mVibratorStateListeners.finishBroadcast();
355         }
356     }
357 
358     @GuardedBy("mLock")
notifyStateListenerLocked(IVibratorStateListener listener)359     private void notifyStateListenerLocked(IVibratorStateListener listener) {
360         try {
361             listener.onVibrating(mIsVibrating);
362         } catch (RemoteException | RuntimeException e) {
363             Slog.e(TAG, "Vibrator state listener failed to call", e);
364         }
365     }
366 
367     /** Wrapper around the static-native methods of {@link VibratorController} for tests. */
368     @VisibleForTesting
369     public static class NativeWrapper {
370         /**
371          * Initializes the native part of this controller, creating a global reference to given
372          * {@link OnVibrationCompleteListener} and returns a newly allocated native pointer. This
373          * wrapper is responsible for deleting this pointer by calling the method pointed
374          * by {@link #getNativeFinalizer()}.
375          *
376          * <p><b>Note:</b> Make sure the given implementation of {@link OnVibrationCompleteListener}
377          * do not hold any strong reference to the instance responsible for deleting the returned
378          * pointer, to avoid creating a cyclic GC root reference.
379          */
nativeInit(int vibratorId, OnVibrationCompleteListener listener)380         private static native long nativeInit(int vibratorId, OnVibrationCompleteListener listener);
381 
382         /**
383          * Returns pointer to native function responsible for cleaning up the native pointer
384          * allocated and returned by {@link #nativeInit(int, OnVibrationCompleteListener)}.
385          */
getNativeFinalizer()386         private static native long getNativeFinalizer();
isAvailable(long nativePtr)387         private static native boolean isAvailable(long nativePtr);
on(long nativePtr, long milliseconds, long vibrationId)388         private static native long on(long nativePtr, long milliseconds, long vibrationId);
off(long nativePtr)389         private static native void off(long nativePtr);
setAmplitude(long nativePtr, float amplitude)390         private static native void setAmplitude(long nativePtr, float amplitude);
performEffect(long nativePtr, long effect, long strength, long vibrationId)391         private static native long performEffect(long nativePtr, long effect, long strength,
392                 long vibrationId);
393 
performComposedEffect(long nativePtr, PrimitiveSegment[] effect, long vibrationId)394         private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect,
395                 long vibrationId);
396 
performPwleEffect(long nativePtr, RampSegment[] effect, int braking, long vibrationId)397         private static native long performPwleEffect(long nativePtr, RampSegment[] effect,
398                 int braking, long vibrationId);
399 
setExternalControl(long nativePtr, boolean enabled)400         private static native void setExternalControl(long nativePtr, boolean enabled);
401 
alwaysOnEnable(long nativePtr, long id, long effect, long strength)402         private static native void alwaysOnEnable(long nativePtr, long id, long effect,
403                 long strength);
404 
alwaysOnDisable(long nativePtr, long id)405         private static native void alwaysOnDisable(long nativePtr, long id);
406 
getInfo(long nativePtr, float suggestedFrequencyRange, VibratorInfo.Builder infoBuilder)407         private static native boolean getInfo(long nativePtr, float suggestedFrequencyRange,
408                 VibratorInfo.Builder infoBuilder);
409 
410         private long mNativePtr = 0;
411 
412         /** Initializes native controller and allocation registry to destroy native instances. */
init(int vibratorId, OnVibrationCompleteListener listener)413         public void init(int vibratorId, OnVibrationCompleteListener listener) {
414             mNativePtr = nativeInit(vibratorId, listener);
415             long finalizerPtr = getNativeFinalizer();
416 
417             if (finalizerPtr != 0) {
418                 NativeAllocationRegistry registry =
419                         NativeAllocationRegistry.createMalloced(
420                                 VibratorController.class.getClassLoader(), finalizerPtr);
421                 registry.registerNativeAllocation(this, mNativePtr);
422             }
423         }
424 
425         /** Check if the vibrator is currently available. */
isAvailable()426         public boolean isAvailable() {
427             return isAvailable(mNativePtr);
428         }
429 
430         /** Turns vibrator on for given time. */
on(long milliseconds, long vibrationId)431         public long on(long milliseconds, long vibrationId) {
432             return on(mNativePtr, milliseconds, vibrationId);
433         }
434 
435         /** Turns vibrator off. */
off()436         public void off() {
437             off(mNativePtr);
438         }
439 
440         /** Sets the amplitude for the vibrator to run. */
setAmplitude(float amplitude)441         public void setAmplitude(float amplitude) {
442             setAmplitude(mNativePtr, amplitude);
443         }
444 
445         /** Turns vibrator on to perform one of the supported effects. */
perform(long effect, long strength, long vibrationId)446         public long perform(long effect, long strength, long vibrationId) {
447             return performEffect(mNativePtr, effect, strength, vibrationId);
448         }
449 
450         /** Turns vibrator on to perform effect composed of give primitives effect. */
compose(PrimitiveSegment[] primitives, long vibrationId)451         public long compose(PrimitiveSegment[] primitives, long vibrationId) {
452             return performComposedEffect(mNativePtr, primitives, vibrationId);
453         }
454 
455         /** Turns vibrator on to perform PWLE effect composed of given primitives. */
composePwle(RampSegment[] primitives, int braking, long vibrationId)456         public long composePwle(RampSegment[] primitives, int braking, long vibrationId) {
457             return performPwleEffect(mNativePtr, primitives, braking, vibrationId);
458         }
459 
460         /** Enabled the device vibrator to be controlled by another service. */
setExternalControl(boolean enabled)461         public void setExternalControl(boolean enabled) {
462             setExternalControl(mNativePtr, enabled);
463         }
464 
465         /** Enable always-on vibration with given id and effect. */
alwaysOnEnable(long id, long effect, long strength)466         public void alwaysOnEnable(long id, long effect, long strength) {
467             alwaysOnEnable(mNativePtr, id, effect, strength);
468         }
469 
470         /** Disable always-on vibration for given id. */
alwaysOnDisable(long id)471         public void alwaysOnDisable(long id) {
472             alwaysOnDisable(mNativePtr, id);
473         }
474 
475         /**
476          * Loads device vibrator metadata and returns true if all metadata was loaded successfully.
477          */
getInfo(float suggestedFrequencyRange, VibratorInfo.Builder infoBuilder)478         public boolean getInfo(float suggestedFrequencyRange, VibratorInfo.Builder infoBuilder) {
479             return getInfo(mNativePtr, suggestedFrequencyRange, infoBuilder);
480         }
481     }
482 }
483