Android 12 - media notification disappearing - android

I've been experiencing some odd behavior on Android 12. Specifically with a MediaBrowserService. The app will play sounds in the background for extended periods of time. I am running into two distinct but unwanted behaviors on Android 12.
On a pixel 5a - the notification appears, but will disappear after an hour or so in the background. The music is still playing fine and can be paused if the app is opened up.
On a Samsung s21 - the notification appears when the sound plays. but if it is stopped, and another started, the notification will not appear again upon starting the service until the phone itself is restarted.
On a s9 running Android 10, and a OnePlus 8 running Android 11, there are no issues.
Creating the notification seems pretty straightforward:
companion object {
const val ACTION_STOP = "action_stop"
const val CHANNEL_ID = "app_notification_channel"
const val NOTIFICATION_CHANNEL_TAG = "Playing Controls"
const val ONGOING_NOTIFICATION_ID = 2222
}
private fun refreshNotification(stopCalled: Boolean = false) {
val sounds = playingSounds
if (sounds.isEmpty() || stopCalled) {
// if we lost audio focus we will expect to restart momentarily. Don't stop the service
if (!audioFocusTakenBackground) {
Timber.e("Stopping Foreground")
stopForeground(true)
}
return
}
val builder = NotificationCompat.Builder(this, CHANNEL_ID).apply {
color = this#SoundPlayerService.getColor(R.color.magenta_0FF)
val nowPlaying = getString(R.string.action_now_playing)
setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
setContentTitle(nowPlaying)
setContentText(name)
setSmallIcon(R.drawable.ic_icon_small)
setContentIntent(relaunchPendingIntent())
setSilent(true)
setCategory(NotificationCompat.CATEGORY_SERVICE)
priority = NotificationCompat.PRIORITY_HIGH
addAction(NotificationCompat.Action(R.drawable.ic_playbar_stop, getString(R.string.action_stop), stopIntent))
setStyle(
androidx.media.app.NotificationCompat.MediaStyle()
.setShowActionsInCompactView(0)
.setMediaSession(mediaSession.sessionToken)
.setShowCancelButton(true)
.setCancelButtonIntent(stopIntent)
)
}
// If we lost audio focus in the background, we don't stop the service.
// Ensure that its not called in the background aka failure android 12
if (!audioFocusTakenBackground && Application.instance.isAppInForeground) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(
ONGOING_NOTIFICATION_ID,
builder.build(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
)
} else {
startForeground(
ONGOING_NOTIFICATION_ID,
builder.build()
)
}
}
}
The media session is created earlier:
// Create a MediaSession
mediaSession = MediaSessionCompat(this, this.javaClass.name).apply {
setSessionActivity(sessionActivityPendingActivity)
isActive = true
}
sessionToken = mediaSession.sessionToken
The media session is then hooked up to an ExoPlayer instance via a MediaSessionConnector
if (mediaSession != null) {
MediaSessionConnector(mediaSession).also { mediaSessionConnector ->
mediaSessionConnector.setPlayer(mCurrentPlayer)
}
}
Again, this hasn't been an issue until 12.
If anyone has seen this or could point me in a way to try and debug, I'd be extremely grateful.
Thanks!

Related

AudioManager MODE_IN_COMMUNICATION decreases the sound quality and causing crackling/popping sound

