Android Auto consiste à utiliser son smartphone dans son véhicule. Fonctionnant sous la forme d'un écran externe, l'interface est optimisée. Il s'agit de l'équivalent de CarPlay chez Apple.
Il ne faut pas confondre avec Android Automotive, dont le tableau de bord est entièrement sous Android. Des voitures comme la dernière Renault Mega électrique ou la Polestar 2 (groupe Volvo) en bénéficient.
Vous pouvez directement regarder la vidéo ou suivre les instructions ci-dessous.
Aucun émulateur ne permet d'utiliser Android Auto, il faut obligatoirement passer par un "vrai" téléphone. Sur celui-ci, commencez par vous assurer d'avoir la dernière version d'Android Auto.
Une fois celle-ci installée, vous noterez qu'elle n'est pas présente sur le bureau. Pour y accéder, il faut suivre ces étapes :
Sur un téléphone Samsung :
Pour que l'application puisse être utilisée en debug, descendez jusqu'à la section Version et cliquez à de nombreuses reprises dessus, jusqu'à voir apparaître cette popup :
Cliquez alors sur OK.
Ensuite, appuyez sur les Trois points en haut de l'écran et sélectionnez Démarrer serveur unité princ.
Vous pouvez également vous rendre dans les Paramètres pour développeurs, afin d'activer certaines fonctionnalités (ex : forcer le mode sombre).
Accédez aux Paramètres d'Android Studio et rendez-vous dans Appearance & Behavior -> System settings -> Android SDK
. Puis allez dans l'onglet SDK Tools
.
Cochez ensuite la case de Android Auto Desktop Head Unit Emulator
et validez :
Sur macOS, le SDK se trouve dans ce dossier :
~/Library/Android/sdk/
Sur Windows et Linux, vous pouvez trouver son emplacement sur l'écran précédent :
Sur les système *nix (macOS et Linux), assurez-vous de donner les droits en exécution :
chmod +x extras/google/auto/desktop-head-unit
Puis tapez :
adb forward tcp:5277 tcp:5277
Il ne reste plus qu'à lancer le DHU :
./extras/google/auto/desktop-head-unit
Vous devriez alors voir apparaître cet écran :
Cliquez sur Continuer jusqu'à arriver sur l'interface principale :
Récupérez le projet sur ce git :
https://github.com/g123k/android_auto_base_project
Et ouvrez-le dans Android Studio.
Afin de lancer l'application, cliquez sur Automotive
en haut de l'écran et sélectionnez Edit Configurations...
Dans la section Launch Options
, sélectionnez Nothing
.
Vous pourrez alors installer l'application, en faisant un Run
.
⚠️ Normalement, l'application n'apparaît pas sur l'accueil Android Auto. Dans ce cas, débranchez, puis rebranchez le câble USB. Retapez la commande :
adb forward tcp:5277 tcp:5277
Et relancez le DHU :
./extras/google/auto/desktop-head-unit
L'application devrait alors apparaître :
Elle vous affichera alors un Hello World :
Contrairement à une application Android classique, Android Auto fonctionne avec ses propres notions. Il n'est donc pas question ici d'Activity
, de Fragment
, de Navigation Graph
...
À la place, nous avons trois grandes notions :
Pour simplifier, un Service
sur Android permet d'exécuter du code sans qu'une interface graphique ne soit affichée à l'écran. Le CarAppService
est une extension d'un service classique visant à créer une Session
.
Une Session
représente - comme son nom l'indique - une session dans l'application. Elle va tout simplement gérer une série d'écrans.
Les Screen
s désignent des écrans. Pour basculer de l'un à l'autre, on utilise le ScreenManager
, qui a deux mécanismes :
Push
: Ouvrir un nouvel écranPop
: Fermer un ou plusieurs écransSi on schématise, nous avons ainsi :
Dans le code donné, vous aurez ce visuel :
Si on regarde, on a l'équivalent d'une Toolbar avec l'icône verte :
PaneTemplate.Builder(Pane.Builder().build())
.setHeaderAction(Action.APP_ICON)
.build()
Puis le contenu est créé avec :
val row = Row.Builder().setTitle("Hello World!").build()
PaneTemplate.Builder(Pane.Builder().addRow(row).build())
1. Faire en sorte de modifier le header, pour donner le titre "Welcome".
2. Modifier le contenu de la Row
pour ajouter du texte en dessous.
Y-a-t-il une limite sur le nombre de lignes ?
3. Que permet la méthode setBrowsable
?
Si vous indiquez setBrowsable(true)
, que se passe-t-il ?
4. Utilisez la méthode setImage
pour ajouter une image prédéfinie
5. Essayons maintenant d'utiliser notre propre image.
Pour cela ajoutez dans les drawable
de l'application, le contenu suivant :
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M17.6,11.48 L19.44,8.3a0.63,0.63 0,0 0,-1.09 -0.63l-1.88,3.24a11.43,11.43 0,0 0,-8.94 0L5.65,7.67a0.63,0.63 0,0 0,-1.09 0.63L6.4,11.48A10.81,10.81 0,0 0,1 20L23,20A10.81,10.81 0,0 0,17.6 11.48ZM7,17.25A1.25,1.25 0,1 1,8.25 16,1.25 1.25,0 0,1 7,17.25ZM17,17.25A1.25,1.25 0,1 1,18.25 16,1.25 1.25,0 0,1 17,17.25Z"
android:fillColor="#FF000000"/>
</vector>
En utilisant le CarIcon.Builder
, faites en sorte d'afficher maintenant l'image ajoutée dans les ressources.
6. Imaginons que nous voulions utiliser le setLoading(true)
dans le PaneBuilder
, que doit-on changer pour que cela fonctionne ?
7. Créez plusieurs Row
(une quinzaine par exemple) et faites en sorte de les afficher les unes en dessous des autres :
Aide :
val l = List(15) {
// Ce lambda sera appelé 15x (it désignant l'élément courant [0, 1, 2...])
}
8. Essayez de scroller. Que se passe-t-il ?
9. Essayez maintenant d'afficher un Switch
à côté du contenu. Que se passe-t-il ?
10. Terminons par les Action
s. Essayez d'en ajouter. Que permettent-elles ? Y a-t-il un nombre maximum ?
Vous avez vu que tout ne marchait pas forcément. Les listes devraient nous débloquer. Passons donc au chapitre suivant.
Jusqu'à présent, nous nous sommes limités à un seul élément (ou Pane
) qui contient des Row
et des Action
. Mais nous avons vu que le scroll était très rapidement limité, d'où le passage aux listes (scrollables). Il faut pour cela utiliser le ItemList.Builder()
1. En utilisant le ItemList.Builder()
, faites en sorte de donner 15 Rows avec la méthode addItem
.
2. Pour pouvoir afficher le contenu, il ne faut plus utiliser un PaneTemplate, mais le code suivant :
ListTemplate.Builder()
.setSingleList(builder.build())
.setTitle("Test")
.build()
Vous devriez alors avoir un résultat de ce type :
3. Tout s'affiche-t-il correctement ou y a-t-il encore des problèmes ?
4. Ajoutez un ActionStrip
sur le ListTemplate.Builder
.
Qu'est-ce que cela permet-il ? Y a-t-il - là aussi - une limitation sur le nombre d'éléments affichables ?
5. Plutôt qu'appeler setSingleList
, essayez addSectionedList
. Quelle est la différence ? Y a-t-il - là aussi - une limitation sur le nombre d'éléments affichables ?
6. Remplacez le ListTemplate
par un GridTemplate
et faites les éventuelles modifications dans votre code.
Jusqu'à présent, nous avons utilisé un seul écran : le HelloWorldScreen
.
1. Créez deux nouvelles classes (nommons les BScreen
et CScreen
) qui étendent et reprennent le même type de constructeur que HelloWorldScreen
.
2. Renommez le HelloWorldScreen
en AScreen
.
3. Modifiez AScreen
pour qu'il affiche une Row
et qu'au clic, cela ouvre BScreen
.
4. Modifiez BScreen
pour qu'il affiche une Row
et qu'au clic, cela ouvre CScreen
.
5. Dans CScreen
, ajoutez deux actions strips
:
screenManager.pop()
screenManager.popToRoot()
Quelle est la différence ?
6. Dans AScreen
, faites en sorte de lui donner pour Marker
: "AScreen".
7. À la place du screenManager.pop()
, appelez screenManager.pop("AScreen")
. Qu'est-ce que cela permet ?
Créez un nouveau Screen
qui se chargera de générer des notifications. Vous indiquerez à l'AppService
que c'est désormais ce Screen
qui sert d'écran d'accueil.
Depuis Android 8.0, il est obligatoire de créer des channels pour les notifications. Voici un exemple de code :
fun createNotificationChannel(channelName: String, publicName: String) {
val manager = CarNotificationManager.from(carContext)
if (manager.getNotificationChannel(channelName) == null) {
val navChannel = NotificationChannelCompat.Builder(
channelName,
NotificationManagerCompat.IMPORTANCE_HIGH
)
.setName(publicName)
.build()
manager.createNotificationChannel(navChannel)
}
}
Pour publier une notification, il faut a minima fournir : une icône, un titre et un contenu.
val builder: NotificationCompat.Builder =
NotificationCompat.Builder(carContext, "nomduchannel")
builder.setContentTitle("Titre de la notification")
builder.setContentText("Contenu de la notification")
builder.setSmallIcon(R.mipmap.ic_launcher)
val manager = CarNotificationManager.from(carContext)
manager.notify(id, builder) // id est un identifiant unique
1. Que se passe-t-il si vous appelez les deux codes précédemment donnés ?
2. En utilisant un CarAppExtender
, faites en sorte de faire un contenu différent entre ce qui est affiché sur le téléphone et sur Android Auto.
Avant toute chose, il faut obtenir la permission d'accès à la localisation de l'utilisateur. Pour cela, prenez ce code qui ajoute un écran dédié :
import android.Manifest.permission
import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.Screen
import androidx.car.app.model.*
class RequestPermissionScreen(
carContext: CarContext,
var mLocationPermissionCheckCallback: LocationPermissionCheckCallback,
) : Screen(carContext) {
/** Callback called when the location permission is granted. */
interface LocationPermissionCheckCallback {
/** Callback called when the location permission is granted. */
fun onPermissionGranted()
}
override fun onGetTemplate(): Template {
val permissions: MutableList<String> = ArrayList()
permissions.add(permission.ACCESS_FINE_LOCATION)
val message = "This app needs access to location in order to show the map around you"
val listener: OnClickListener = ParkedOnlyOnClickListener.create {
carContext.requestPermissions(
permissions
) { approved: List<String?>, rejected: List<String?>? ->
CarToast.makeText(
carContext,
String.format("Approved: %s Rejected: %s", approved, rejected),
CarToast.LENGTH_LONG
).show()
if (approved.isNotEmpty()) {
mLocationPermissionCheckCallback.onPermissionGranted()
finish()
}
}
}
val action = Action.Builder()
.setTitle("Grant Access")
.setBackgroundColor(CarColor.GREEN)
.setOnClickListener(listener)
.build()
return MessageTemplate.Builder(message).addAction(action).setHeaderAction(
Action.APP_ICON
).build()
}
}
Ensuite, modifiez votre Session
pour qu'elle demande la permission au démarrage de l'application :
override fun onCreateScreen(intent: Intent): Screen {
if (carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED
) {
// TODO Renvoyer l'écran une fois la permission accordée
}
return RequestPermissionScreen(carContext) {
val screenManager = carContext.getCarService(ScreenManager::class.java)
screenManager.pop()
}
}
Nous voulons afficher la liste des ascenseurs disponibles dans le réseau d'IDF Mobilités. Pour cela, nous allons utiliser un nouveau template : le PlaceListMapTemplate
.
Une requête pour obtenir ces résultats se fait sur
https://api.formation-android.fr/idf_elevators
Mais tout le code est déjà prêt dans le projet, via NetworkManager.listIdfElevators()
Avant d'en venir là, essayons d'afficher une liste simple :
override fun onGetTemplate(): Template {
return PlaceListMapTemplate.Builder()
.setItemList(
ItemList.Builder()
.build()
)
.setTitle("Résultats")
.setCurrentLocationEnabled(true)
.setAnchor(
Place.Builder(
CarLocation.create(48.866667, 2.333333)
).build(),
)
.build()
}
1. Dans le ItemList.Builder
, donnez une Row
pour voir à quoi ressemble un élément de résultat.
2. Faites en sorte de donner à la Row
un Metadata
contenant une Place
.
Aide :
.setMetadata(
Metadata.Builder()
.setPlace(
Place.Builder(
CarLocation.create(...
3. Faites en sorte qu'en cliquant sur une Row
, l'Anchor
donné dans la partie 1 change sur celui cliqué.
4. Appelez le NetworkManager.listIdfElevators()
, pour avoir la liste des ascenseurs.
5. Affichez ces éléments dans la liste des résultats
6. Lors de la récupération des résultats, indiquez au PlaceListMapTemplate
qu'il est en mode loading
.
En suivant ce qui est fait dans l'exemple Place List Navigation Template, modifiez votre code pour que le clic sur un résultat de la recherche lance la navigation.