4

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?

John Boy
  • 41
  • 3
  • Having this exact same issue. Have also tried detecting Android Auto via the dock API (https://developer.android.com/training/monitoring-device-state/docking-monitoring) to no avail. I have a feeling this is a bug. I also tried updating target SDK to 31 to no avail. – Jared Andrews Nov 22 '21 at 19:12
  • 1
    Hello John Boy, I have filed an issue on the Google Issue Tracker: https://issuetracker.google.com/issues/207389461 – Jared Andrews Nov 22 '21 at 21:23
  • @JaredAndrews Thank you very much for confirming that I'm not alone, and for filing an issue. – John Boy Nov 22 '21 at 21:38

2 Answers2

1

You need to use the CarConnection API documented here

  • This is not a solution for apps such as mine that are not designed or intended for Android Auto. Mine is a driving app that detects Android Auto connections in order to best route audio notifications in the way that the user wants. This detection is broken on Android 12, and using APIs for Android Auto apps is not an available workaround. – John Boy Mar 08 '22 at 17:38
  • 1
    Why wouldn'it suitable? CarConnection API gives you the connection status, you don't have to design for Android Auto to retrieve the ConnectionType – Pierre-Olivier Dybman Mar 08 '22 at 19:00
  • In my case, I don't want to query the connection status. Instead, I want my receiver to be invoked when the phone is connected to the car so that my app, which is not running, can perform a network call to get a status to show in a notification in Android Auto. – 80sTron Oct 30 '22 at 00:02
  • CarConnection API requires API 29 or higher...my app runs with minSDKVersion 21, going to 29 is not an alternative – GGK stands for Ukraine Dec 29 '22 at 14:35
1

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.

G.Zxuan
  • 66
  • 3