Description of the problem:
When the app is connected through Bluetooth headsets, I'm starting the audioManager.startBluetoothSco(). You can check the code below. The connection works well, but I have a problem with the crackling sounds that are played by MediaPlayer. Whenever an action sound is played, the quality is bad or a crackling sound happens. (eg. R.raw.record_start, R.raw.success_action, R.raw.failure_action)
Devices that I used to test:
OpenComm by AfterShokx and Jabra Evolve 65
Samsung A52, Samsung A50, Pixel 3, OnePlus 6 Pro
What I've tried so far?
I tried to change audioManager.mode = AudioManager.MODE_IN_COMMUNICATION to AudioManager.MODE_IN_CALL or AudioManager.MODE_NORMAL.
Changing to MODE_IN_CALL doesn't change the audioManager.mode. The value stays as 0 which is MODE_NORMAL.
Changing to MODE_NORMAL only works on Samsung A52. No crackling sounds everything works perfectly. This is what I want to achieve,
BUT all other phones switch to a different stream or something else; that's why I can't HEAR any media sounds.
Let's say I use Pixel 3 and connected with Jabra Evolve 65 in MODE_NORMAL. I can't hear any kind of media sounds anymore including other apps like Youtube, Spotify, and system sounds, but it works in MODE_IN_COMMUNICATION with a crackling sound.
I tried to change MediaPlayer's AudioAttributes but still bad quality.
val actionSound = if (triggerErrorEarcon) R.raw.failure_action else if (longRecordingBreak) R.raw.success_action else R.raw.record_start
val md = MediaPlayer.create(context, actionSound)
val streamType = if (audioDeviceManager.headsetConnected) AudioManager.STREAM_VOICE_CALL else AudioManager.STREAM_MUSIC
md.setAudioAttributes(
AudioAttributes.Builder()
.setLegacyStreamType(streamType)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build())
md.play()
Possible Solutions?
I don't know how but fixing audioManager.mode or some other properties of audioManager.
Keep using MODE_IN_COMMUNICATION but a fix on MediaPlayer properties to disable bad quality of sound on Bluetooth device.
I can't think of anything else from this point. I hope you could help me with this problem, thanks in advance.
AudioDeviceManager.kt:
class AudioDeviceManager(val context: Context) {
internal val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
internal val headsetConnectedSubject = BehaviorSubject.createDefault(false)
internal val headsetConnected: Boolean get() = headsetConnectedSubject.value ?: false
private val intentFilter = IntentFilter().apply { addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED) }
private val audioDeviceCallback = object: AudioDeviceCallback() {
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
super.onAudioDevicesAdded(addedDevices)
updateBluetoothHeadsetState()
}
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
super.onAudioDevicesRemoved(removedDevices)
updateBluetoothHeadsetState()
}
}
private val scoReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1) == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
// SCO now connected
audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
audioManager.isSpeakerphoneOn = false
audioManager.isBluetoothScoOn = true
}
}
}
fun start() {
if (headsetConnected.not())
audioManager.registerAudioDeviceCallback(audioDeviceCallback, null)
}
fun stop(unregisterAudioDeviceCallback: Boolean = false) {
if (unregisterAudioDeviceCallback)
unregisterDeviceCallback()
audioManager.mode = AudioManager.MODE_NORMAL
audioManager.isSpeakerphoneOn = true
audioManager.isBluetoothScoOn = false
audioManager.stopBluetoothSco()
}
private fun unregisterDeviceCallback() = audioManager.unregisterAudioDeviceCallback(audioDeviceCallback)
fun updateBluetoothHeadsetState() {
val headset = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)?.firstOrNull { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }
val headsetConnected = headset != null
headsetConnectedSubject.onNext(headsetConnected)
if (headsetConnected) {
audioManager.startBluetoothSco()
context.registerReceiver(scoReceiver, intentFilter)
} else {
audioManager.mode = AudioManager.MODE_NORMAL
audioManager.isSpeakerphoneOn = true
audioManager.isBluetoothScoOn = false
audioManager.stopBluetoothSco()
}
}
}
Playing an action sound in the app:
MediaPlayer.create(context, R.raw.record_start)
.play()
.subscribe()
.addTo(disposable)
Extension function:
fun MediaPlayer.play(): Completable {
return Completable.create { emitter ->
var isCancelled = false
emitter.setCancellable {
isCancelled = true
}
setOnCompletionListener {
GlobalScope.launch {
// release the sound a bit later. Listener is triggering so fast!
delay(3000)
it.release()
}
if (isCancelled) { return#setOnCompletionListener }
emitter.onComplete()
}
start()
}
}

How to detect Android Auto is connected with Android 12

In my driving-companion app, I have a need to detect the state of Android Auto. For several years now, I've been using UiModeManager to get the current state at startup and a BroadcastReceiver to detect state changes while the app is running. This has always worked perfectly, until Android 12. With Android 12, UiModeManager always reports UI_MODE_TYPE_NORMAL, even when Android Auto is connected and active, and my BroadcastReceiver is never called after connecting or disconnecting.
This is my code for detecting state at startup:
inCarMode = uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR;
and this is my BroadcastReceiver setup:
IntentFilter carModeFilter = new IntentFilter();
carModeFilter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE);
carModeFilter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE);
registerReceiver(carModeReceiver, carModeFilter);
Again, this has always worked perfectly with Android 5 through Android 11. Is this a bug in Android 12, or is there some new way to detect Android Auto state in Android 12?
You need to use the CarConnection API documented here
Configuration.UI_MODE_TYPE_CAR is not working on Anroid 12. As #Pierre-Olivier Dybman said, you can use CarConnection API in the androidx.car.app:app library. But that is too heavy to import entire library only for car connections if you don't need other features.
So I write a piece of code base on the CarConnection to detect Android Auto connection, as below:
class AutoConnectionDetector(val context: Context) {
companion object {
const val TAG = "AutoConnectionDetector"
// columnName for provider to query on connection status
const val CAR_CONNECTION_STATE = "CarConnectionState"
// auto app on your phone will send broadcast with this action when connection state changes
const val ACTION_CAR_CONNECTION_UPDATED = "androidx.car.app.connection.action.CAR_CONNECTION_UPDATED"
// phone is not connected to car
const val CONNECTION_TYPE_NOT_CONNECTED = 0
// phone is connected to Automotive OS
const val CONNECTION_TYPE_NATIVE = 1
// phone is connected to Android Auto
const val CONNECTION_TYPE_PROJECTION = 2
private const val QUERY_TOKEN = 42
private const val CAR_CONNECTION_AUTHORITY = "androidx.car.app.connection"
private val PROJECTION_HOST_URI = Uri.Builder().scheme("content").authority(CAR_CONNECTION_AUTHORITY).build()
}
private val carConnectionReceiver = CarConnectionBroadcastReceiver()
private val carConnectionQueryHandler = CarConnectionQueryHandler(context.contentResolver)
fun registerCarConnectionReceiver() {
context.registerReceiver(carConnectionReceiver, IntentFilter(ACTION_CAR_CONNECTION_UPDATED))
queryForState()
}
fun unRegisterCarConnectionReceiver() {
context.unregisterReceiver(carConnectionReceiver)
}
private fun queryForState() {
carConnectionQueryHandler.startQuery(
QUERY_TOKEN,
null,
PROJECTION_HOST_URI,
arrayOf(CAR_CONNECTION_STATE),
null,
null,
null
)
}
inner class CarConnectionBroadcastReceiver : BroadcastReceiver() {
// query for connection state every time the receiver receives the broadcast
override fun onReceive(context: Context?, intent: Intent?) {
queryForState()
}
}
internal class CarConnectionQueryHandler(resolver: ContentResolver?) : AsyncQueryHandler(resolver) {
// notify new queryed connection status when query complete
override fun onQueryComplete(token: Int, cookie: Any?, response: Cursor?) {
if (response == null) {
Log.w(TAG, "Null response from content provider when checking connection to the car, treating as disconnected")
notifyCarDisconnected()
return
}
val carConnectionTypeColumn = response.getColumnIndex(CAR_CONNECTION_STATE)
if (carConnectionTypeColumn < 0) {
Log.w(TAG, "Connection to car response is missing the connection type, treating as disconnected")
notifyCarDisconnected()
return
}
if (!response.moveToNext()) {
Log.w(TAG, "Connection to car response is empty, treating as disconnected")
notifyCarDisconnected()
return
}
val connectionState = response.getInt(carConnectionTypeColumn)
if (connectionState == CONNECTION_TYPE_NOT_CONNECTED) {
Log.i(TAG, "Android Auto disconnected")
notifyCarDisconnected()
} else {
Log.i(TAG, "Android Auto connected")
notifyCarConnected()
}
}
}
}
This solution works on android 6~12. If you need to detect car connection status on android 5, use the Configuration.UI_MODE_TYPE_CAR solution.

