3

I am making an app that should change its behavior when the phone is connected to an Android Auto. It does not have any Auto capabilities and will not be marketed/submitted as an Android Auto app.

Is there a way to detect if the phone is connected to an Android Auto? I know it is possible for Auto media apps to detect their connection status via a BroadcastReceiver registered for com.google.android.gms.car.media.STATUS action. The documentation is not 100% clear, so will this reliably work also for all other non-Auto apps?

shelll
  • 3,234
  • 3
  • 33
  • 67

5 Answers5

7

Edit: with Android 12, the solution doesn't work anymore and instead, it's better to use CarConnection API documented here

I know this is an old thread but since it comes first in Google, here is the answer from another question

public static boolean isCarUiMode(Context c) {
UiModeManager uiModeManager = (UiModeManager) c.getSystemService(Context.UI_MODE_SERVICE);
if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
    LogHelper.d(TAG, "Running in Car mode");
    return true;
} else {
    LogHelper.d(TAG, "Running on a non-Car mode");
    return false;
}

}

  • 2
    Thanks for the info. There is on small issue mentioned in the comment for that other SO answer. – shelll Aug 17 '17 at 06:08
  • This is not working anymore on Android 12. Result is always getting 1 which is UI_MODE_TYPE_NORMAL. It was working on Android 11 but there is a problem on Android 12. How can we solve this? – Arda Ç. Feb 19 '22 at 07:22
  • @ArdaÇ. you need to use the CarConnection API, I'll update my answer, thanks for pointing this out. – Pierre-Olivier Dybman Mar 07 '22 at 16:13
3

Configuration.UI_MODE_TYPE_CAR is not working on Anroid 12. 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
  • 1
    This will work, but this query element is needed on Android 11+: `````` – M66B Jul 07 '22 at 06:00
  • @M66B It seems we don't need to declare this provider the solution still work. – G.Zxuan Jul 07 '22 at 07:20
  • @M66B Actually, this provider is declared in the Manifest file in the android auto apk. `` – G.Zxuan Jul 07 '22 at 07:22
  • Without this being declared in the manifest of the connecting app, it won't work because the app won't be allowed to connect to the content provider by Android. – M66B Jul 07 '22 at 10:39
  • It means that your app have Content Provider inside and you want to provide data outside when you declare provider. But in this case your app don't have to declare it cuz your app don't provide data to others. The provider is declared in Android Auto app, which is the app to share connection data to your app. – G.Zxuan Sep 05 '22 at 08:36
  • And for answer by @Pavel Braynin, there is no need to add dependency "androidx.car.app:app-projected:1.1.0", as I stated in the first part of my answer 「But that is too heavy to import entire library only for car connections if you don't need other features.」 – G.Zxuan Sep 05 '22 at 08:42
  • Tested on Pixel 6 Pro with Android 13 and a new Toyota car with Android Auto. Works like charm, no need to declare provider. Thank you! – gregko Aug 17 '23 at 20:29
1

See https://developer.android.com/training/cars/apps#car-connection

val Context.isAndroidAutoConnected: LiveData<Boolean>
    get() = CarConnection(this).type
        .map { it == CarConnection.CONNECTION_TYPE_PROJECTION }

app/build.gradle:

dependencies {
    implementation 'androidx.car.app:app:1.1.0'
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
}
arekolek
  • 9,128
  • 3
  • 58
  • 79
0

Recomendation G.Zxuan work perfect, but we must add in dependecies "androidx.car.app:app-projected:1.1.0" in build.gradle

  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 14 '22 at 17:24
0

@G.Zxuan 's Kotlin solution works great. I've transferred it into Java and added a listener interface. Tested on Android 12

public class AutoConnectionDetector {
    private final Context context;
    private static String TAG = "AutoConnectionDetector";
    private final CarConnectionBroadcastReceiver carConnectionReceiver = new CarConnectionBroadcastReceiver();
    private final CarConnectionQueryHandler carConnectionQueryHandler;
    // columnName for provider to query on connection status
    private static final String CAR_CONNECTION_STATE = "CarConnectionState";

    // auto app on your phone will send broadcast with this action when connection state changes
    private final String ACTION_CAR_CONNECTION_UPDATED = "androidx.car.app.connection.action.CAR_CONNECTION_UPDATED";

    // phone is not connected to car
    private static final int CONNECTION_TYPE_NOT_CONNECTED = 0;

    // phone is connected to Automotive OS
    private final int CONNECTION_TYPE_NATIVE = 1;

    // phone is connected to Android Auto
    private final int CONNECTION_TYPE_PROJECTION = 2;

    private final int QUERY_TOKEN = 42;

    private final String CAR_CONNECTION_AUTHORITY = "androidx.car.app.connection";

    private final Uri PROJECTION_HOST_URI = new Uri.Builder().scheme("content").authority(CAR_CONNECTION_AUTHORITY).build();

    public interface OnCarConnectionStateListener {
        void onCarConnected();

        void onCarDisconnected();
    }

    private static OnCarConnectionStateListener listener;

    public void setListener(OnCarConnectionStateListener listener) {
        AutoConnectionDetector.listener = listener;
    }

    public AutoConnectionDetector(Context context) {
        this.context = context;
        carConnectionQueryHandler = new CarConnectionQueryHandler(context.getContentResolver());
    }

    public void registerCarConnectionReceiver() {
        context.registerReceiver(carConnectionReceiver, new IntentFilter(ACTION_CAR_CONNECTION_UPDATED));
        queryForState();
        Log.i(TAG, "registerCarConnectionReceiver: ");
    }

    public void unRegisterCarConnectionReceiver() {
        context.unregisterReceiver(carConnectionReceiver);
        Log.i(TAG, "unRegisterCarConnectionReceiver: ");
    }

    private void queryForState() {
        String[] projection = {CAR_CONNECTION_STATE};
        carConnectionQueryHandler.startQuery(
                QUERY_TOKEN,
                null,
                PROJECTION_HOST_URI,
                projection,
                null,
                null,
                null
        );
    }

    private static void notifyCarConnected() {
        listener.onCarConnected();
    }

    private static void notifyCarDisconnected() {
        listener.onCarDisconnected();
    }

    class CarConnectionBroadcastReceiver extends BroadcastReceiver {
        // query for connection state every time the receiver receives the broadcast
        @Override
        public void onReceive(Context context, Intent intent) {
            queryForState();
        }
    }

    private static class CarConnectionQueryHandler extends AsyncQueryHandler {
        public CarConnectionQueryHandler(ContentResolver contentResolver) {
            super(contentResolver);
        }

        @Override
        protected void onQueryComplete(int token, Object cookie, Cursor response) {
            if (response == null) {
                Log.w(TAG, "Null response from content provider when checking connection to the car, treating as disconnected");
                notifyCarDisconnected();
                return;
            }
            int 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;
            }
            int connectionState = response.getInt(carConnectionTypeColumn);

            if (connectionState == CONNECTION_TYPE_NOT_CONNECTED) {
                Log.i(TAG, "Android Auto disconnected");
                notifyCarDisconnected();
            } else {
                Log.i(TAG, "Android Auto connected");
                Log.i(TAG, "onQueryComplete: " + connectionState);
                notifyCarConnected();
            }
        }
    }
}
Sinan Ceylan
  • 1,043
  • 1
  • 10
  • 14