Why vibration doesn't work on Android P (API 28)? - android

I'm trying to implement haptic feedback when changing a value of a seekbar.
It works correctly on Android pre-P. On Android P it doesn't work at all.
Code:
private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator?
private val effect by lazy { VibrationEffect.createOneShot(VIBRATION_DURATION, 50)}
...
fun vibrate() {
if (vibrator == null || !vibrator.hasVibrator()) {
return
}
vibrator.cancel()
vibrator.vibrate(effect)

It turns out the user has to enable Touch vibration in Settings -> Accessibility -> Vibration -> Touch vibration:
Without it, short vibration (less than 5 seconds) won't work. For me, this is not quite intuitive, so I decided to post it here

exactly what Mike said.
but for android Pie - Setting->Sounds and vibration -> System sounds and vibration -> Touch vibration

Related

audioManager not working on Android 11 and 12

I am making an internal calls application, when I call a number, the native android dialer opens and then instead of following the call in the native dialer, I follow the call in the app. The problem is that from the app it has no effect when I want to activate or deactivate the native dialer speaker. The native dialer is in the background, and the app is in the foreground, but for some reason when I try to activate the speaker from the app, to follow the call from here, it doesn't work. This only happens in versions of Android 11 and 12, however in versions of Android 8,9 and 10 it works perfectly, I can manipulate the speaker from the app, without going to the native dialer.
This is my code to activate and desactivate the speaker:
private fun enableSpeaker() {
audioManager?.let {
if (!it.isSpeakerphoneOn) {
audioManager!!.mode = AudioManager.MODE_IN_COMMUNICATION
audioManager!!.isSpeakerphoneOn = true
}
}
}
private fun disableSpeaker() {
audioManager?.let {
if (it.isSpeakerphoneOn) {
//audioManager.setMicrophoneMute(false)
audioManager!!.mode = AudioManager.MODE_NORMAL
audioManager!!.isSpeakerphoneOn = false
}
}
}
For Android 12 and above, use audioManager.setCommunicationDevice instead of audioManager.isSpeakerPhoneOn = true
More info here : https://developer.android.com/reference/kotlin/android/media/AudioManager#setCommunicationDevice(android.media.AudioDeviceInfo)

MediaSessionCompat behaviour changed in Android 12 and setPlaybackToRemote stopped working properly

Prior to Android 12, I used code below to detect physical volume buttons pressed to show my custom UI, but it stopped working on Android 12 devices and onAdjustVolume is never called when I press volume buttons:
mediaSessionCompat = MediaSessionCompat(context, "My App")
mediaSessionCompat?.isActive = true
mediaSessionCompat?.setPlaybackState(
PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1f)
.build())
mediaSessionCompat?.setPlaybackToRemote(object :
VolumeProviderCompat(VOLUME_CONTROL_ABSOLUTE, 7, 4) {
override fun onAdjustVolume(direction: Int) {
Log.v("My App", "Volume adjusted")
}
})
Please, check this link:
https://issuetracker.google.com/issues/201546605?pli=1
It seems like Google changed it because of "privacy changes", according to them, but it is supposed it will be reverted in the next release (nobody knows when).

Text to speech over Bluetooth gets cut off at the starting. How to fix it?

