I need to alert the user of certain events by means of:
The vibration should remain on indefinitely until the user ACKs the notification.
The problem is vibration stops when the device goes to sleep. I've read the following questions:
Allow phone to vibrate when screen turns off
Continue vibration even after the screen goes to sleep mode in Android
There was an answer to one of the above mentioned questions saying that vibrating without patterns did the trick. So I've tried calling the version of Vibrator.vibrate
that accepts milliseconds instead of a pattern with a large number but the vibration stops anyway.
Other answers suggest to register a receiver on the ACTION_SCREEN_OFF
action. This would allow me to resume vibration if the device goes to sleep after the alarm has started, but won't work if the device was already slept.
However, I could get the thing working if I were able to turn the screen on first, then register the receiver to deal with any screen off event that could happen from there on. So I've tried acquiring a full wake lock when the triggering event is received, before starting sound or vibration, but it does not work despite I'm using the flags FULL_WAKE_LOCK and ACQUIRE_CAUSES_WAKEUP. The wakeup part works, but soon after that the device goes to sleep again. I would like to think the FULL_WAKE_LOCK flag does not work because it has been deprecated in API 17, but my device is a Samsung running 4.1.2 which is API 16!
The recommended approach now seems to be using WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON but this should be called from an activity, and I don't have any screen up unless the user clicks in the notification, and if this happens the sound and vibration should already have been stopped.
So it looks like a dead-end.
What else could I try?
UPDATE:
I had no luck keeping the screen always on with wake locks, but on the other hand they allow me to turn the screen on if only for a few seconds. I actually don't need to keep the screen on, so I'm registering the receiver on the Intent.ACTION_SCREEN_OFF
action, and when the screen goes off, the receiver resumes vibration again. This worked well in the Samsung, but I've now switched to a Huawei to continue testing and the receiver does not work.
UPDATE:
Here's the stack trace of the exception in the Huawei device:
java.util.NoSuchElementException: Death link does not exist
at android.os.BinderProxy.unlinkToDeath(Native Method)
at com.android.server.VibratorService.unlinkVibration(VibratorService.java:294)
at com.android.server.VibratorService.removeVibrationLocked(VibratorService.java:284)
at com.android.server.VibratorService.cancelVibrate(VibratorService.java:213)
at android.os.IVibratorService$Stub.onTransact(IVibratorService.java:83)
at android.os.Binder.execTransact(Binder.java:338)
at dalvik.system.NativeStart.run(Native Method)
After some testing I finally managed to get it working.
The Vibrator class hass two methods:
vibrate (long[] pattern, int repeat)
vibrate (long milliseconds)
The first one is the only way of vibrating indefinitely using the API (passing 0 as the second argument). But this has been proven to break in some devices (Huawei), as I posted in the question. I'm not talking about the vibration being stopped by the OS when the device goes to sleep, this had been dealt with using a receiver plus a wake lock as described in the question. I'm talking about exceptions, caused by a bugged implementation (the Vibrator class is abstract).
The second variant of this method does not accept a pattern, and does not allow indefinite vibration, but we can cheat this by passing a very large number of milliseconds as parameter. This works well in some devices (Huawei) as the answer I cited in the question correctly pointed, but does not work in others (Samsung), where the implementation has a default max value that will be used instead if the value passed as parameter exceeds it. This max value is actually less than a minute, and this means we can't rely on this approach.
So I went all out on this and created a Service, where I manually vibrate indefinitely like this:
while(vibrationActive){
Vibrator.vibrate(1000);
Thread.sleep(1000);
}
The receiver trick to detect when the screen goes off is no longer needed. Of course the OS keeps shutting down the vibrator when this happens, but the next iteration of the loop will resume vibration again. With this approach it is possible to create a sort of pattern as well, if the sleep time is greater than the vibration time, but again this pattern will be interrupted at any point if the screen goes off.
A dedicated service just to turn the vibrator on and off reliably. Can you believe it? About 150 lines of code (without the unit tests) for something that should have been possible with a few lines.