John Boy
John Boy

Reputation: 41

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?

Upvotes: 4

Views: 2351

Answers (2)

G.Zxuan
G.Zxuan

Reputation: 86

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.

Upvotes: 1

Pierre-Olivier Dybman
Pierre-Olivier Dybman

Reputation: 973

You need to use the CarConnection API documented here

Upvotes: 1

Related Questions