I've been working on a navigation feature for a maps app which has voice instructions. The problem is that when announcing the instructions, the first 500 milliseconds of the instruction gets cut off. For eg, if the instruction is "In 200m turn right", in the bluetooth earphone it ends up sounding like "200m turn right". Or if the instruction is "Continue for 2 kilometers", then it sounds like "tinue for 2 kilometers".
This is the code I'm using for TTS -
//Initialisation happens only once
var textToSpeechEngine = TextToSpeech(this) { status ->
if (status == TextToSpeech.SUCCESS) {
textToSpeechEngine?.language = Locale.ENGLISH
textToSpeechEngine?.setSpeechRate(0.8f)
}
}
...
//When text to speak is ready, invoking the speak method
textToSpeechEngine?.speak(textToSpeak, TextToSpeech.QUEUE_FLUSH, null, "tts1")
Additionally, I'm also using AudioFocusRequest to request and abandon focus so that any music playing in the background lowers its volume while the instruction is being spoken. This is the code I'm using for that -
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
var focusRequest: AudioFocusRequest? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build())
.setAcceptsDelayedFocusGain(false)
.build()
audioManager.requestAudioFocus(focusRequest)
} else {
audioManager.requestAudioFocus(null, AudioManager.STREAM_NOTIFICATION, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
}
textToSpeechEngine?.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
fun abandonFocus() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
focusRequest?.let { request -> audioManager.abandonAudioFocusRequest(request) }
} else {
audioManager.abandonAudioFocus(null)
}
}
override fun onStart(utteranceId: String?) {}
override fun onDone(utteranceId: String?) {
abandonFocus()
}
override fun onError(utteranceId: String?) {
abandonFocus()
}
})
Point to note here is that this issue is not happening in all the bluetooth devices I've tested with. The issue happened with 2 Chinese brands bluetooth earphones but is not happening with a bluetooth Sony over-the-head headset. Also, the issue does not happen if music is playing in the background while navigation is going on.
As I understand it, it seems that the cheaper bluetooth earphones seem to keep the "connection alive" only when audio is actively coming through else it stops the connection temporarily in order to save battery I guess? However, when music is playing in the background, the connection is kept alive constantly so the instruction speech does not get cut off.
What can I do to fix this or work around it?
note that requestAudioFocus may take a OnAudioFocusChangeListener as first param, you are passing null in there (also Builder have this param). switching focus may take some (short) time, so I would recomend to fire your TTS when you get this callback fired with AUDIOFOCUS_GAIN
Assuming that you're correct that it is purely hardware at fault (which is what it seems like):
You can use the playSilence() or playSilentUtterance() methods of the TextToSpeech class to play silence for 500ms prior to your main speak() command... which should fool the speakers/headphones.
It may help to also use QUEUE_ADD instead of QUEUE_FLUSH for your main speak() request to make sure that it it attached the the previous (silence) with no gap... and that it doesn't prematurely end the first (silent) utterance.
There are a lot of variables at play here. You could consider:
Can you find an app that uses TextToSpeech which DOES work correctly even on these problematic devices? If so, then the problem must be solvable in code, and maybe you could find the source code for the app you tested and look at how they are setting up the AudioManager.

Android: How to Toggle Between Vibrate Settings (Always, Never, Only in Silent Mode, Only When Not in Silent Mode)? Revisited

