MediaSessionCompat behaviour changed in Android 12 and setPlaybackToRemote stopped working properly - android

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).

Related

POST_NOTIFICATIONS permission dialog not showing

I'm facing unwelcome behavior when asking new POST_NOTIFICATIONS permission on some particular device owned by remote tester - Pixel 4a (Android 13). Sadly don't have logs and "cable access". Freshly installed app - still targetting API30, but also tried with target set to 33, just for test - should show custom splashscreen Activity, after that shows "main" Activity, which in onResume tries to create NotificationChannel. This should cause perm dialog pop up
If your app targets 12L (API level 32) or lower, the system shows the permission dialog the first time your app starts an activity after you create a notification channel, or when your app starts an activity and then creates its first notification channel. This is usually on app startup.
Well, not on this Pixel 4a with Android 13, meanwhile on Pixel 6 with Android 13 dialog shows up...
Funniest thing is... When tester install app, runs first time, no dialog, then kill it, navigate to system settings and clear data/cache (or even won't make first run, just clean after installation), then dialogs shows up at "first" run...
Why?!
Edit: so now I can reproduce problem also on Pixel 6. I've introduced middle-Dialog with info about content in pushes/notifications and simple yes/no buttons. "Yes" is creating (first) NotificationChannel and this doesn't cause POST_NOTIFICATIONS perm dialog appearance...
#RequiresApi(Build.VERSION_CODES.O)
fun addStaticNotificationChannel(channelId: String, nameResId: Int, descriptionResId: Int,
importance: Int, soundOn: Boolean = true, forceRecreate: Boolean = false): String {
val name = context.resources.getText(nameResId).toString()
val description = context.resources.getText(descriptionResId).toString()
/*if (manager.getNotificationChannel(channelId) != null) {
if (forceRecreate) manager.deleteNotificationChannel(channelId)
else return channelId
}*/
val channel = NotificationChannel(channelId, name, importance)
channel.description = description
channel.lockscreenVisibility = VISIBILITY_PUBLIC
channel.setShowBadge(true)
channel.enableLights(true)
channel.lightColor = ContextCompat.getColor(context, R.color.tsi_blue)
if (!soundOn)
channel.setSound(null, null)
Log.i(this.javaClass.simpleName, "createNotificationChannel channeldId:$channelId")
manager.createNotificationChannel(channel)
return channelId
}
I've messed up targetSdk and branches of my project, and turned out that:
asking perm straight (ContextCompat.request...) when targetting < 33 won't show any dialog
creating channel when targetting 33+ and no permission granted (yet?) also won't show any dialog
so comprehensive future-proof code for those, who can't jump to 33 right away would be
val targetSdkVersion: Int = activity.applicationContext.applicationInfo.targetSdkVersion
if (targetSdkVersion < 33) {
// behavior when targeting < 33 (Android 13), first channel creation will show perm prompt
Log.i(this.javaClass.simpleName, "trying to create channel, when no perm")
addStaticNotificationChannel(SOME_CHANNEL_ID, R.string.notification_some_channel_title,
R.string.notification_some_channel_desc, IMPORTANCE_LOW, forceRecreate = true)
} else {
Log.i(this.javaClass.simpleName, "asking for notification perm")
ActivityCompat.requestPermissions(activity, arrayOf(android.Manifest.permission.POST_NOTIFICATIONS), 89)
}

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)

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.

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

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

Set lock screen background in Android (like Spotify do)