Unable to run ble scanner when screen is locked

Am running a foreground service to scan ble devices which is working fine when the phone is not locked. But when the phone is locked, the scanner is unable to detect any devices near by. The scanned count is always 0 when the phone is locked. I have also added the filter for my scanner but still no fortune. Looking for some help.
//adding filters of the manufacturer and the uuid
fun startScan(){
settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
val builder = ScanFilter.Builder()
builder.setManufacturerData(0x004c, byteArrayOf())
val manufactureFilter= builder.build()
val uuidBuilder = ScanFilter.Builder()
val serviceUuidString = "f8c62883-xxxx-xxxx-xxxx-430326af8bd0"
val serviceUuidMaskString = "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"
val parcelUuid: ParcelUuid = ParcelUuid.fromString(serviceUuidString)
val parcelUuidMask: ParcelUuid = ParcelUuid.fromString(serviceUuidMaskString)
uuidBuilder.setServiceUuid(parcelUuid, parcelUuidMask)
val uuidFilter = uuidBuilder.build()
filters = ArrayList<ScanFilter>()
filters.add(manufactureFilter)
filters.add(uuidFilter)
scanLeDevice(true)
}
//to start the ble scan for a short period
fun scanLeDevice(enable: Boolean) {
if (enable) {
Log.i(TAG, "Scanning started")
if(beaconCollectionTimer != null){
beaconCollectionTimer?.cancel()
}
beaconCollectionTimer = Timer()
beaconCollectionTimer?.schedule(object : TimerTask(){
override fun run() {
scanLeDevice(false)
}
}, SCANNING_INTERVEL)
bluetoothAdapter.getBluetoothLeScanner()
.startScan(filters, settings, mScanCallback)
} else {
Log.i(TAG, "scanning stopped")
if (bluetoothAdapter.getBluetoothLeScanner() != null) {
bluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback)
}
isScanning = false
}
}
After trying various libraries to get my scanner work properly, I realized that the issue is not in the code but with the battery saver. All I did is removed the app from the battery optimization apps list and my scanner started working as expected. Even after the screen is locked am able to run the bleScanner and detect the near by devices.