Issue
Finding methods to toggle between:
Always
Never
Only in Silent Mode
Only When Not in Silent Mode
These choices are found by the path --- Menu >> Settings >> Sound >> Vibrate --- on the phone.
It is simple to change by navigation on the phone (by the way, my phone is a Motorola Atrix 2 with Android 2.3.3), but I have yet to come across methods to use in my code.
Code
I basically have buttons that should manipulate the vibrate settings when clicked. One of these buttons is shown here:
bSilent.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
audioManager.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER, AudioManager.VIBRATE_SETTING_OFF);
audioManager.setVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION, AudioManager.VIBRATE_SETTING_OFF);
Toast.makeText(getBaseContext(), "Set to Never", Toast.LENGTH_SHORT).show();
}
});
audioManager is defined somewhere above this code as:
final AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
Android offers the AudioManager.setVibrateSetting, but it is now deprecated. Instead, they reference you to the getRingerMode method.
http://developer.android.com/reference/android/media/AudioManager.html
However, using these functions (and any combination of them) do not efficiently move between the four vibrate settings. For example, if I start at "Always", it is seemingly impossible for me to get to "Never". All combinations of vibrate methods will only move between "Always" and "Only in Silent Mode". On the other hand, if I start at "Never", the offered methods will only toggle between "Never" and "Only When Not in Silent Mode".
Therefore, suppose I want to have my phone in silent mode and want it to vibrate. Then, I decide I do not wish it to vibrate any longer. I am unable to switch from "Always" or "Only in Silent Mode" to "Never".
Past Solutions and Posts
I am aware that this is somewhat of a duplicate post on StackOverflow. The issue has been brought up before...
Here: Vibrate settings on Android 2.2
And (more recently) here: Changing vibrate setting
The former of the links provides an "answer". LuTHieR ends up in a discussion and eventually figures out a way on his own. He references the site:
https://android.googlesource.com/platform/packages/apps/Settings/+/froyo-release/src/com/android/settings/SoundSettings.java
and says "I looked at the source code of the com.android.settings.Settings class and copied part of the methods that enable and disable vibrate".
I looked through this site vigorously and could not find what he did. Could anyone clarify his solution?
Question
Does anyone have a way to precisely toggle between "Always", "Never", "Only in Silent Mode", and "Only When Not in Silent Mode"?
My solution (path of the function with income String sParam with needed mode of vibration set, refactoring if need to integer 0-3):
AudioManager audioManager = getSystemService( Context.AUDIO_SERVICE);
if( Build.VERSION.SDK_INT < 16)
{
// sParam may be:
// 0 - Always
// 1 - Never
// 2 - Only in silent mode (when sound is off)
// 3 - Only when not in silent mode (when sound is on)
if( (sParam.equals( "1") == true) || (sParam.equals( "3") == true))
{
Settings.System.putInt( Static.contextApplication.getContentResolver(), "vibrate_in_silent", 0);
if( sParam.equals( "1") == true)
audioManager.setVibrateSetting( AudioManager.VIBRATE_TYPE_RINGER, AudioManager.VIBRATE_SETTING_OFF);
if( sParam.equals( "3") == true)
audioManager.setVibrateSetting( AudioManager.VIBRATE_TYPE_RINGER, AudioManager.VIBRATE_SETTING_ON);
}
if( (sParam.equals( "0") == true) || (sParam.equals( "2") == true))
{
Settings.System.putInt( Static.contextApplication.getContentResolver(), "vibrate_in_silent", 1);
if( sParam.equals( "0") == true)
audioManager.setVibrateSetting( AudioManager.VIBRATE_TYPE_RINGER, AudioManager.VIBRATE_SETTING_ON);
if( sParam.equals( "2") == true)
audioManager.setVibrateSetting( AudioManager.VIBRATE_TYPE_RINGER, AudioManager.VIBRATE_SETTING_ONLY_SILENT);
}
}
// else (for new SDK > 16 via setRingerMode() ??? )

How can I find out if a device has a vibrator?

I have a device of which I don't know if it has a vibrator.
Is there a way to query for the availability of the vibrator?
The Vibrator class does just that. It's hasVibrator() method returns a boolean indicating if vibrating is supported.
Get an instance of the Vibrator class which is a system service.
Query the Vibrator class using the hasVibrator() method.
String vs = Context.VIBRATOR_SERVICE;
Vibrator mVibrator = (Vibrator)getSystemService(vs);
boolean isVibrator = mVibrator.hasVibrator();
This may help for API<11:
Context.getSystemService() returns a service object or null if no service.
if ( getSystemService(VIBRATOR_SERVICE) != null ) {
//Vibrator exists
}
You need to support (at least) Android 3.0 (11 HoneyComb) before you can use hasVibrator().
Oddly, you can use vibrate() itself on any/all versions.
So the REAL question is: How do v1 - v10 detect if the device has vibrator?
(Or will nothing bad happen if you try to vibrate a device without a vibrator?)
Maybe it'll help someone.
Since I'm building a PWA, I ended up checking the screen size:
if (window.innerWidth < 600) {
window.navigator.vibrate(300);
}

Categories

Resources