I know that this topic has been already discussed here, here and here, and the answer seems to be that it is not possible.
But I recently installed Spotify in my Nexus 4 (4.4.2), and it seems to be possible. When I listen a song in Spotify the lock screen background change with the cover of the album that I'm listening (see screenshots).
My theory was:
when the phone is locked they change the phone wallpaper with the album cover in order to change also the lock screen background, then they set back the previous one when the phone is unlocked. But this is not how they do it, because in the permissions list of Spotify there is no "android.permission.SET_WALLPAPER"... :(
How do they do it? Some theory?
Edit:
The solution below only works for applications that have registered itself as a media controller, so apps that don't play audio can't/shouldn't use this mechanism to change the lockscreen wallpaper.
It can be done using RemoteControlClient, part of Android since ICS. If you want a working example, download VLC for Android and check out org.videolan.vlc.AudioService:
This part of the code is to intercept media controls.
/**
* Set up the remote control and tell the system we want to be the default receiver for the MEDIA buttons
* #see http://android-developers.blogspot.fr/2010/06/allowing-applications-to-play-nicer.html
*/
#TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void setUpRemoteControlClient() {
Context context = VLCApplication.getAppContext();
AudioManager audioManager = (AudioManager)context.getSystemService(AUDIO_SERVICE);
if(Util.isICSOrLater()) {
audioManager.registerMediaButtonEventReceiver(mRemoteControlClientReceiverComponent);
if (mRemoteControlClient == null) {
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(mRemoteControlClientReceiverComponent);
PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(context, 0, mediaButtonIntent, 0);
// create and register the remote control client
mRemoteControlClient = new RemoteControlClient(mediaPendingIntent);
audioManager.registerRemoteControlClient(mRemoteControlClient);
}
mRemoteControlClient.setTransportControlFlags(
RemoteControlClient.FLAG_KEY_MEDIA_PLAY |
RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS |
RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
RemoteControlClient.FLAG_KEY_MEDIA_STOP);
} else if (Util.isFroyoOrLater()) {
audioManager.registerMediaButtonEventReceiver(mRemoteControlClientReceiverComponent);
}
}
This part is to update artwork, among other info:
#TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private void updateRemoteControlClientMetadata() {
if(!Util.isICSOrLater()) // NOP check
return;
if (mRemoteControlClient != null) {
MetadataEditor editor = mRemoteControlClient.editMetadata(true);
editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, getCurrentMedia().getAlbum());
editor.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, getCurrentMedia().getArtist());
editor.putString(MediaMetadataRetriever.METADATA_KEY_GENRE, getCurrentMedia().getGenre());
editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, getCurrentMedia().getTitle());
editor.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, getCurrentMedia().getLength());
editor.putBitmap(MetadataEditor.BITMAP_KEY_ARTWORK, getCover());
editor.apply();
}
}
For me, the most instructive example was Random Music Player, mentioned in documentation about Android 4.0 APIs:
"For a sample implementation, see the Random Music Player, which provides compatibility logic such that it enables the remote control client on Android 4.0 devices while continuing to support devices back to Android 2.1."
In addition, I converted text to bitmap to have text as album art.
Well, after trying some ways, I have a simple code here;
Try using this method;
private void updateMetaData() {
mediaSession =new MediaSessionCompat(context,"BXPlayer");
Bitmap cover = BitmapFactory.decodeResource(context.getResources(),
R.drawable.cover2);
mediaSession.setMetadata(new MediaMetadataCompat.Builder()
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, cover)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, mSelectedSong.getArtist())
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, mSelectedSong.getAlbum())
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, mSelectedSong.getTitle())
.build());
}
then in your notification you need to set style to android.support.v4.media.app.NotificationCompat.MediaStyle() and set the media session token to use the current metadata.
Check this snippet below;
builder.setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
.setShowActionsInCompactView(0, 1, 2)
.setMediaSession(mediaSession.getSessionToken()));
return builder.build();
To work, you must include implementation "com.android.support:support-v4:$latest_version" in your app build.gradle
And boom! you are good to go.
So here is the new "official docs"
At the bottom it describes the lock screen details
https://developer.android.com/guide/topics/media-apps/working-with-a-media-session.html#maintain-state
As an alternative, once I understood all the terms and jargon, this tutorial helped me outline the general structure for the MediaSessionCompat services.
https://code.tutsplus.com/tutorials/background-audio-in-android-with-mediasessioncompat--cms-27030
Finally, there is an API for lock screen wallpaper in Nougat and greater.
Why this is not support lib is beyond me at this time.
as explained here the key is to to pass a MediaMetadata object to your MediaSession. If these terms seems alien to you it's best to start the linked tutorial from the top.
I found the .putBitmap(MediaMetadata.METADATA_KEY_ART, bitmap) line to be the one that is taken to load the image to the lockscreen background. But be sure to populate .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bitmap)as well.
I know this is late but perfect answer is still required. So to set lock screen background in Android (like Spotify do) we have to perform following steps.
1. set media session active
mSession.setActive(true). if session is not active so it is not gona show.
2. set playback state
playBackStateBuilder = new PlaybackStateCompat.Builder()
.setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
| PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
| PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_PLAY_PAUSE);`
`mSession.setPlaybackState(playBackStateBuilder.setState(PlaybakStateCompate.STATE_PLAYING, 0, 0).build());
Note: lock screen image is showed when first playback state is set to playing then it can be switch to other states
3. set meta data
mSession.setMetadata(new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap)
.build());
here KEY_ALBUM_ART is required because this is the image which is shown on lock screen.
By setting above three things it had showed on my galaxy device but not on pixels devices so for that follow last point.
4. show notification with media style
NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, CHANNEL_ID);
builder.setStyle(
new androidx.media.app.NotificationCompat.MediaStyle()
);
mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(121, builder.build());

Categories

Resources