How do I make sure my notification sounds play reliably on all devices?

My app sets up two notification channels, each with its own sound effect. But users are saying the sounds aren't working right on some devices, like the Pixel. Here's my setup. Is there anything I could do to improve the channel settings to ensure that the sounds (two .mp3 files kept in res/raw/) play as reliably as possible (as background notifications, not foreground)? Are there better audio settings or formats or configuration that I am missing?
I'm using Firebase Cloud Messaging. Here's the code creating the channels:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val importance = NotificationManager.IMPORTANCE_HIGH
val audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ALARM)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setFlags(FLAG_AUDIBILITY_ENFORCED)
.build()
val newChannel = NotificationChannel(channelId, name, importance)
newChannel.description = description
newChannel.importance = IMPORTANCE_HIGH
newChannel.setSound(sound, audioAttributes)
return newChannel
}
And here's code for obtaining the sound URI:
private fun createSoundUri(soundNum: Int, context: Context):Uri? {
val scheme = ContentResolver.SCHEME_ANDROID_RESOURCE
val packageName = context.packageName
val root = "$scheme://$packageName/"
return when(soundNum) {
0 -> Uri.parse(root + R.raw.sound1)
1 -> Uri.parse(root + R.raw.sound2)
2 -> Settings.System.DEFAULT_NOTIFICATION_URI
else -> null
}
}
I received data using FCM then started an activity initiating the media player in my app.
MediaPlayer mediaPlayer = MediaPlayer.create(this,R.raw.ringtone);
mediaPlayer.setLooping(true);
mediaPlayer.start();
Maybe you could use this idea

Notifications not working when app is swiped away

I'm writing a simple app that sets up to 8 random, repeating alarms, sends a notification, and then generates a quote when the user taps on the notification. This all seems to work properly when the app is running but when the app is swiped away from the recent apps, or force closed, the notifications don't work.
I've poured over my research from the last several days and can't find a solution that's current or fixes my problem. The most common thing I've seen is to use onReceive to set up a service, but my reading has shown me that this no longer works with Oreo and is outdated advice. I've also seen some stuff about foreground services, but I'm really not looking to have a persistent notification bothering the user.
I've also seen some people say to do some work in onDestroy, but that hasn't worked for me either. A lot of the stuff I've found has said that this kind of behavior is "expected behavior", as the system assumes that if an app is swiped away, the user no longer wants it doing anything. I don't want this happening and there must be some way around it, since reminders and notifications from other apps are able to come through.
Any help would be greatly appreciated, I've been struggling with this for a long time. I'll post my code for setting alarms below, as well as the code for setting up the notification channels and the BroadcastReceiver class.
By the way, my test device is a Pixel 2XL with Android 9.
//method to save preferences when the user clicks "SAVE"
private fun saveData() {
if (NOTIFICATIONS_PER_DAY > 0) {
setAlarms()
} else {
clearAlarms() //clearing if the user is removing notifications
}
val sharedPreferences = activity!!.getSharedPreferences(SHARED_PREFS, MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.putString(THEME_PREF, THEME_SELECTED)
editor.putInt(NOTS_PREF, NOTIFICATIONS_PER_DAY)
editor.apply()
Toast.makeText(context, "Settings saved", Toast.LENGTH_SHORT).show()
}//saveData method
//method to set repeating notification alarms (random times)
private fun setAlarms() {
//clearing any previously saved alarms to prevent tons of extra
clearAlarms()
calList.clear()
var hour: Int
var minute: Int
for (i in 0 until (NOTIFICATIONS_PER_DAY)) {
val hourRand = (0..23).random()
val minuteRand = (0..59).random()
hour = hourRand
minute = minuteRand
val cal = Calendar.getInstance()
cal.set(Calendar.HOUR_OF_DAY, hour)
cal.set(Calendar.MINUTE, minute)
cal.set(Calendar.SECOND, 0)
calList.add(cal)
}//for
var i = 0
for (cal in calList) {
val alarmManager = context!!.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, AlertReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(context, i, intent, 0)
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, cal.timeInMillis, AlarmManager.INTERVAL_DAY, pendingIntent)
println(i)
i++
}//for
}//setAlarms method
class BetterDays : Application() {
override fun onCreate() {
super.onCreate()
createNotificationChannels()
}
private fun createNotificationChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel1 = NotificationChannel(CHANNEL_1_ID, "Quote Channel", NotificationManager.IMPORTANCE_DEFAULT).apply { description = "New quotes notification" }
channel1.enableLights(true)
channel1.enableVibration(true)
//channel1.description = "New quotes notification"
/* val channel2 = NotificationChannel(CHANNEL_2_ID, "New Quote!", NotificationManager.IMPORTANCE_DEFAULT)
channel2.enableLights(true)
channel2.enableVibration(true)
channel2.description = "New quotes notification" */
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel1)
//manager.createNotificationChannel(channel2)
}
}//createNotificationChannels method
companion object {
val CHANNEL_1_ID = "quotes notification"
val CHANNEL_2_ID = "quotes notification 2"
}
}
class AlertReceiver : BroadcastReceiver() {
private var notificationManager: NotificationManagerCompat? = null
private var theContext: Context? = null
override fun onReceive(context: Context, intent: Intent) {
notificationManager = NotificationManagerCompat.from(context)
theContext = context
sendOnChannel1()
}//onReceive method
private fun sendOnChannel1() {
val title = "New Quote Available"
val message = "Come check it out!"
var index: Int = 0
if(quotesList.size != 0) {
index = Random.nextInt(quotesList.size)
}//if
quoteText = quotesList[index]
speakerText = speakersList[index]
quoteTextView?.text = quotesList[index]
speakerTextView?.text = speakersList[index]
val intent = Intent(theContext!!, MainActivity::class.java)
intent.putExtra("From", "quotesFragment")
val pendingIntent: PendingIntent = PendingIntent.getActivity(theContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val notification = NotificationCompat.Builder(theContext!!, CHANNEL_1_ID)
.setSmallIcon(R.drawable.ic_quotes)
.setContentTitle(title)
.setContentText(message)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setContentIntent(pendingIntent)
.build()
val id = createID()
notificationManager!!.notify(id, notification)
}//sendOnChannel1 method
/* //for future functionality
fun sendOnChannel2() {
val title = "Title"
val message = "Message"
val notification = NotificationCompat.Builder(theContext!!, CHANNEL_2_ID)
.setSmallIcon(R.drawable.ic_quotes)
.setContentTitle(title)
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.build()
notificationManager!!.notify(2, notification)
}//sendOnChannel2 method*/
//method to generate a unique ID
private fun createID(): Int{
val now = Date()
val id = Integer.parseInt(SimpleDateFormat("ddHHmmss", Locale.US).format(now))
return id
}//createID method
}//AlertReceiver class
Some chinese device with their own modified android system so when the apps are swiped from the recent app tray your app gets terminated (similar to Force Stop). And due to this every task running in the background like Services, Jobs gets killed with the app. Even High priority FCM doesn’t see the daylight in Chinese ROMs.
you can read in here : https://medium.com/mindorks/enable-background-services-in-chinese-roms-32e73dfba1a6
maybe can help ;)

Categories

Resources